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