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