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