8e12759e9d04d0e4fccda88040086ac11764f47c
[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_show_latest (TNY_GTK_HEADER_LIST_MODEL (headers), priv->show_latest);
1187
1188         /* Start the monitor in the callback of the
1189            tny_gtk_header_list_model_set_folder call. It's crucial to
1190            do it there and not just after the call because we want the
1191            monitor to observe only the headers returned by the
1192            tny_folder_get_headers_async call that it's inside the
1193            tny_gtk_header_list_model_set_folder call. This way the
1194            monitor infrastructure could successfully cope with
1195            duplicates. For example if a tny_folder_add_msg_async is
1196            happening while tny_gtk_header_list_model_set_folder is
1197            invoked, then the first call could add a header that will
1198            be added again by tny_gtk_header_list_model_set_folder, so
1199            we'd end up with duplicate headers. sergio */
1200         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1201                                               folder, refresh,
1202                                               set_folder_intern_get_headers_async_cb,
1203                                               NULL, self);
1204
1205         /* Init filter_row function to examine empty status */
1206         priv->status  = HEADER_VIEW_INIT;
1207
1208         /* Create sortable model */
1209         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1210         g_object_unref (headers);
1211
1212         /* Create a tree model filter to hide and show rows for cut operations  */
1213         filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1214         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1215                                                 filter_row, self, NULL);
1216         g_object_unref (sortable);
1217
1218         /* install our special sorting functions */
1219         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1220
1221         /* Restore sort column id */
1222         if (cols) {
1223                 type  = modest_tny_folder_guess_folder_type (folder);
1224                 if (type == TNY_FOLDER_TYPE_INVALID)
1225                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1226
1227                 sort_colid = modest_header_view_get_sort_column_id (self, type);
1228                 sort_type = modest_header_view_get_sort_type (self, type);
1229                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1230                                                       sort_colid,
1231                                                       sort_type);
1232                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1233                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1234                                                  (GtkTreeIterCompareFunc) cmp_rows,
1235                                                  cols->data, NULL);
1236                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1237                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1238                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
1239                                                  cols->data, NULL);
1240         }
1241
1242         /* Set new model */
1243         gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1244         modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1245         g_object_unref (filter_model);
1246
1247         /* Free */
1248         g_list_free (cols);
1249 }
1250
1251 void
1252 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1253                                       guint sort_colid,
1254                                       GtkSortType sort_type)
1255 {
1256         ModestHeaderViewPrivate *priv = NULL;
1257         GtkTreeModel *sortable = NULL, *filter_model = NULL;
1258         TnyFolderType type;
1259
1260         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1261         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1262
1263         /* Get model and private data */
1264         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1265         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1266         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1267                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1268         }
1269
1270         /* Sort tree model */
1271         type  = modest_tny_folder_guess_folder_type (priv->folder);
1272         if (type == TNY_FOLDER_TYPE_INVALID)
1273                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1274         else {
1275                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1276                                                       sort_colid,
1277                                                       sort_type);
1278                 /* Store new sort parameters */
1279                 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1280         }
1281 }
1282
1283 void
1284 modest_header_view_set_sort_params (ModestHeaderView *self,
1285                                     guint sort_colid,
1286                                     GtkSortType sort_type,
1287                                     TnyFolderType type)
1288 {
1289         ModestHeaderViewPrivate *priv;
1290         ModestHeaderViewStyle style;
1291
1292         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1293         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1294         g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1295
1296         style = modest_header_view_get_style   (self);
1297         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1298
1299         priv->sort_colid[style][type] = sort_colid;
1300         priv->sort_type[style][type] = sort_type;
1301 }
1302
1303 gint
1304 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1305                                        TnyFolderType type)
1306 {
1307         ModestHeaderViewPrivate *priv;
1308         ModestHeaderViewStyle style;
1309
1310         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1311         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1312
1313         style = modest_header_view_get_style   (self);
1314         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1315
1316         return priv->sort_colid[style][type];
1317 }
1318
1319 GtkSortType
1320 modest_header_view_get_sort_type (ModestHeaderView *self,
1321                                   TnyFolderType type)
1322 {
1323         ModestHeaderViewPrivate *priv;
1324         ModestHeaderViewStyle style;
1325
1326         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1327         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1328
1329         style = modest_header_view_get_style   (self);
1330         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1331
1332         return priv->sort_type[style][type];
1333 }
1334
1335 typedef struct {
1336         ModestHeaderView *header_view;
1337         RefreshAsyncUserCallback cb;
1338         gpointer user_data;
1339 } SetFolderHelper;
1340
1341 static void
1342 folder_refreshed_cb (ModestMailOperation *mail_op,
1343                      TnyFolder *folder,
1344                      gpointer user_data)
1345 {
1346         ModestHeaderViewPrivate *priv;
1347         SetFolderHelper *info;
1348
1349         info = (SetFolderHelper*) user_data;
1350
1351         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1352
1353         /* User callback */
1354         if (info->cb)
1355                 info->cb (mail_op, folder, info->user_data);
1356
1357         /* Start the folder count changes observer. We do not need it
1358            before the refresh. Note that the monitor could still be
1359            called for this refresh but now we know that the callback
1360            was previously called */
1361         g_mutex_lock (priv->observers_lock);
1362         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1363         g_mutex_unlock (priv->observers_lock);
1364
1365         /* Notify the observers that the update is over */
1366         g_signal_emit (G_OBJECT (info->header_view),
1367                        signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1368
1369         /* Allow filtering notifications from now on if the current
1370            folder is still the same (if not then the user has selected
1371            another one to refresh, we should wait until that refresh
1372            finishes) */
1373         if (priv->folder == folder)
1374                 priv->notify_status = TRUE;
1375
1376         /* Frees */
1377         g_object_unref (info->header_view);
1378         g_free (info);
1379 }
1380
1381 static void
1382 refresh_folder_error_handler (ModestMailOperation *mail_op,
1383                               gpointer user_data)
1384 {
1385         const GError *error = modest_mail_operation_get_error (mail_op);
1386
1387         if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1388             error->code == TNY_IO_ERROR_WRITE ||
1389             error->code == TNY_IO_ERROR_READ) {
1390                 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1391                 /* If the mail op has been cancelled then it's not an error: don't show any message */
1392                 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1393                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1394                         modest_platform_information_banner (NULL, NULL, msg);
1395                         g_free (msg);
1396                 }
1397         }
1398 }
1399
1400 void
1401 modest_header_view_set_folder (ModestHeaderView *self,
1402                                TnyFolder *folder,
1403                                gboolean refresh,
1404                                ModestWindow *progress_window,
1405                                RefreshAsyncUserCallback callback,
1406                                gpointer user_data)
1407 {
1408         ModestHeaderViewPrivate *priv;
1409
1410         g_return_if_fail (self);
1411
1412         priv =     MODEST_HEADER_VIEW_GET_PRIVATE(self);
1413
1414         if (priv->folder) {
1415                 if (priv->status_timeout) {
1416                         g_source_remove (priv->status_timeout);
1417                         priv->status_timeout = 0;
1418                 }
1419
1420                 g_mutex_lock (priv->observers_lock);
1421                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1422                 g_object_unref (priv->folder);
1423                 priv->folder = NULL;
1424                 g_mutex_unlock (priv->observers_lock);
1425         }
1426
1427         if (folder) {
1428                 GtkTreeSelection *selection;
1429                 SetFolderHelper *info;
1430                 ModestMailOperation *mail_op = NULL;
1431
1432                 /* Set folder in the model */
1433                 modest_header_view_set_folder_intern (self, folder, refresh);
1434
1435                 /* Pick my reference. Nothing to do with the mail operation */
1436                 priv->folder = g_object_ref (folder);
1437
1438                 /* Do not notify about filterings until the refresh finishes */
1439                 priv->notify_status = FALSE;
1440
1441                 /* Clear the selection if exists */
1442                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1443                 gtk_tree_selection_unselect_all(selection);
1444                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1445
1446                 /* Notify the observers that the update begins */
1447                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1448                                0, TRUE, NULL);
1449
1450                 /* create the helper */
1451                 info = g_malloc0 (sizeof (SetFolderHelper));
1452                 info->header_view = g_object_ref (self);
1453                 info->cb = callback;
1454                 info->user_data = user_data;
1455
1456                 /* Create the mail operation (source will be the parent widget) */
1457                 if (progress_window)
1458                         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1459                                                                                  refresh_folder_error_handler,
1460                                                                                  NULL, NULL);
1461                 if (refresh) {
1462                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1463                                                          mail_op);
1464
1465                         /* Refresh the folder asynchronously */
1466                         modest_mail_operation_refresh_folder (mail_op,
1467                                                               folder,
1468                                                               folder_refreshed_cb,
1469                                                               info);
1470                 } else {
1471                         folder_refreshed_cb (mail_op, folder, info);
1472                 }
1473                 /* Free */
1474                 if (mail_op)
1475                         g_object_unref (mail_op);
1476         } else {
1477                 g_mutex_lock (priv->observers_lock);
1478
1479                 if (priv->monitor) {
1480                         tny_folder_monitor_stop (priv->monitor);
1481                         g_object_unref (G_OBJECT (priv->monitor));
1482                         priv->monitor = NULL;
1483                 }
1484
1485                 if (priv->autoselect_reference) {
1486                         gtk_tree_row_reference_free (priv->autoselect_reference);
1487                         priv->autoselect_reference = NULL;
1488                 }
1489
1490                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1491
1492                 modest_header_view_notify_observers(self, NULL, NULL);
1493
1494                 g_mutex_unlock (priv->observers_lock);
1495
1496                 /* Notify the observers that the update is over */
1497                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1498                                0, FALSE, NULL);
1499         }
1500 }
1501
1502 static void
1503 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1504                          GtkTreeViewColumn *column, gpointer userdata)
1505 {
1506         ModestHeaderView *self = NULL;
1507         GtkTreeIter iter;
1508         GtkTreeModel *model = NULL;
1509         TnyHeader *header = NULL;
1510         TnyHeaderFlags flags;
1511
1512         self = MODEST_HEADER_VIEW (treeview);
1513
1514         model = gtk_tree_view_get_model (treeview);
1515         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1516                 goto frees;
1517
1518         /* get the first selected item */
1519         gtk_tree_model_get (model, &iter,
1520                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1521                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1522                             -1);
1523
1524         /* Dont open DELETED messages */
1525         if (flags & TNY_HEADER_FLAG_DELETED) {
1526                 GtkWidget *win;
1527                 gchar *msg;
1528                 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1529                 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1530                 modest_platform_information_banner (NULL, NULL, msg);
1531                 g_free (msg);
1532                 goto frees;
1533         }
1534
1535         /* Emit signal */
1536         g_signal_emit (G_OBJECT(self),
1537                        signals[HEADER_ACTIVATED_SIGNAL],
1538                        0, header, path);
1539
1540         /* Free */
1541  frees:
1542         if (header != NULL)
1543                 g_object_unref (G_OBJECT (header));
1544
1545 }
1546
1547 static void
1548 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1549 {
1550         GtkTreeModel *model;
1551         TnyHeader *header = NULL;
1552         GtkTreePath *path = NULL;
1553         GtkTreeIter iter;
1554         ModestHeaderView *self;
1555         GList *selected = NULL;
1556
1557         g_return_if_fail (sel);
1558         g_return_if_fail (user_data);
1559
1560         self = MODEST_HEADER_VIEW (user_data);
1561
1562         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1563         if (selected != NULL)
1564                 path = (GtkTreePath *) selected->data;
1565         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1566                 return; /* msg was _un_selected */
1567
1568         gtk_tree_model_get (model, &iter,
1569                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1570                             &header, -1);
1571
1572         /* Emit signal */
1573         g_signal_emit (G_OBJECT(self),
1574                        signals[HEADER_SELECTED_SIGNAL],
1575                        0, header);
1576
1577         g_object_unref (G_OBJECT (header));
1578
1579         /* free all items in 'selected' */
1580         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1581         g_list_free (selected);
1582 }
1583
1584
1585 /* PROTECTED method. It's useful when we want to force a given
1586    selection to reload a msg. For example if we have selected a header
1587    in offline mode, when Modest become online, we want to reload the
1588    message automatically without an user click over the header */
1589 void
1590 _modest_header_view_change_selection (GtkTreeSelection *selection,
1591                                       gpointer user_data)
1592 {
1593         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1594         g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1595
1596         on_selection_changed (selection, user_data);
1597 }
1598
1599 static gint
1600 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1601 {
1602         /* HH, LL, NN */
1603         if (p1 == p2)
1604                 return 0;
1605
1606         /* HL HN */
1607         if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1608                 return 1;
1609
1610         /* LH LN */
1611         if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1612                 return -1;
1613
1614         /* NH */
1615         if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1616                 return -1;
1617
1618         /* NL */
1619         return 1;
1620 }
1621
1622 static gint
1623 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1624           gpointer user_data)
1625 {
1626         gint col_id;
1627         gint t1, t2;
1628         gint val1, val2;
1629         gint cmp;
1630
1631         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1632         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1633
1634
1635         switch (col_id) {
1636         case TNY_HEADER_FLAG_ATTACHMENTS:
1637
1638                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1639                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1640                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1641                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1642
1643                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1644                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1645
1646                 return cmp ? cmp : t1 - t2;
1647
1648         case TNY_HEADER_FLAG_PRIORITY_MASK: {
1649                 TnyHeader *header1 = NULL, *header2 = NULL;
1650
1651                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1652                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1653                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1654                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1655
1656                 /* This is for making priority values respect the intuitive sort relationship
1657                  * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1658
1659                 if (header1 && header2) {
1660                         cmp =  compare_priorities (tny_header_get_priority (header1),
1661                                 tny_header_get_priority (header2));
1662                         g_object_unref (header1);
1663                         g_object_unref (header2);
1664
1665                         return cmp ? cmp : t1 - t2;
1666                 }
1667
1668                 return t1 - t2;
1669         }
1670         default:
1671                 return &iter1 - &iter2; /* oughhhh  */
1672         }
1673 }
1674
1675 static gint
1676 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1677                   gpointer user_data)
1678 {
1679         gint t1, t2;
1680         gchar *val1, *val2;
1681         gint cmp;
1682
1683         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1684
1685         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1686                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1687         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1688                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1689
1690         /* Do not use the prefixes for sorting. Consume all the blank
1691            spaces for sorting */
1692         cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1693                                              g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1694                                              TRUE);
1695
1696         /* If they're equal based on subject without prefix then just
1697            sort them by length. This will show messages like this.
1698            * Fw:
1699            * Fw:Fw:
1700            * Fw:Fw:
1701            * Fw:Fw:Fw:
1702            * */
1703         if (cmp == 0)
1704                 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1705
1706         g_free (val1);
1707         g_free (val2);
1708         return cmp;
1709 }
1710
1711 /* Drag and drop stuff */
1712 static void
1713 drag_data_get_cb (GtkWidget *widget,
1714                   GdkDragContext *context,
1715                   GtkSelectionData *selection_data,
1716                   guint info,
1717                   guint time,
1718                   gpointer data)
1719 {
1720         ModestHeaderView *self = NULL;
1721         ModestHeaderViewPrivate *priv = NULL;
1722
1723         self = MODEST_HEADER_VIEW (widget);
1724         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1725
1726         /* Set the data. Do not use the current selection because it
1727            could be different than the selection at the beginning of
1728            the d&d */
1729         modest_dnd_selection_data_set_paths (selection_data,
1730                                              priv->drag_begin_cached_selected_rows);
1731 }
1732
1733 /**
1734  * We're caching the selected rows at the beginning because the
1735  * selection could change between drag-begin and drag-data-get, for
1736  * example if we have a set of rows already selected, and then we
1737  * click in one of them (without SHIFT key pressed) and begin a drag,
1738  * the selection at that moment contains all the selected lines, but
1739  * after dropping the selection, the release event provokes that only
1740  * the row used to begin the drag is selected, so at the end the
1741  * drag&drop affects only one rows instead of all the selected ones.
1742  *
1743  */
1744 static void
1745 drag_begin_cb (GtkWidget *widget,
1746                GdkDragContext *context,
1747                gpointer data)
1748 {
1749         ModestHeaderView *self = NULL;
1750         ModestHeaderViewPrivate *priv = NULL;
1751         GtkTreeSelection *selection;
1752
1753         self = MODEST_HEADER_VIEW (widget);
1754         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1755
1756         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1757         priv->drag_begin_cached_selected_rows =
1758                 gtk_tree_selection_get_selected_rows (selection, NULL);
1759 }
1760
1761 /**
1762  * We use the drag-end signal to clear the cached selection, we use
1763  * this because this allways happens, whether or not the d&d was a
1764  * success
1765  */
1766 static void
1767 drag_end_cb (GtkWidget *widget,
1768              GdkDragContext *dc,
1769              gpointer data)
1770 {
1771         ModestHeaderView *self = NULL;
1772         ModestHeaderViewPrivate *priv = NULL;
1773
1774         self = MODEST_HEADER_VIEW (widget);
1775         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1776
1777         /* Free cached data */
1778         g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1779         g_list_free (priv->drag_begin_cached_selected_rows);
1780         priv->drag_begin_cached_selected_rows = NULL;
1781 }
1782
1783 /* Header view drag types */
1784 const GtkTargetEntry header_view_drag_types[] = {
1785         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1786 };
1787
1788 static void
1789 enable_drag_and_drop (GtkWidget *self)
1790 {
1791 #ifdef MODEST_TOOLKIT_HILDON2
1792         return;
1793 #endif
1794         gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1795                              header_view_drag_types,
1796                              G_N_ELEMENTS (header_view_drag_types),
1797                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1798 }
1799
1800 static void
1801 disable_drag_and_drop (GtkWidget *self)
1802 {
1803 #ifdef MODEST_TOOLKIT_HILDON2
1804         return;
1805 #endif
1806         gtk_drag_source_unset (self);
1807 }
1808
1809 static void
1810 setup_drag_and_drop (GtkWidget *self)
1811 {
1812 #ifdef MODEST_TOOLKIT_HILDON2
1813         return;
1814 #endif
1815         enable_drag_and_drop(self);
1816         g_signal_connect(G_OBJECT (self), "drag_data_get",
1817                          G_CALLBACK(drag_data_get_cb), NULL);
1818
1819         g_signal_connect(G_OBJECT (self), "drag_begin",
1820                          G_CALLBACK(drag_begin_cb), NULL);
1821
1822         g_signal_connect(G_OBJECT (self), "drag_end",
1823                          G_CALLBACK(drag_end_cb), NULL);
1824 }
1825
1826 static GtkTreePath *
1827 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1828 {
1829         GtkTreePath *path = NULL;
1830         GtkTreeSelection *sel = NULL;
1831         GList *rows = NULL;
1832
1833         sel   = gtk_tree_view_get_selection(self);
1834         rows = gtk_tree_selection_get_selected_rows (sel, model);
1835
1836         if ((rows == NULL) || (g_list_length(rows) != 1))
1837                 goto frees;
1838
1839         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1840
1841
1842         /* Free */
1843  frees:
1844         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1845         g_list_free(rows);
1846
1847         return path;
1848 }
1849
1850 #ifndef MODEST_TOOLKIT_HILDON2
1851 /*
1852  * This function moves the tree view scroll to the current selected
1853  * row when the widget grabs the focus
1854  */
1855 static gboolean
1856 on_focus_in (GtkWidget     *self,
1857              GdkEventFocus *event,
1858              gpointer       user_data)
1859 {
1860         GtkTreeSelection *selection;
1861         GtkTreeModel *model;
1862         GList *selected = NULL;
1863         GtkTreePath *selected_path = NULL;
1864
1865         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1866         if (!model)
1867                 return FALSE;
1868
1869         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1870         /* If none selected yet, pick the first one */
1871         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1872                 GtkTreeIter iter;
1873                 GtkTreePath *path;
1874
1875                 /* Return if the model is empty */
1876                 if (!gtk_tree_model_get_iter_first (model, &iter))
1877                         return FALSE;
1878
1879                 path = gtk_tree_model_get_path (model, &iter);
1880                 gtk_tree_selection_select_path (selection, path);
1881                 gtk_tree_path_free (path);
1882         }
1883
1884         /* Need to get the all the rows because is selection multiple */
1885         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1886         if (selected == NULL) return FALSE;
1887         selected_path = (GtkTreePath *) selected->data;
1888
1889         /* Frees */
1890         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1891         g_list_free (selected);
1892
1893         return FALSE;
1894 }
1895
1896 static gboolean
1897 on_focus_out (GtkWidget     *self,
1898              GdkEventFocus *event,
1899              gpointer       user_data)
1900 {
1901
1902         if (!gtk_widget_is_focus (self)) {
1903                 GtkTreeSelection *selection = NULL;
1904                 GList *selected_rows = NULL;
1905                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1906                 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1907                         selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1908                         g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1909                         gtk_tree_selection_unselect_all (selection);
1910                         gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1911                         g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1912                         g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1913                         g_list_free (selected_rows);
1914                 }
1915         }
1916         return FALSE;
1917 }
1918 #endif
1919
1920 static gboolean
1921 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1922 {
1923         enable_drag_and_drop(self);
1924         return FALSE;
1925 }
1926
1927 static gboolean
1928 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1929 {
1930         GtkTreeSelection *selection = NULL;
1931         GtkTreePath *path = NULL;
1932         gboolean already_selected = FALSE, already_opened = FALSE;
1933         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1934
1935         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1936                 GtkTreeIter iter;
1937                 GtkTreeModel *model;
1938
1939                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1940                 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1941
1942                 /* Get header from model */
1943                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1944                 if (gtk_tree_model_get_iter (model, &iter, path)) {
1945                         GValue value = {0,};
1946                         TnyHeader *header;
1947
1948                         gtk_tree_model_get_value (model, &iter,
1949                                                   TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1950                                                   &value);
1951                         header = (TnyHeader *) g_value_get_object (&value);
1952                         if (TNY_IS_HEADER (header)) {
1953                                 status = modest_tny_all_send_queues_get_msg_status (header);
1954                                 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1955                                                                                            header, NULL);
1956                         }
1957                         g_value_unset (&value);
1958                 }
1959         }
1960
1961         /* Enable drag and drop only if the user clicks on a row that
1962            it's already selected. If not, let him select items using
1963            the pointer. If the message is in an OUTBOX and in sending
1964            status disable drag and drop as well */
1965         if (!already_selected ||
1966             status == MODEST_TNY_SEND_QUEUE_SENDING ||
1967             already_opened)
1968                 disable_drag_and_drop(self);
1969
1970         if (path != NULL)
1971                 gtk_tree_path_free(path);
1972
1973         /* If it's already opened then do not let the button-press
1974            event go on because it'll perform a message open because
1975            we're clicking on to an already selected header */
1976         return FALSE;
1977 }
1978
1979 static void
1980 folder_monitor_update (TnyFolderObserver *self,
1981                        TnyFolderChange *change)
1982 {
1983         ModestHeaderViewPrivate *priv = NULL;
1984         TnyFolderChangeChanged changed;
1985         TnyFolder *folder = NULL;
1986
1987         changed = tny_folder_change_get_changed (change);
1988
1989         /* Do not notify the observers if the folder of the header
1990            view has changed before this call to the observer
1991            happens */
1992         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1993         folder = tny_folder_change_get_folder (change);
1994         if (folder != priv->folder)
1995                 goto frees;
1996
1997         MODEST_DEBUG_BLOCK (
1998                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1999                                     g_print ("ADDED %d/%d (r/t) \n",
2000                                              tny_folder_change_get_new_unread_count (change),
2001                                              tny_folder_change_get_new_all_count (change));
2002                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
2003                                     g_print ("ALL COUNT %d\n",
2004                                              tny_folder_change_get_new_all_count (change));
2005                             if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
2006                                     g_print ("UNREAD COUNT %d\n",
2007                                              tny_folder_change_get_new_unread_count (change));
2008                             if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
2009                                     g_print ("EXPUNGED %d/%d (r/t) \n",
2010                                              tny_folder_change_get_new_unread_count (change),
2011                                              tny_folder_change_get_new_all_count (change));
2012                             if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
2013                                     g_print ("FOLDER RENAME\n");
2014                             if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2015                                     g_print ("MSG RECEIVED %d/%d (r/t) \n",
2016                                              tny_folder_change_get_new_unread_count (change),
2017                                              tny_folder_change_get_new_all_count (change));
2018                             g_print ("---------------------------------------------------\n");
2019                             );
2020
2021         /* Check folder count */
2022         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2023             (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2024
2025                 g_mutex_lock (priv->observers_lock);
2026
2027                 /* Emit signal to evaluate how headers changes affects
2028                    to the window view  */
2029                 g_signal_emit (G_OBJECT(self),
2030                                signals[MSG_COUNT_CHANGED_SIGNAL],
2031                                0, folder, change);
2032
2033                 /* Added or removed headers, so data stored on cliboard are invalid  */
2034                 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2035                         modest_email_clipboard_clear (priv->clipboard);
2036
2037                 g_mutex_unlock (priv->observers_lock);
2038         }
2039
2040         /* Free */
2041  frees:
2042         if (folder != NULL)
2043                 g_object_unref (folder);
2044 }
2045
2046 gboolean
2047 modest_header_view_is_empty (ModestHeaderView *self)
2048 {
2049         ModestHeaderViewPrivate *priv;
2050
2051         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2052
2053         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2054
2055         return priv->status == HEADER_VIEW_EMPTY;
2056 }
2057
2058 void
2059 modest_header_view_clear (ModestHeaderView *self)
2060 {
2061         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2062
2063         modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2064 }
2065
2066 void
2067 modest_header_view_copy_selection (ModestHeaderView *header_view)
2068 {
2069         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2070
2071         /* Copy selection */
2072         _clipboard_set_selected_data (header_view, FALSE);
2073 }
2074
2075 void
2076 modest_header_view_cut_selection (ModestHeaderView *header_view)
2077 {
2078         ModestHeaderViewPrivate *priv = NULL;
2079         const gchar **hidding = NULL;
2080         guint i, n_selected;
2081
2082         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2083
2084         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2085
2086         /* Copy selection */
2087         _clipboard_set_selected_data (header_view, TRUE);
2088
2089         /* Get hidding ids */
2090         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2091
2092         /* Clear hidding array created by previous cut operation */
2093         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2094
2095         /* Copy hidding array */
2096         priv->n_selected = n_selected;
2097         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2098         for (i=0; i < n_selected; i++)
2099                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2100
2101         /* Hide cut headers */
2102         modest_header_view_refilter (header_view);
2103 }
2104
2105
2106
2107
2108 static void
2109 _clipboard_set_selected_data (ModestHeaderView *header_view,
2110                               gboolean delete)
2111 {
2112         ModestHeaderViewPrivate *priv = NULL;
2113         TnyList *headers = NULL;
2114
2115         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2116         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2117
2118         /* Set selected data on clipboard   */
2119         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2120         headers = modest_header_view_get_selected_headers (header_view);
2121         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2122
2123         /* Free */
2124         g_object_unref (headers);
2125 }
2126
2127 typedef struct {
2128         ModestHeaderView *self;
2129         TnyFolder *folder;
2130 } NotifyFilterInfo;
2131
2132 static gboolean
2133 notify_filter_change (gpointer data)
2134 {
2135         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2136
2137         g_signal_emit (info->self,
2138                        signals[MSG_COUNT_CHANGED_SIGNAL],
2139                        0, info->folder, NULL);
2140
2141         return FALSE;
2142 }
2143
2144 static void
2145 notify_filter_change_destroy (gpointer data)
2146 {
2147         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2148         ModestHeaderViewPrivate *priv;
2149
2150         priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2151         priv->status_timeout = 0;
2152
2153         g_object_unref (info->self);
2154         g_object_unref (info->folder);
2155         g_slice_free (NotifyFilterInfo, info);
2156 }
2157
2158 static gboolean
2159 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2160 {
2161         /* For the moment we only need to filter outbox */
2162         return priv->is_outbox;
2163 }
2164
2165 static gboolean
2166 header_match_string (TnyHeader *header, gchar **words)
2167 {
2168         gchar *subject_fold;
2169         gchar *cc_fold;
2170         gchar *bcc_fold;
2171         gchar *to_fold;
2172         gchar *from_fold;
2173
2174         gchar **current_word;
2175         gboolean found;
2176
2177         subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2178         if (subject_fold == NULL) {
2179                 gchar *subject;
2180                 subject = tny_header_dup_subject (header);
2181                 if (subject != NULL) {
2182                         subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2183                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2184                                                 subject_fold, (GDestroyNotify) g_free);
2185                 }
2186                 g_free (subject);
2187         }
2188
2189         from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2190         if (from_fold == NULL) {
2191                 gchar *from;
2192                 from = tny_header_dup_from (header);
2193                 if (from != NULL) {
2194                         from_fold = from?g_utf8_casefold (from, -1):NULL;
2195                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2196                                                 from_fold, (GDestroyNotify) g_free);
2197                 }
2198                 g_free (from);
2199         }
2200
2201         to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2202         if (to_fold == NULL) {
2203                 gchar *to;
2204                 to = tny_header_dup_to (header);
2205                 if (to != NULL) {
2206                         to_fold = to?g_utf8_casefold (to, -1):NULL;
2207                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2208                                                 to_fold, (GDestroyNotify) g_free);
2209                 }
2210                 g_free (to);
2211         }
2212
2213         cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2214         if (cc_fold == NULL) {
2215                 gchar *cc;
2216                 cc = tny_header_dup_cc (header);
2217                 if (cc != NULL) {
2218                         cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2219                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2220                                                 cc_fold, (GDestroyNotify) g_free);
2221                 }
2222                 g_free (cc);
2223         }
2224
2225         bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2226         if (bcc_fold == NULL) {
2227                 gchar *bcc;
2228                 bcc = tny_header_dup_bcc (header);
2229                 if (bcc != NULL) {
2230                         bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2231                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2232                                                 bcc_fold, (GDestroyNotify) g_free);
2233                 }
2234                 g_free (bcc);
2235         }
2236
2237         found = TRUE;
2238
2239         for (current_word = words; *current_word != NULL; current_word++) {
2240
2241                 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2242                     || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2243                     || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2244                     || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2245                     || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2246                         found = TRUE;
2247                 } else {
2248                         found = FALSE;
2249                         break;
2250                 }
2251         }
2252
2253         return found;
2254 }
2255
2256 static gboolean
2257 filter_row (GtkTreeModel *model,
2258             GtkTreeIter *iter,
2259             gpointer user_data)
2260 {
2261         ModestHeaderViewPrivate *priv = NULL;
2262         TnyHeaderFlags flags;
2263         TnyHeader *header = NULL;
2264         guint i;
2265         gchar *id = NULL;
2266         gboolean visible = TRUE;
2267         gboolean found = FALSE;
2268         GValue value = {0,};
2269         HeaderViewStatus old_status;
2270
2271         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2272         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2273
2274         /* Get header from model */
2275         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2276         flags = (TnyHeaderFlags) g_value_get_int (&value);
2277         g_value_unset (&value);
2278         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2279         header = (TnyHeader *) g_value_get_object (&value);
2280         g_value_unset (&value);
2281
2282         /* Get message id from header (ensure is a valid id) */
2283         if (!header) {
2284                 visible = FALSE;
2285                 goto frees;
2286         }
2287
2288         /* Hide deleted and mark as deleted heders */
2289         if (flags & TNY_HEADER_FLAG_DELETED ||
2290             flags & TNY_HEADER_FLAG_EXPUNGED) {
2291                 visible = FALSE;
2292                 goto frees;
2293         }
2294
2295         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2296                 if (current_folder_needs_filtering (priv) &&
2297                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2298                         visible = FALSE;
2299                         goto frees;
2300                 }
2301         }
2302
2303         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2304                 if (current_folder_needs_filtering (priv) &&
2305                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2306                         visible = FALSE;
2307                         goto frees;
2308                 }
2309         }
2310
2311         if (visible && priv->filter_string) {
2312                 if (!header_match_string (header, priv->filter_string_splitted)) {
2313                         visible = FALSE;
2314                         goto frees;
2315                 }
2316                 if (priv->filter_date_range) {
2317                         if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2318                             ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2319                                 visible = FALSE;
2320                                 goto frees;
2321                         }
2322                 }
2323         }
2324
2325         /* If no data on clipboard, return always TRUE */
2326         if (modest_email_clipboard_cleared(priv->clipboard)) {
2327                 visible = TRUE;
2328                 goto frees;
2329         }
2330
2331         /* Check hiding */
2332         if (priv->hidding_ids != NULL) {
2333                 id = tny_header_dup_message_id (header);
2334                 for (i=0; i < priv->n_selected && !found; i++)
2335                         if (priv->hidding_ids[i] != NULL && id != NULL)
2336                                 found = (!strcmp (priv->hidding_ids[i], id));
2337
2338                 visible = !found;
2339                 g_free(id);
2340         }
2341
2342  frees:
2343         old_status = priv->status;
2344         priv->status = ((gboolean) priv->status) && !visible;
2345         if ((priv->notify_status) && (priv->status != old_status)) {
2346                 if (priv->status_timeout)
2347                         g_source_remove (priv->status_timeout);
2348
2349                 if (header) {
2350                         NotifyFilterInfo *info;
2351
2352                         info = g_slice_new0 (NotifyFilterInfo);
2353                         info->self = g_object_ref (G_OBJECT (user_data));
2354                         if (header)
2355                                 info->folder = tny_header_get_folder (header);
2356                         priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2357                                                                    notify_filter_change,
2358                                                                    info,
2359                                                                    notify_filter_change_destroy);
2360                 }
2361         }
2362         
2363         return visible;
2364 }
2365
2366 static void
2367 _clear_hidding_filter (ModestHeaderView *header_view)
2368 {
2369         ModestHeaderViewPrivate *priv = NULL;
2370         guint i;
2371
2372         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2373         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2374
2375         if (priv->hidding_ids != NULL) {
2376                 for (i=0; i < priv->n_selected; i++)
2377                         g_free (priv->hidding_ids[i]);
2378                 g_free(priv->hidding_ids);
2379         }
2380 }
2381
2382 void
2383 modest_header_view_refilter (ModestHeaderView *header_view)
2384 {
2385         GtkTreeModel *filter_model = NULL;
2386         ModestHeaderViewPrivate *priv = NULL;
2387
2388         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2389         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2390
2391         /* Hide cut headers */
2392         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2393         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2394                 priv->status = HEADER_VIEW_INIT;
2395                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2396         }
2397 }
2398
2399 /*
2400  * Called when an account is removed. If I'm showing a folder of the
2401  * account that has been removed then clear the view
2402  */
2403 static void
2404 on_account_removed (TnyAccountStore *self,
2405                     TnyAccount *account,
2406                     gpointer user_data)
2407 {
2408         ModestHeaderViewPrivate *priv = NULL;
2409
2410         /* Ignore changes in transport accounts */
2411         if (TNY_IS_TRANSPORT_ACCOUNT (account))
2412                 return;
2413
2414         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2415
2416         if (priv->folder) {
2417                 TnyAccount *my_account;
2418
2419                 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2420                     tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2421                         ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2422                         my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2423                 } else {
2424                         my_account = tny_folder_get_account (priv->folder);
2425                 }
2426
2427                 if (my_account) {
2428                         if (my_account == account)
2429                                 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2430                         g_object_unref (my_account);
2431                 }
2432         }
2433 }
2434
2435 void
2436 modest_header_view_add_observer(ModestHeaderView *header_view,
2437                                      ModestHeaderViewObserver *observer)
2438 {
2439         ModestHeaderViewPrivate *priv;
2440
2441         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2442         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2443
2444         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2445
2446         g_mutex_lock(priv->observer_list_lock);
2447         priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2448         g_mutex_unlock(priv->observer_list_lock);
2449 }
2450
2451 void
2452 modest_header_view_remove_observer(ModestHeaderView *header_view,
2453                                    ModestHeaderViewObserver *observer)
2454 {
2455         ModestHeaderViewPrivate *priv;
2456
2457         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2458         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2459
2460         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2461
2462         g_mutex_lock(priv->observer_list_lock);
2463         priv->observer_list = g_slist_remove(priv->observer_list, observer);
2464         g_mutex_unlock(priv->observer_list_lock);
2465 }
2466
2467 static void
2468 modest_header_view_notify_observers(ModestHeaderView *header_view,
2469                                     GtkTreeModel *model,
2470                                     const gchar *tny_folder_id)
2471 {
2472         ModestHeaderViewPrivate *priv = NULL;
2473         GSList *iter;
2474         ModestHeaderViewObserver *observer;
2475
2476
2477         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2478
2479         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2480
2481         g_mutex_lock(priv->observer_list_lock);
2482         iter = priv->observer_list;
2483         while(iter != NULL){
2484                 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2485                 modest_header_view_observer_update(observer, model,
2486                                 tny_folder_id);
2487                 iter = g_slist_next(iter);
2488         }
2489         g_mutex_unlock(priv->observer_list_lock);
2490 }
2491
2492 const gchar *
2493 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2494 {
2495         ModestHeaderViewPrivate *priv = NULL;
2496
2497         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2498         return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2499 }
2500
2501 void
2502 modest_header_view_set_filter (ModestHeaderView *self,
2503                                ModestHeaderViewFilter filter)
2504 {
2505         ModestHeaderViewPrivate *priv;
2506
2507         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2508         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2509
2510         priv->filter |= filter;
2511
2512         if (current_folder_needs_filtering (priv))
2513                 modest_header_view_refilter (self);
2514 }
2515
2516 void
2517 modest_header_view_unset_filter (ModestHeaderView *self,
2518                                  ModestHeaderViewFilter filter)
2519 {
2520         ModestHeaderViewPrivate *priv;
2521
2522         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2523         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2524
2525         priv->filter &= ~filter;
2526
2527         if (current_folder_needs_filtering (priv))
2528                 modest_header_view_refilter (self);
2529 }
2530
2531 static void
2532 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2533 {
2534         if (strcmp ("style", spec->name) == 0) {
2535                 update_style (MODEST_HEADER_VIEW (obj));
2536                 gtk_widget_queue_draw (GTK_WIDGET (obj));
2537         }
2538 }
2539
2540 static void
2541 update_style (ModestHeaderView *self)
2542 {
2543         ModestHeaderViewPrivate *priv;
2544         GdkColor style_color;
2545         GdkColor style_active_color;
2546         PangoAttrList *attr_list;
2547         GtkStyle *style;
2548         PangoAttribute *attr;
2549
2550         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2551         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2552
2553         /* Set color */
2554
2555         attr_list = pango_attr_list_new ();
2556         if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
2557                 gdk_color_parse ("grey", &style_color);
2558         }
2559         priv->secondary_color = style_color;
2560         attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2561         pango_attr_list_insert (attr_list, attr);
2562
2563         /* set font */
2564         style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2565                                            (GTK_WIDGET(self)),
2566                                            "SmallSystemFont", NULL,
2567                                            G_TYPE_NONE);
2568         if (style) {
2569                 attr = pango_attr_font_desc_new (pango_font_description_copy
2570                                                  (style->font_desc));
2571                 pango_attr_list_insert (attr_list, attr);
2572
2573                 g_object_set (G_OBJECT (priv->renderer_address),
2574                               "foreground-gdk", &(priv->secondary_color),
2575                               "foreground-set", TRUE,
2576                               "attributes", attr_list,
2577                               NULL);
2578                 g_object_set (G_OBJECT (priv->renderer_date_status),
2579                               "foreground-gdk", &(priv->secondary_color),
2580                               "foreground-set", TRUE,
2581                               "attributes", attr_list,
2582                               NULL);
2583                 pango_attr_list_unref (attr_list);
2584         } else {
2585                 g_object_set (G_OBJECT (priv->renderer_address),
2586                               "foreground-gdk", &(priv->secondary_color),
2587                               "foreground-set", TRUE,
2588                               "scale", PANGO_SCALE_SMALL,
2589                               "scale-set", TRUE,
2590                               NULL);
2591                 g_object_set (G_OBJECT (priv->renderer_date_status),
2592                               "foreground-gdk", &(priv->secondary_color),
2593                               "foreground-set", TRUE,
2594                               "scale", PANGO_SCALE_SMALL,
2595                               "scale-set", TRUE,
2596                               NULL);
2597         }
2598
2599         if (gtk_style_lookup_color (GTK_WIDGET (self)->style, "ActiveTextColor", &style_active_color)) {
2600                 priv->active_color = style_active_color;
2601 #ifdef MODEST_TOOLKIT_HILDON2
2602                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2603                 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2604 #endif
2605         } else {
2606 #ifdef MODEST_TOOLKIT_HILDON2
2607                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2608 #endif
2609         }
2610 }
2611
2612 TnyHeader *
2613 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2614                                       gint initial_x,
2615                                       gint initial_y)
2616 {
2617         GtkTreePath *path;
2618         GtkTreeModel *tree_model;
2619         GtkTreeIter iter;
2620         TnyHeader *header;
2621
2622         /* Get tree path */
2623         if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2624                                                 initial_x,
2625                                                 initial_y,
2626                                                 &path,
2627                                                 NULL))
2628                 return NULL;
2629
2630         /* Get model */
2631         tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2632         if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2633                 return NULL;
2634
2635         /* Get header */
2636         gtk_tree_model_get (tree_model, &iter,
2637                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2638                             &header, -1);
2639
2640         return header;
2641 }
2642
2643 static gboolean
2644 parse_date_side (const gchar *string, time_t *date_side)
2645 {
2646         gchar *today;
2647         gchar *yesterday;
2648         gchar *casefold;
2649         GDate *date;
2650         gboolean result = FALSE;
2651
2652         if (string && string[0] == '\0') {
2653                 *date_side = 0;
2654                 return TRUE;
2655         }
2656
2657         casefold = g_utf8_casefold (string, -1);
2658         today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2659         yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2660         date = g_date_new ();
2661
2662         if (g_utf8_collate (casefold, today) == 0) {
2663                 *date_side = time (NULL);
2664                 result = TRUE;
2665                 goto frees;
2666         }
2667
2668         if (g_utf8_collate (casefold, yesterday) == 0) {
2669                 *date_side = time (NULL) - 24*60*60;
2670                 result = TRUE;
2671                 goto frees;
2672         }
2673
2674         g_date_set_parse (date, string);
2675         if (g_date_valid (date)) {
2676                 struct tm tm = {0};
2677                 g_date_to_struct_tm (date, &tm);
2678                 *date_side = mktime (&tm);
2679                 
2680                 result = TRUE;
2681                 goto frees;
2682         }
2683 frees:
2684         g_free (today);
2685         g_free (yesterday);
2686         g_free (casefold);
2687         g_date_free (date);
2688
2689         return result;
2690 }
2691
2692 static gboolean
2693 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2694 {
2695         gchar ** parts;
2696         gboolean valid;
2697
2698         parts = g_strsplit (string, "..", 2);
2699         valid = TRUE;
2700
2701         if (g_strv_length (parts) != 2) {
2702                 valid = FALSE;
2703                 goto frees;
2704                 g_strfreev (parts);
2705                 return FALSE;
2706         }
2707
2708         if (!parse_date_side (parts[0], date_range_start)) {
2709                 valid = FALSE;
2710                 goto frees;
2711         }
2712
2713         if (parse_date_side (parts[1], date_range_end)) {
2714                 if (*date_range_end == 0) {
2715                         *date_range_end = (time_t) -1;
2716                 } else {
2717                         *date_range_end += (24*60*60 - 1);
2718                 }
2719         } else {
2720                 valid = FALSE;
2721                 goto frees;
2722         }
2723                 
2724 frees:
2725         g_strfreev (parts);
2726         return valid;
2727 }
2728
2729 void
2730 modest_header_view_set_show_latest (ModestHeaderView *header_view,
2731                                     gint show_latest)
2732 {
2733         ModestHeaderViewPrivate *priv;
2734         GtkTreeModel *sortable, *filter, *model;
2735
2736         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2737         priv->show_latest = show_latest;
2738
2739         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2740         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2741                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2742                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2743                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2744                         if (model) {
2745                                 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (model), priv->show_latest);
2746                         }
2747                 }
2748         }
2749 }
2750
2751 gint
2752 modest_header_view_get_show_latest (ModestHeaderView *header_view)
2753 {
2754         ModestHeaderViewPrivate *priv;
2755         GtkTreeModel *sortable, *filter, *model;
2756         gint result;
2757
2758         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2759
2760         result = priv->show_latest;
2761         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2762         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2763                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2764                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2765                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2766                         if (model) {
2767                                 result = tny_gtk_header_list_model_get_show_latest (TNY_GTK_HEADER_LIST_MODEL (model));
2768                         }
2769                 }
2770         }
2771
2772         return result;
2773 }
2774
2775 gint
2776 modest_header_view_get_not_latest (ModestHeaderView *header_view)
2777 {
2778         ModestHeaderViewPrivate *priv;
2779         gint not_latest = 0;
2780         GtkTreeModel *sortable, *filter, *model;
2781
2782         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2783
2784         if (priv->show_latest == 0)
2785                 return 0;
2786
2787         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2788         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2789                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2790                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2791                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2792                         if (model) {
2793                                 not_latest = MAX (0, tny_list_get_length (TNY_LIST (model)) - priv->show_latest);
2794                         }
2795                 }
2796         }
2797
2798         return not_latest;
2799 }
2800
2801 void
2802 modest_header_view_set_filter_string (ModestHeaderView *self,
2803                                       const gchar *filter_string)
2804 {
2805         ModestHeaderViewPrivate *priv;
2806
2807         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2808         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2809
2810         if (priv->filter_string)
2811                 g_free (priv->filter_string);
2812
2813         priv->filter_string = g_strdup (filter_string);
2814         priv->filter_date_range = FALSE;
2815
2816         if (priv->filter_string_splitted) {
2817                 g_strfreev (priv->filter_string_splitted);
2818                 priv->filter_string_splitted = NULL;
2819         }
2820
2821         if (priv->filter_string) {
2822                 gchar **split, **current, **current_target;
2823
2824                 split = g_strsplit (priv->filter_string, " ", 0);
2825
2826                 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2827                 current_target = priv->filter_string_splitted;
2828                 for (current = split; *current != 0; current ++) {
2829                         gboolean has_date_range = FALSE;;
2830                         if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2831                                 time_t range_start, range_end;
2832                                 /* It contains .. but it's not ".." so it may be a date range */
2833                                 if (parse_date_range (*current, &range_start, &range_end)) {
2834                                         priv->filter_date_range = TRUE;
2835                                         has_date_range = TRUE;
2836                                         priv->date_range_start = range_start;
2837                                         priv->date_range_end = range_end;
2838                                 }
2839                         }
2840                         if (!has_date_range) {
2841                                 *current_target = g_utf8_casefold (*current, -1);
2842                                 current_target++;
2843                         }
2844                 }
2845                 *current_target = '\0';
2846                 g_strfreev (split);
2847         }
2848         modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2849 }
2850
2851 #ifdef MODEST_TOOLKIT_HILDON2
2852
2853 static gboolean
2854 on_live_search_timeout (ModestHeaderView *self)
2855 {
2856         const gchar *needle;
2857         ModestHeaderViewPrivate *priv;
2858
2859         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2860
2861         needle = hildon_live_search_get_text (HILDON_LIVE_SEARCH (priv->live_search));
2862         if (needle && needle[0] != '\0') {
2863                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2864                 if (priv->show_latest > 0)
2865                         modest_header_view_set_show_latest (MODEST_HEADER_VIEW (self), 0);
2866         } else {
2867                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2868         }
2869
2870         priv->live_search_timeout = 0;
2871
2872         return FALSE;
2873 }
2874
2875 static gboolean
2876 on_live_search_refilter (HildonLiveSearch *livesearch,
2877                          ModestHeaderView *self)
2878 {
2879         ModestHeaderViewPrivate *priv;
2880         GtkTreeModel *model, *sortable, *filter;
2881
2882         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2883
2884         if (priv->live_search_timeout > 0) {
2885                 g_source_remove (priv->live_search_timeout);
2886                 priv->live_search_timeout = 0;
2887         }
2888
2889         model = NULL;
2890         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2891         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2892                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2893                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2894                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2895                 }
2896         }
2897
2898         if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2899                 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2900         } else {
2901                 on_live_search_timeout (self);
2902         }
2903
2904         return TRUE;
2905 }
2906
2907 GtkWidget *
2908 modest_header_view_setup_live_search (ModestHeaderView *self)
2909 {
2910         ModestHeaderViewPrivate *priv;
2911
2912         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2913         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2914         priv->live_search = hildon_live_search_new ();
2915
2916         g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2917
2918         return priv->live_search;
2919 }
2920 #endif