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