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