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