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