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