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