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