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