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