Cache casefold of strings to speed up live search in headers list.
[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
1173         /* Start the monitor in the callback of the
1174            tny_gtk_header_list_model_set_folder call. It's crucial to
1175            do it there and not just after the call because we want the
1176            monitor to observe only the headers returned by the
1177            tny_folder_get_headers_async call that it's inside the
1178            tny_gtk_header_list_model_set_folder call. This way the
1179            monitor infrastructure could successfully cope with
1180            duplicates. For example if a tny_folder_add_msg_async is
1181            happening while tny_gtk_header_list_model_set_folder is
1182            invoked, then the first call could add a header that will
1183            be added again by tny_gtk_header_list_model_set_folder, so
1184            we'd end up with duplicate headers. sergio */
1185         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1186                                               folder, refresh,
1187                                               set_folder_intern_get_headers_async_cb,
1188                                               NULL, self);
1189
1190         /* Init filter_row function to examine empty status */
1191         priv->status  = HEADER_VIEW_INIT;
1192
1193         /* Create sortable model */
1194         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1195         g_object_unref (headers);
1196
1197         /* Create a tree model filter to hide and show rows for cut operations  */
1198         filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1199         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1200                                                 filter_row, self, NULL);
1201         g_object_unref (sortable);
1202
1203         /* install our special sorting functions */
1204         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1205
1206         /* Restore sort column id */
1207         if (cols) {
1208                 type  = modest_tny_folder_guess_folder_type (folder);
1209                 if (type == TNY_FOLDER_TYPE_INVALID)
1210                         g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1211
1212                 sort_colid = modest_header_view_get_sort_column_id (self, type);
1213                 sort_type = modest_header_view_get_sort_type (self, type);
1214                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1215                                                       sort_colid,
1216                                                       sort_type);
1217                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1218                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1219                                                  (GtkTreeIterCompareFunc) cmp_rows,
1220                                                  cols->data, NULL);
1221                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1222                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1223                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
1224                                                  cols->data, NULL);
1225         }
1226
1227         /* Set new model */
1228         gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1229         modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1230         g_object_unref (filter_model);
1231
1232         /* Free */
1233         g_list_free (cols);
1234 }
1235
1236 void
1237 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1238                                       guint sort_colid,
1239                                       GtkSortType sort_type)
1240 {
1241         ModestHeaderViewPrivate *priv = NULL;
1242         GtkTreeModel *sortable = NULL, *filter_model = NULL;
1243         TnyFolderType type;
1244
1245         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1246         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1247
1248         /* Get model and private data */
1249         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1250         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1251         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1252                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1253         }
1254
1255         /* Sort tree model */
1256         type  = modest_tny_folder_guess_folder_type (priv->folder);
1257         if (type == TNY_FOLDER_TYPE_INVALID)
1258                 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1259         else {
1260                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1261                                                       sort_colid,
1262                                                       sort_type);
1263                 /* Store new sort parameters */
1264                 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1265         }
1266 }
1267
1268 void
1269 modest_header_view_set_sort_params (ModestHeaderView *self,
1270                                     guint sort_colid,
1271                                     GtkSortType sort_type,
1272                                     TnyFolderType type)
1273 {
1274         ModestHeaderViewPrivate *priv;
1275         ModestHeaderViewStyle style;
1276
1277         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1278         g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1279         g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1280
1281         style = modest_header_view_get_style   (self);
1282         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1283
1284         priv->sort_colid[style][type] = sort_colid;
1285         priv->sort_type[style][type] = sort_type;
1286 }
1287
1288 gint
1289 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1290                                        TnyFolderType type)
1291 {
1292         ModestHeaderViewPrivate *priv;
1293         ModestHeaderViewStyle style;
1294
1295         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1296         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1297
1298         style = modest_header_view_get_style   (self);
1299         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1300
1301         return priv->sort_colid[style][type];
1302 }
1303
1304 GtkSortType
1305 modest_header_view_get_sort_type (ModestHeaderView *self,
1306                                   TnyFolderType type)
1307 {
1308         ModestHeaderViewPrivate *priv;
1309         ModestHeaderViewStyle style;
1310
1311         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1312         g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1313
1314         style = modest_header_view_get_style   (self);
1315         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1316
1317         return priv->sort_type[style][type];
1318 }
1319
1320 typedef struct {
1321         ModestHeaderView *header_view;
1322         RefreshAsyncUserCallback cb;
1323         gpointer user_data;
1324 } SetFolderHelper;
1325
1326 static void
1327 folder_refreshed_cb (ModestMailOperation *mail_op,
1328                      TnyFolder *folder,
1329                      gpointer user_data)
1330 {
1331         ModestHeaderViewPrivate *priv;
1332         SetFolderHelper *info;
1333
1334         info = (SetFolderHelper*) user_data;
1335
1336         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1337
1338         /* User callback */
1339         if (info->cb)
1340                 info->cb (mail_op, folder, info->user_data);
1341
1342         /* Start the folder count changes observer. We do not need it
1343            before the refresh. Note that the monitor could still be
1344            called for this refresh but now we know that the callback
1345            was previously called */
1346         g_mutex_lock (priv->observers_lock);
1347         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1348         g_mutex_unlock (priv->observers_lock);
1349
1350         /* Notify the observers that the update is over */
1351         g_signal_emit (G_OBJECT (info->header_view),
1352                        signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1353
1354         /* Allow filtering notifications from now on if the current
1355            folder is still the same (if not then the user has selected
1356            another one to refresh, we should wait until that refresh
1357            finishes) */
1358         if (priv->folder == folder)
1359                 priv->notify_status = TRUE;
1360
1361         /* Frees */
1362         g_object_unref (info->header_view);
1363         g_free (info);
1364 }
1365
1366 static void
1367 refresh_folder_error_handler (ModestMailOperation *mail_op,
1368                               gpointer user_data)
1369 {
1370         const GError *error = modest_mail_operation_get_error (mail_op);
1371
1372         if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1373             error->code == TNY_IO_ERROR_WRITE ||
1374             error->code == TNY_IO_ERROR_READ) {
1375                 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1376                 /* If the mail op has been cancelled then it's not an error: don't show any message */
1377                 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1378                         gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1379                         modest_platform_information_banner (NULL, NULL, msg);
1380                         g_free (msg);
1381                 }
1382         }
1383 }
1384
1385 void
1386 modest_header_view_set_folder (ModestHeaderView *self,
1387                                TnyFolder *folder,
1388                                gboolean refresh,
1389                                ModestWindow *progress_window,
1390                                RefreshAsyncUserCallback callback,
1391                                gpointer user_data)
1392 {
1393         ModestHeaderViewPrivate *priv;
1394
1395         g_return_if_fail (self);
1396
1397         priv =     MODEST_HEADER_VIEW_GET_PRIVATE(self);
1398
1399         if (priv->folder) {
1400                 if (priv->status_timeout) {
1401                         g_source_remove (priv->status_timeout);
1402                         priv->status_timeout = 0;
1403                 }
1404
1405                 g_mutex_lock (priv->observers_lock);
1406                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1407                 g_object_unref (priv->folder);
1408                 priv->folder = NULL;
1409                 g_mutex_unlock (priv->observers_lock);
1410         }
1411
1412         if (folder) {
1413                 GtkTreeSelection *selection;
1414                 SetFolderHelper *info;
1415                 ModestMailOperation *mail_op = NULL;
1416
1417                 /* Set folder in the model */
1418                 modest_header_view_set_folder_intern (self, folder, refresh);
1419
1420                 /* Pick my reference. Nothing to do with the mail operation */
1421                 priv->folder = g_object_ref (folder);
1422
1423                 /* Do not notify about filterings until the refresh finishes */
1424                 priv->notify_status = FALSE;
1425
1426                 /* Clear the selection if exists */
1427                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1428                 gtk_tree_selection_unselect_all(selection);
1429                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1430
1431                 /* Notify the observers that the update begins */
1432                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1433                                0, TRUE, NULL);
1434
1435                 /* create the helper */
1436                 info = g_malloc0 (sizeof (SetFolderHelper));
1437                 info->header_view = g_object_ref (self);
1438                 info->cb = callback;
1439                 info->user_data = user_data;
1440
1441                 /* Create the mail operation (source will be the parent widget) */
1442                 if (progress_window)
1443                         mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1444                                                                                  refresh_folder_error_handler,
1445                                                                                  NULL, NULL);
1446                 if (refresh) {
1447                         modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1448                                                          mail_op);
1449
1450                         /* Refresh the folder asynchronously */
1451                         modest_mail_operation_refresh_folder (mail_op,
1452                                                               folder,
1453                                                               folder_refreshed_cb,
1454                                                               info);
1455                 } else {
1456                         folder_refreshed_cb (mail_op, folder, info);
1457                 }
1458                 /* Free */
1459                 if (mail_op)
1460                         g_object_unref (mail_op);
1461         } else {
1462                 g_mutex_lock (priv->observers_lock);
1463
1464                 if (priv->monitor) {
1465                         tny_folder_monitor_stop (priv->monitor);
1466                         g_object_unref (G_OBJECT (priv->monitor));
1467                         priv->monitor = NULL;
1468                 }
1469
1470                 if (priv->autoselect_reference) {
1471                         gtk_tree_row_reference_free (priv->autoselect_reference);
1472                         priv->autoselect_reference = NULL;
1473                 }
1474
1475                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1476
1477                 modest_header_view_notify_observers(self, NULL, NULL);
1478
1479                 g_mutex_unlock (priv->observers_lock);
1480
1481                 /* Notify the observers that the update is over */
1482                 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1483                                0, FALSE, NULL);
1484         }
1485 }
1486
1487 static void
1488 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1489                          GtkTreeViewColumn *column, gpointer userdata)
1490 {
1491         ModestHeaderView *self = NULL;
1492         GtkTreeIter iter;
1493         GtkTreeModel *model = NULL;
1494         TnyHeader *header = NULL;
1495         TnyHeaderFlags flags;
1496
1497         self = MODEST_HEADER_VIEW (treeview);
1498
1499         model = gtk_tree_view_get_model (treeview);
1500         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1501                 goto frees;
1502
1503         /* get the first selected item */
1504         gtk_tree_model_get (model, &iter,
1505                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1506                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1507                             -1);
1508
1509         /* Dont open DELETED messages */
1510         if (flags & TNY_HEADER_FLAG_DELETED) {
1511                 GtkWidget *win;
1512                 gchar *msg;
1513                 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1514                 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1515                 modest_platform_information_banner (NULL, NULL, msg);
1516                 g_free (msg);
1517                 goto frees;
1518         }
1519
1520         /* Emit signal */
1521         g_signal_emit (G_OBJECT(self),
1522                        signals[HEADER_ACTIVATED_SIGNAL],
1523                        0, header, path);
1524
1525         /* Free */
1526  frees:
1527         if (header != NULL)
1528                 g_object_unref (G_OBJECT (header));
1529
1530 }
1531
1532 static void
1533 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1534 {
1535         GtkTreeModel *model;
1536         TnyHeader *header = NULL;
1537         GtkTreePath *path = NULL;
1538         GtkTreeIter iter;
1539         ModestHeaderView *self;
1540         GList *selected = NULL;
1541
1542         g_return_if_fail (sel);
1543         g_return_if_fail (user_data);
1544
1545         self = MODEST_HEADER_VIEW (user_data);
1546
1547         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1548         if (selected != NULL)
1549                 path = (GtkTreePath *) selected->data;
1550         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1551                 return; /* msg was _un_selected */
1552
1553         gtk_tree_model_get (model, &iter,
1554                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1555                             &header, -1);
1556
1557         /* Emit signal */
1558         g_signal_emit (G_OBJECT(self),
1559                        signals[HEADER_SELECTED_SIGNAL],
1560                        0, header);
1561
1562         g_object_unref (G_OBJECT (header));
1563
1564         /* free all items in 'selected' */
1565         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1566         g_list_free (selected);
1567 }
1568
1569
1570 /* PROTECTED method. It's useful when we want to force a given
1571    selection to reload a msg. For example if we have selected a header
1572    in offline mode, when Modest become online, we want to reload the
1573    message automatically without an user click over the header */
1574 void
1575 _modest_header_view_change_selection (GtkTreeSelection *selection,
1576                                       gpointer user_data)
1577 {
1578         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1579         g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1580
1581         on_selection_changed (selection, user_data);
1582 }
1583
1584 static gint
1585 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1586 {
1587         /* HH, LL, NN */
1588         if (p1 == p2)
1589                 return 0;
1590
1591         /* HL HN */
1592         if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1593                 return 1;
1594
1595         /* LH LN */
1596         if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1597                 return -1;
1598
1599         /* NH */
1600         if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1601                 return -1;
1602
1603         /* NL */
1604         return 1;
1605 }
1606
1607 static gint
1608 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1609           gpointer user_data)
1610 {
1611         gint col_id;
1612         gint t1, t2;
1613         gint val1, val2;
1614         gint cmp;
1615
1616         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1617         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1618
1619
1620         switch (col_id) {
1621         case TNY_HEADER_FLAG_ATTACHMENTS:
1622
1623                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1624                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1625                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1626                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1627
1628                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1629                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1630
1631                 return cmp ? cmp : t1 - t2;
1632
1633         case TNY_HEADER_FLAG_PRIORITY_MASK: {
1634                 TnyHeader *header1 = NULL, *header2 = NULL;
1635
1636                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1637                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1638                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1639                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1640
1641                 /* This is for making priority values respect the intuitive sort relationship
1642                  * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1643
1644                 if (header1 && header2) {
1645                         cmp =  compare_priorities (tny_header_get_priority (header1),
1646                                 tny_header_get_priority (header2));
1647                         g_object_unref (header1);
1648                         g_object_unref (header2);
1649
1650                         return cmp ? cmp : t1 - t2;
1651                 }
1652
1653                 return t1 - t2;
1654         }
1655         default:
1656                 return &iter1 - &iter2; /* oughhhh  */
1657         }
1658 }
1659
1660 static gint
1661 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1662                   gpointer user_data)
1663 {
1664         gint t1, t2;
1665         gchar *val1, *val2;
1666         gint cmp;
1667
1668         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1669
1670         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1671                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1672         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1673                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1674
1675         /* Do not use the prefixes for sorting. Consume all the blank
1676            spaces for sorting */
1677         cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1678                                              g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1679                                              TRUE);
1680
1681         /* If they're equal based on subject without prefix then just
1682            sort them by length. This will show messages like this.
1683            * Fw:
1684            * Fw:Fw:
1685            * Fw:Fw:
1686            * Fw:Fw:Fw:
1687            * */
1688         if (cmp == 0)
1689                 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1690
1691         g_free (val1);
1692         g_free (val2);
1693         return cmp;
1694 }
1695
1696 /* Drag and drop stuff */
1697 static void
1698 drag_data_get_cb (GtkWidget *widget,
1699                   GdkDragContext *context,
1700                   GtkSelectionData *selection_data,
1701                   guint info,
1702                   guint time,
1703                   gpointer data)
1704 {
1705         ModestHeaderView *self = NULL;
1706         ModestHeaderViewPrivate *priv = NULL;
1707
1708         self = MODEST_HEADER_VIEW (widget);
1709         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1710
1711         /* Set the data. Do not use the current selection because it
1712            could be different than the selection at the beginning of
1713            the d&d */
1714         modest_dnd_selection_data_set_paths (selection_data,
1715                                              priv->drag_begin_cached_selected_rows);
1716 }
1717
1718 /**
1719  * We're caching the selected rows at the beginning because the
1720  * selection could change between drag-begin and drag-data-get, for
1721  * example if we have a set of rows already selected, and then we
1722  * click in one of them (without SHIFT key pressed) and begin a drag,
1723  * the selection at that moment contains all the selected lines, but
1724  * after dropping the selection, the release event provokes that only
1725  * the row used to begin the drag is selected, so at the end the
1726  * drag&drop affects only one rows instead of all the selected ones.
1727  *
1728  */
1729 static void
1730 drag_begin_cb (GtkWidget *widget,
1731                GdkDragContext *context,
1732                gpointer data)
1733 {
1734         ModestHeaderView *self = NULL;
1735         ModestHeaderViewPrivate *priv = NULL;
1736         GtkTreeSelection *selection;
1737
1738         self = MODEST_HEADER_VIEW (widget);
1739         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1740
1741         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1742         priv->drag_begin_cached_selected_rows =
1743                 gtk_tree_selection_get_selected_rows (selection, NULL);
1744 }
1745
1746 /**
1747  * We use the drag-end signal to clear the cached selection, we use
1748  * this because this allways happens, whether or not the d&d was a
1749  * success
1750  */
1751 static void
1752 drag_end_cb (GtkWidget *widget,
1753              GdkDragContext *dc,
1754              gpointer data)
1755 {
1756         ModestHeaderView *self = NULL;
1757         ModestHeaderViewPrivate *priv = NULL;
1758
1759         self = MODEST_HEADER_VIEW (widget);
1760         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1761
1762         /* Free cached data */
1763         g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1764         g_list_free (priv->drag_begin_cached_selected_rows);
1765         priv->drag_begin_cached_selected_rows = NULL;
1766 }
1767
1768 /* Header view drag types */
1769 const GtkTargetEntry header_view_drag_types[] = {
1770         { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1771 };
1772
1773 static void
1774 enable_drag_and_drop (GtkWidget *self)
1775 {
1776 #ifdef MODEST_TOOLKIT_HILDON2
1777         return;
1778 #endif
1779         gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1780                              header_view_drag_types,
1781                              G_N_ELEMENTS (header_view_drag_types),
1782                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1783 }
1784
1785 static void
1786 disable_drag_and_drop (GtkWidget *self)
1787 {
1788 #ifdef MODEST_TOOLKIT_HILDON2
1789         return;
1790 #endif
1791         gtk_drag_source_unset (self);
1792 }
1793
1794 static void
1795 setup_drag_and_drop (GtkWidget *self)
1796 {
1797 #ifdef MODEST_TOOLKIT_HILDON2
1798         return;
1799 #endif
1800         enable_drag_and_drop(self);
1801         g_signal_connect(G_OBJECT (self), "drag_data_get",
1802                          G_CALLBACK(drag_data_get_cb), NULL);
1803
1804         g_signal_connect(G_OBJECT (self), "drag_begin",
1805                          G_CALLBACK(drag_begin_cb), NULL);
1806
1807         g_signal_connect(G_OBJECT (self), "drag_end",
1808                          G_CALLBACK(drag_end_cb), NULL);
1809 }
1810
1811 static GtkTreePath *
1812 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1813 {
1814         GtkTreePath *path = NULL;
1815         GtkTreeSelection *sel = NULL;
1816         GList *rows = NULL;
1817
1818         sel   = gtk_tree_view_get_selection(self);
1819         rows = gtk_tree_selection_get_selected_rows (sel, model);
1820
1821         if ((rows == NULL) || (g_list_length(rows) != 1))
1822                 goto frees;
1823
1824         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1825
1826
1827         /* Free */
1828  frees:
1829         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1830         g_list_free(rows);
1831
1832         return path;
1833 }
1834
1835 #ifndef MODEST_TOOLKIT_HILDON2
1836 /*
1837  * This function moves the tree view scroll to the current selected
1838  * row when the widget grabs the focus
1839  */
1840 static gboolean
1841 on_focus_in (GtkWidget     *self,
1842              GdkEventFocus *event,
1843              gpointer       user_data)
1844 {
1845         GtkTreeSelection *selection;
1846         GtkTreeModel *model;
1847         GList *selected = NULL;
1848         GtkTreePath *selected_path = NULL;
1849
1850         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1851         if (!model)
1852                 return FALSE;
1853
1854         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1855         /* If none selected yet, pick the first one */
1856         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1857                 GtkTreeIter iter;
1858                 GtkTreePath *path;
1859
1860                 /* Return if the model is empty */
1861                 if (!gtk_tree_model_get_iter_first (model, &iter))
1862                         return FALSE;
1863
1864                 path = gtk_tree_model_get_path (model, &iter);
1865                 gtk_tree_selection_select_path (selection, path);
1866                 gtk_tree_path_free (path);
1867         }
1868
1869         /* Need to get the all the rows because is selection multiple */
1870         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1871         if (selected == NULL) return FALSE;
1872         selected_path = (GtkTreePath *) selected->data;
1873
1874         /* Frees */
1875         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1876         g_list_free (selected);
1877
1878         return FALSE;
1879 }
1880
1881 static gboolean
1882 on_focus_out (GtkWidget     *self,
1883              GdkEventFocus *event,
1884              gpointer       user_data)
1885 {
1886
1887         if (!gtk_widget_is_focus (self)) {
1888                 GtkTreeSelection *selection = NULL;
1889                 GList *selected_rows = NULL;
1890                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1891                 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1892                         selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1893                         g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1894                         gtk_tree_selection_unselect_all (selection);
1895                         gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1896                         g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1897                         g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1898                         g_list_free (selected_rows);
1899                 }
1900         }
1901         return FALSE;
1902 }
1903 #endif
1904
1905 static gboolean
1906 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1907 {
1908         enable_drag_and_drop(self);
1909         return FALSE;
1910 }
1911
1912 static gboolean
1913 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1914 {
1915         GtkTreeSelection *selection = NULL;
1916         GtkTreePath *path = NULL;
1917         gboolean already_selected = FALSE, already_opened = FALSE;
1918         ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1919
1920         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1921                 GtkTreeIter iter;
1922                 GtkTreeModel *model;
1923
1924                 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1925                 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1926
1927                 /* Get header from model */
1928                 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1929                 if (gtk_tree_model_get_iter (model, &iter, path)) {
1930                         GValue value = {0,};
1931                         TnyHeader *header;
1932
1933                         gtk_tree_model_get_value (model, &iter,
1934                                                   TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1935                                                   &value);
1936                         header = (TnyHeader *) g_value_get_object (&value);
1937                         if (TNY_IS_HEADER (header)) {
1938                                 status = modest_tny_all_send_queues_get_msg_status (header);
1939                                 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1940                                                                                            header, NULL);
1941                         }
1942                         g_value_unset (&value);
1943                 }
1944         }
1945
1946         /* Enable drag and drop only if the user clicks on a row that
1947            it's already selected. If not, let him select items using
1948            the pointer. If the message is in an OUTBOX and in sending
1949            status disable drag and drop as well */
1950         if (!already_selected ||
1951             status == MODEST_TNY_SEND_QUEUE_SENDING ||
1952             already_opened)
1953                 disable_drag_and_drop(self);
1954
1955         if (path != NULL)
1956                 gtk_tree_path_free(path);
1957
1958         /* If it's already opened then do not let the button-press
1959            event go on because it'll perform a message open because
1960            we're clicking on to an already selected header */
1961         return FALSE;
1962 }
1963
1964 static void
1965 folder_monitor_update (TnyFolderObserver *self,
1966                        TnyFolderChange *change)
1967 {
1968         ModestHeaderViewPrivate *priv = NULL;
1969         TnyFolderChangeChanged changed;
1970         TnyFolder *folder = NULL;
1971
1972         changed = tny_folder_change_get_changed (change);
1973
1974         /* Do not notify the observers if the folder of the header
1975            view has changed before this call to the observer
1976            happens */
1977         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1978         folder = tny_folder_change_get_folder (change);
1979         if (folder != priv->folder)
1980                 goto frees;
1981
1982         MODEST_DEBUG_BLOCK (
1983                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1984                                     g_print ("ADDED %d/%d (r/t) \n",
1985                                              tny_folder_change_get_new_unread_count (change),
1986                                              tny_folder_change_get_new_all_count (change));
1987                             if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1988                                     g_print ("ALL COUNT %d\n",
1989                                              tny_folder_change_get_new_all_count (change));
1990                             if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1991                                     g_print ("UNREAD COUNT %d\n",
1992                                              tny_folder_change_get_new_unread_count (change));
1993                             if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1994                                     g_print ("EXPUNGED %d/%d (r/t) \n",
1995                                              tny_folder_change_get_new_unread_count (change),
1996                                              tny_folder_change_get_new_all_count (change));
1997                             if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1998                                     g_print ("FOLDER RENAME\n");
1999                             if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2000                                     g_print ("MSG RECEIVED %d/%d (r/t) \n",
2001                                              tny_folder_change_get_new_unread_count (change),
2002                                              tny_folder_change_get_new_all_count (change));
2003                             g_print ("---------------------------------------------------\n");
2004                             );
2005
2006         /* Check folder count */
2007         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2008             (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2009
2010                 g_mutex_lock (priv->observers_lock);
2011
2012                 /* Emit signal to evaluate how headers changes affects
2013                    to the window view  */
2014                 g_signal_emit (G_OBJECT(self),
2015                                signals[MSG_COUNT_CHANGED_SIGNAL],
2016                                0, folder, change);
2017
2018                 /* Added or removed headers, so data stored on cliboard are invalid  */
2019                 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2020                         modest_email_clipboard_clear (priv->clipboard);
2021
2022                 g_mutex_unlock (priv->observers_lock);
2023         }
2024
2025         /* Free */
2026  frees:
2027         if (folder != NULL)
2028                 g_object_unref (folder);
2029 }
2030
2031 gboolean
2032 modest_header_view_is_empty (ModestHeaderView *self)
2033 {
2034         ModestHeaderViewPrivate *priv;
2035
2036         g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2037
2038         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2039
2040         return priv->status == HEADER_VIEW_EMPTY;
2041 }
2042
2043 void
2044 modest_header_view_clear (ModestHeaderView *self)
2045 {
2046         g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2047
2048         modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2049 }
2050
2051 void
2052 modest_header_view_copy_selection (ModestHeaderView *header_view)
2053 {
2054         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2055
2056         /* Copy selection */
2057         _clipboard_set_selected_data (header_view, FALSE);
2058 }
2059
2060 void
2061 modest_header_view_cut_selection (ModestHeaderView *header_view)
2062 {
2063         ModestHeaderViewPrivate *priv = NULL;
2064         const gchar **hidding = NULL;
2065         guint i, n_selected;
2066
2067         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2068
2069         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2070
2071         /* Copy selection */
2072         _clipboard_set_selected_data (header_view, TRUE);
2073
2074         /* Get hidding ids */
2075         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2076
2077         /* Clear hidding array created by previous cut operation */
2078         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2079
2080         /* Copy hidding array */
2081         priv->n_selected = n_selected;
2082         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2083         for (i=0; i < n_selected; i++)
2084                 priv->hidding_ids[i] = g_strdup(hidding[i]);
2085
2086         /* Hide cut headers */
2087         modest_header_view_refilter (header_view);
2088 }
2089
2090
2091
2092
2093 static void
2094 _clipboard_set_selected_data (ModestHeaderView *header_view,
2095                               gboolean delete)
2096 {
2097         ModestHeaderViewPrivate *priv = NULL;
2098         TnyList *headers = NULL;
2099
2100         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2101         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2102
2103         /* Set selected data on clipboard   */
2104         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2105         headers = modest_header_view_get_selected_headers (header_view);
2106         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2107
2108         /* Free */
2109         g_object_unref (headers);
2110 }
2111
2112 typedef struct {
2113         ModestHeaderView *self;
2114         TnyFolder *folder;
2115 } NotifyFilterInfo;
2116
2117 static gboolean
2118 notify_filter_change (gpointer data)
2119 {
2120         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2121
2122         g_signal_emit (info->self,
2123                        signals[MSG_COUNT_CHANGED_SIGNAL],
2124                        0, info->folder, NULL);
2125
2126         return FALSE;
2127 }
2128
2129 static void
2130 notify_filter_change_destroy (gpointer data)
2131 {
2132         NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2133         ModestHeaderViewPrivate *priv;
2134
2135         priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2136         priv->status_timeout = 0;
2137
2138         g_object_unref (info->self);
2139         g_object_unref (info->folder);
2140         g_slice_free (NotifyFilterInfo, info);
2141 }
2142
2143 static gboolean
2144 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2145 {
2146         /* For the moment we only need to filter outbox */
2147         return priv->is_outbox;
2148 }
2149
2150 static gboolean
2151 header_match_string (TnyHeader *header, gchar **words)
2152 {
2153         gchar *subject_fold;
2154         gchar *cc_fold;
2155         gchar *bcc_fold;
2156         gchar *to_fold;
2157         gchar *from_fold;
2158
2159         gchar **current_word;
2160         gboolean found;
2161
2162         subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2163         if (subject_fold == NULL) {
2164                 gchar *subject;
2165                 subject = tny_header_dup_subject (header);
2166                 if (subject != NULL) {
2167                         subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2168                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2169                                                 subject_fold, (GDestroyNotify) g_free);
2170                 }
2171                 g_free (subject);
2172         }
2173
2174         from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2175         if (from_fold == NULL) {
2176                 gchar *from;
2177                 from = tny_header_dup_from (header);
2178                 if (from != NULL) {
2179                         from_fold = from?g_utf8_casefold (from, -1):NULL;
2180                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2181                                                 from_fold, (GDestroyNotify) g_free);
2182                 }
2183                 g_free (from);
2184         }
2185
2186         to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2187         if (to_fold == NULL) {
2188                 gchar *to;
2189                 to = tny_header_dup_to (header);
2190                 if (to != NULL) {
2191                         to_fold = to?g_utf8_casefold (to, -1):NULL;
2192                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2193                                                 to_fold, (GDestroyNotify) g_free);
2194                 }
2195                 g_free (to);
2196         }
2197
2198         cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2199         if (cc_fold == NULL) {
2200                 gchar *cc;
2201                 cc = tny_header_dup_cc (header);
2202                 if (cc != NULL) {
2203                         cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2204                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2205                                                 cc_fold, (GDestroyNotify) g_free);
2206                 }
2207                 g_free (cc);
2208         }
2209
2210         bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2211         if (bcc_fold == NULL) {
2212                 gchar *bcc;
2213                 bcc = tny_header_dup_bcc (header);
2214                 if (bcc != NULL) {
2215                         bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2216                         g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2217                                                 bcc_fold, (GDestroyNotify) g_free);
2218                 }
2219                 g_free (bcc);
2220         }
2221
2222         found = TRUE;
2223
2224         for (current_word = words; *current_word != NULL; current_word++) {
2225
2226                 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2227                     || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2228                     || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2229                     || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2230                     || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2231                         found = TRUE;
2232                 } else {
2233                         found = FALSE;
2234                         break;
2235                 }
2236         }
2237
2238         return found;
2239 }
2240
2241 static gboolean
2242 filter_row (GtkTreeModel *model,
2243             GtkTreeIter *iter,
2244             gpointer user_data)
2245 {
2246         ModestHeaderViewPrivate *priv = NULL;
2247         TnyHeaderFlags flags;
2248         TnyHeader *header = NULL;
2249         guint i;
2250         gchar *id = NULL;
2251         gboolean visible = TRUE;
2252         gboolean found = FALSE;
2253         GValue value = {0,};
2254         HeaderViewStatus old_status;
2255
2256         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2257         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2258
2259         /* Get header from model */
2260         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2261         flags = (TnyHeaderFlags) g_value_get_int (&value);
2262         g_value_unset (&value);
2263         gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2264         header = (TnyHeader *) g_value_get_object (&value);
2265         g_value_unset (&value);
2266
2267         /* Get message id from header (ensure is a valid id) */
2268         if (!header) {
2269                 visible = FALSE;
2270                 goto frees;
2271         }
2272
2273         /* Hide deleted and mark as deleted heders */
2274         if (flags & TNY_HEADER_FLAG_DELETED ||
2275             flags & TNY_HEADER_FLAG_EXPUNGED) {
2276                 visible = FALSE;
2277                 goto frees;
2278         }
2279
2280         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2281                 if (current_folder_needs_filtering (priv) &&
2282                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2283                         visible = FALSE;
2284                         goto frees;
2285                 }
2286         }
2287
2288         if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2289                 if (current_folder_needs_filtering (priv) &&
2290                     modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2291                         visible = FALSE;
2292                         goto frees;
2293                 }
2294         }
2295
2296         if (visible && priv->filter_string) {
2297                 if (!header_match_string (header, priv->filter_string_splitted)) {
2298                         visible = FALSE;
2299                         goto frees;
2300                 }
2301                 if (priv->filter_date_range) {
2302                         if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2303                             ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2304                                 visible = FALSE;
2305                                 goto frees;
2306                         }
2307                 }
2308         }
2309
2310         /* If no data on clipboard, return always TRUE */
2311         if (modest_email_clipboard_cleared(priv->clipboard)) {
2312                 visible = TRUE;
2313                 goto frees;
2314         }
2315
2316         /* Check hiding */
2317         if (priv->hidding_ids != NULL) {
2318                 id = tny_header_dup_message_id (header);
2319                 for (i=0; i < priv->n_selected && !found; i++)
2320                         if (priv->hidding_ids[i] != NULL && id != NULL)
2321                                 found = (!strcmp (priv->hidding_ids[i], id));
2322
2323                 visible = !found;
2324                 g_free(id);
2325         }
2326
2327  frees:
2328         old_status = priv->status;
2329         priv->status = ((gboolean) priv->status) && !visible;
2330         if ((priv->notify_status) && (priv->status != old_status)) {
2331                 if (priv->status_timeout)
2332                         g_source_remove (priv->status_timeout);
2333
2334                 if (header) {
2335                         NotifyFilterInfo *info;
2336
2337                         info = g_slice_new0 (NotifyFilterInfo);
2338                         info->self = g_object_ref (G_OBJECT (user_data));
2339                         if (header)
2340                                 info->folder = tny_header_get_folder (header);
2341                         priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2342                                                                    notify_filter_change,
2343                                                                    info,
2344                                                                    notify_filter_change_destroy);
2345                 }
2346         }
2347         
2348         return visible;
2349 }
2350
2351 static void
2352 _clear_hidding_filter (ModestHeaderView *header_view)
2353 {
2354         ModestHeaderViewPrivate *priv = NULL;
2355         guint i;
2356
2357         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2358         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2359
2360         if (priv->hidding_ids != NULL) {
2361                 for (i=0; i < priv->n_selected; i++)
2362                         g_free (priv->hidding_ids[i]);
2363                 g_free(priv->hidding_ids);
2364         }
2365 }
2366
2367 void
2368 modest_header_view_refilter (ModestHeaderView *header_view)
2369 {
2370         GtkTreeModel *filter_model = NULL;
2371         ModestHeaderViewPrivate *priv = NULL;
2372
2373         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2374         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2375
2376         /* Hide cut headers */
2377         filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2378         if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2379                 priv->status = HEADER_VIEW_INIT;
2380                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2381         }
2382 }
2383
2384 /*
2385  * Called when an account is removed. If I'm showing a folder of the
2386  * account that has been removed then clear the view
2387  */
2388 static void
2389 on_account_removed (TnyAccountStore *self,
2390                     TnyAccount *account,
2391                     gpointer user_data)
2392 {
2393         ModestHeaderViewPrivate *priv = NULL;
2394
2395         /* Ignore changes in transport accounts */
2396         if (TNY_IS_TRANSPORT_ACCOUNT (account))
2397                 return;
2398
2399         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2400
2401         if (priv->folder) {
2402                 TnyAccount *my_account;
2403
2404                 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2405                     tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2406                         ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2407                         my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2408                 } else {
2409                         my_account = tny_folder_get_account (priv->folder);
2410                 }
2411
2412                 if (my_account) {
2413                         if (my_account == account)
2414                                 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2415                         g_object_unref (my_account);
2416                 }
2417         }
2418 }
2419
2420 void
2421 modest_header_view_add_observer(ModestHeaderView *header_view,
2422                                      ModestHeaderViewObserver *observer)
2423 {
2424         ModestHeaderViewPrivate *priv;
2425
2426         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2427         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2428
2429         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2430
2431         g_mutex_lock(priv->observer_list_lock);
2432         priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2433         g_mutex_unlock(priv->observer_list_lock);
2434 }
2435
2436 void
2437 modest_header_view_remove_observer(ModestHeaderView *header_view,
2438                                    ModestHeaderViewObserver *observer)
2439 {
2440         ModestHeaderViewPrivate *priv;
2441
2442         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2443         g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2444
2445         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2446
2447         g_mutex_lock(priv->observer_list_lock);
2448         priv->observer_list = g_slist_remove(priv->observer_list, observer);
2449         g_mutex_unlock(priv->observer_list_lock);
2450 }
2451
2452 static void
2453 modest_header_view_notify_observers(ModestHeaderView *header_view,
2454                                     GtkTreeModel *model,
2455                                     const gchar *tny_folder_id)
2456 {
2457         ModestHeaderViewPrivate *priv = NULL;
2458         GSList *iter;
2459         ModestHeaderViewObserver *observer;
2460
2461
2462         g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2463
2464         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2465
2466         g_mutex_lock(priv->observer_list_lock);
2467         iter = priv->observer_list;
2468         while(iter != NULL){
2469                 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2470                 modest_header_view_observer_update(observer, model,
2471                                 tny_folder_id);
2472                 iter = g_slist_next(iter);
2473         }
2474         g_mutex_unlock(priv->observer_list_lock);
2475 }
2476
2477 const gchar *
2478 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2479 {
2480         ModestHeaderViewPrivate *priv = NULL;
2481
2482         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2483         return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2484 }
2485
2486 void
2487 modest_header_view_set_filter (ModestHeaderView *self,
2488                                ModestHeaderViewFilter filter)
2489 {
2490         ModestHeaderViewPrivate *priv;
2491
2492         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2493         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2494
2495         priv->filter |= filter;
2496
2497         if (current_folder_needs_filtering (priv))
2498                 modest_header_view_refilter (self);
2499 }
2500
2501 void
2502 modest_header_view_unset_filter (ModestHeaderView *self,
2503                                  ModestHeaderViewFilter filter)
2504 {
2505         ModestHeaderViewPrivate *priv;
2506
2507         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2508         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2509
2510         priv->filter &= ~filter;
2511
2512         if (current_folder_needs_filtering (priv))
2513                 modest_header_view_refilter (self);
2514 }
2515
2516 static void
2517 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2518 {
2519         if (strcmp ("style", spec->name) == 0) {
2520                 update_style (MODEST_HEADER_VIEW (obj));
2521                 gtk_widget_queue_draw (GTK_WIDGET (obj));
2522         }
2523 }
2524
2525 static void
2526 update_style (ModestHeaderView *self)
2527 {
2528         ModestHeaderViewPrivate *priv;
2529         GdkColor style_color;
2530         GdkColor style_active_color;
2531         PangoAttrList *attr_list;
2532         GtkStyle *style;
2533         PangoAttribute *attr;
2534
2535         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2536         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2537
2538         /* Set color */
2539
2540         attr_list = pango_attr_list_new ();
2541         if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2542                 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2543         }
2544         priv->secondary_color = style_color;
2545         attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2546         pango_attr_list_insert (attr_list, attr);
2547
2548         /* set font */
2549         style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2550                                            (GTK_WIDGET(self)),
2551                                            "SmallSystemFont", NULL,
2552                                            G_TYPE_NONE);
2553         if (style) {
2554                 attr = pango_attr_font_desc_new (pango_font_description_copy
2555                                                  (style->font_desc));
2556                 pango_attr_list_insert (attr_list, attr);
2557
2558                 g_object_set (G_OBJECT (priv->renderer_address),
2559                               "foreground-gdk", &(priv->secondary_color),
2560                               "foreground-set", TRUE,
2561                               "attributes", attr_list,
2562                               NULL);
2563                 g_object_set (G_OBJECT (priv->renderer_date_status),
2564                               "foreground-gdk", &(priv->secondary_color),
2565                               "foreground-set", TRUE,
2566                               "attributes", attr_list,
2567                               NULL);
2568                 pango_attr_list_unref (attr_list);
2569         } else {
2570                 g_object_set (G_OBJECT (priv->renderer_address),
2571                               "foreground-gdk", &(priv->secondary_color),
2572                               "foreground-set", TRUE,
2573                               "scale", PANGO_SCALE_SMALL,
2574                               "scale-set", TRUE,
2575                               NULL);
2576                 g_object_set (G_OBJECT (priv->renderer_date_status),
2577                               "foreground-gdk", &(priv->secondary_color),
2578                               "foreground-set", TRUE,
2579                               "scale", PANGO_SCALE_SMALL,
2580                               "scale-set", TRUE,
2581                               NULL);
2582         }
2583
2584         if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2585                 priv->active_color = style_active_color;
2586 #ifdef MODEST_TOOLKIT_HILDON2
2587                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2588                 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2589 #endif
2590         } else {
2591 #ifdef MODEST_TOOLKIT_HILDON2
2592                 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2593 #endif
2594         }
2595 }
2596
2597 TnyHeader *
2598 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2599                                       gint initial_x,
2600                                       gint initial_y)
2601 {
2602         GtkTreePath *path;
2603         GtkTreeModel *tree_model;
2604         GtkTreeIter iter;
2605         TnyHeader *header;
2606
2607         /* Get tree path */
2608         if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2609                                                 initial_x,
2610                                                 initial_y,
2611                                                 &path,
2612                                                 NULL))
2613                 return NULL;
2614
2615         /* Get model */
2616         tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2617         if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2618                 return NULL;
2619
2620         /* Get header */
2621         gtk_tree_model_get (tree_model, &iter,
2622                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2623                             &header, -1);
2624
2625         return header;
2626 }
2627
2628 static gboolean
2629 parse_date_side (const gchar *string, time_t *date_side)
2630 {
2631         gchar *today;
2632         gchar *yesterday;
2633         gchar *casefold;
2634         GDate *date;
2635         gboolean result = FALSE;
2636
2637         if (string && string[0] == '\0') {
2638                 *date_side = 0;
2639                 return TRUE;
2640         }
2641
2642         casefold = g_utf8_casefold (string, -1);
2643         today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2644         yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2645         date = g_date_new ();
2646
2647         if (g_utf8_collate (casefold, today) == 0) {
2648                 *date_side = time (NULL);
2649                 result = TRUE;
2650                 goto frees;
2651         }
2652
2653         if (g_utf8_collate (casefold, yesterday) == 0) {
2654                 *date_side = time (NULL) - 24*60*60;
2655                 result = TRUE;
2656                 goto frees;
2657         }
2658
2659         g_date_set_parse (date, string);
2660         if (g_date_valid (date)) {
2661                 struct tm tm = {0};
2662                 g_date_to_struct_tm (date, &tm);
2663                 *date_side = mktime (&tm);
2664                 
2665                 result = TRUE;
2666                 goto frees;
2667         }
2668 frees:
2669         g_free (today);
2670         g_free (yesterday);
2671         g_free (casefold);
2672         g_date_free (date);
2673
2674         return result;
2675 }
2676
2677 static gboolean
2678 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2679 {
2680         gchar ** parts;
2681         gboolean valid;
2682
2683         parts = g_strsplit (string, "..", 2);
2684         valid = TRUE;
2685
2686         if (g_strv_length (parts) != 2) {
2687                 valid = FALSE;
2688                 goto frees;
2689                 g_strfreev (parts);
2690                 return FALSE;
2691         }
2692
2693         if (!parse_date_side (parts[0], date_range_start)) {
2694                 valid = FALSE;
2695                 goto frees;
2696         }
2697
2698         if (parse_date_side (parts[1], date_range_end)) {
2699                 if (*date_range_end == 0) {
2700                         *date_range_end = (time_t) -1;
2701                 } else {
2702                         *date_range_end += (24*60*60 - 1);
2703                 }
2704         } else {
2705                 valid = FALSE;
2706                 goto frees;
2707         }
2708                 
2709 frees:
2710         g_strfreev (parts);
2711         return valid;
2712 }
2713
2714 void
2715 modest_header_view_set_filter_string (ModestHeaderView *self,
2716                                       const gchar *filter_string)
2717 {
2718         ModestHeaderViewPrivate *priv;
2719
2720         g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2721         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2722
2723         if (priv->filter_string)
2724                 g_free (priv->filter_string);
2725
2726         priv->filter_string = g_strdup (filter_string);
2727         priv->filter_date_range = FALSE;
2728
2729         if (priv->filter_string_splitted) {
2730                 g_strfreev (priv->filter_string_splitted);
2731                 priv->filter_string_splitted = NULL;
2732         }
2733
2734         if (priv->filter_string) {
2735                 gchar **split, **current, **current_target;
2736
2737                 split = g_strsplit (priv->filter_string, " ", 0);
2738
2739                 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2740                 current_target = priv->filter_string_splitted;
2741                 for (current = split; *current != 0; current ++) {
2742                         gboolean has_date_range = FALSE;;
2743                         if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2744                                 time_t range_start, range_end;
2745                                 /* It contains .. but it's not ".." so it may be a date range */
2746                                 if (parse_date_range (*current, &range_start, &range_end)) {
2747                                         priv->filter_date_range = TRUE;
2748                                         has_date_range = TRUE;
2749                                         priv->date_range_start = range_start;
2750                                         priv->date_range_end = range_end;
2751                                 }
2752                         }
2753                         if (!has_date_range) {
2754                                 *current_target = g_utf8_casefold (*current, -1);
2755                                 current_target++;
2756                         }
2757                 }
2758                 *current_target = '\0';
2759                 g_strfreev (split);
2760         }
2761         modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2762 }
2763
2764 #ifdef MODEST_TOOLKIT_HILDON2
2765 static gboolean
2766 on_live_search_refilter (HildonLiveSearch *livesearch,
2767                          ModestHeaderView *self)
2768 {
2769         const gchar *needle;
2770
2771         needle = hildon_live_search_get_text (livesearch);
2772         if (needle && needle[0] != '\0') {
2773                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2774         } else {
2775                 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2776         }
2777
2778         priv->live_search_timeout = 0;
2779
2780         return FALSE;
2781 }
2782
2783 static gboolean
2784 on_live_search_refilter (HildonLiveSearch *livesearch,
2785                          ModestHeaderView *self)
2786 {
2787         ModestHeaderViewPrivate *priv;
2788         GtkTreeModel *model, *sortable, *filter;
2789
2790         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2791
2792         if (priv->live_search_timeout > 0) {
2793                 g_source_remove (priv->live_search_timeout);
2794                 priv->live_search_timeout = 0;
2795         }
2796
2797         model = NULL;
2798         filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2799         if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2800                 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2801                 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2802                         model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2803                 }
2804         }
2805
2806         if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2807                 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2808         } else {
2809                 on_live_search_timeout (self);
2810         }
2811
2812         return TRUE;
2813 }
2814
2815 GtkWidget *
2816 modest_header_view_setup_live_search (ModestHeaderView *self)
2817 {
2818         ModestHeaderViewPrivate *priv;
2819
2820         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2821         priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2822         priv->live_search = hildon_live_search_new ();
2823
2824         g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2825
2826         return priv->live_search;
2827 }
2828 #endif