* Fixed some diomming rules.
[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 <string.h>
35
36 #include <modest-header-view.h>
37 #include <modest-header-view-priv.h>
38 #include <modest-dnd.h>
39 #include <modest-tny-folder.h>
40
41 #include <modest-main-window.h>
42
43 #include <modest-marshal.h>
44 #include <modest-text-utils.h>
45 #include <modest-icon-names.h>
46 #include <modest-runtime.h>
47 #include "modest-platform.h"
48 #include <modest-hbox-cell-renderer.h>
49 #include <modest-vbox-cell-renderer.h>
50
51 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
52 static void modest_header_view_init        (ModestHeaderView *obj);
53 static void modest_header_view_finalize    (GObject *obj);
54 static void modest_header_view_dispose     (GObject *obj);
55
56 static void          on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
57                                               GtkTreeViewColumn *column, gpointer userdata);
58
59 static gint          cmp_rows               (GtkTreeModel *tree_model,
60                                              GtkTreeIter *iter1,
61                                              GtkTreeIter *iter2,
62                                              gpointer user_data);
63
64 static gint          cmp_subject_rows       (GtkTreeModel *tree_model,
65                                              GtkTreeIter *iter1,
66                                              GtkTreeIter *iter2,
67                                              gpointer user_data);
68
69 static gboolean     filter_row             (GtkTreeModel *model,
70                                             GtkTreeIter *iter,
71                                             gpointer data);
72
73 static void          on_selection_changed   (GtkTreeSelection *sel, 
74                                              gpointer user_data);
75
76 static void          setup_drag_and_drop    (GtkTreeView *self);
77
78 static GtkTreePath * get_selected_row       (GtkTreeView *self, GtkTreeModel **model);
79
80 static gboolean      on_focus_in            (GtkWidget     *sef,
81                                              GdkEventFocus *event,
82                                              gpointer       user_data);
83
84 static void          folder_monitor_update  (TnyFolderObserver *self, 
85                                              TnyFolderChange *change);
86
87 static void          tny_folder_observer_init (TnyFolderObserverIface *klass);
88
89 static void          _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
90
91 static void          _clear_hidding_filter (ModestHeaderView *header_view);
92
93
94 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
95 struct _ModestHeaderViewPrivate {
96         TnyFolder            *folder;
97         ModestHeaderViewStyle style;
98
99         TnyFolderMonitor     *monitor;
100         GMutex               *observers_lock;
101
102         /* not unref this object, its a singlenton */
103         ModestEmailClipboard *clipboard;
104
105         /* Filter tree model */
106         gchar **hidding_ids;
107         guint n_selected;
108
109         gint                  sort_colid[2][TNY_FOLDER_TYPE_NUM];
110         gint                  sort_type[2][TNY_FOLDER_TYPE_NUM];
111
112
113 };
114
115 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
116 struct _HeadersCountChangedHelper {
117         ModestHeaderView *self;
118         TnyFolderChange  *change;       
119 };
120
121
122 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
123                                                 MODEST_TYPE_HEADER_VIEW, \
124                                                 ModestHeaderViewPrivate))
125
126
127
128 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
129
130 enum {
131         HEADER_SELECTED_SIGNAL,
132         HEADER_ACTIVATED_SIGNAL,
133         ITEM_NOT_FOUND_SIGNAL,
134         MSG_COUNT_CHANGED_SIGNAL,
135         LAST_SIGNAL
136 };
137
138 /* globals */
139 static GObjectClass *parent_class = NULL;
140
141 /* uncomment the following if you have defined any signals */
142 static guint signals[LAST_SIGNAL] = {0};
143
144 GType
145 modest_header_view_get_type (void)
146 {
147         static GType my_type = 0;
148         if (!my_type) {
149                 static const GTypeInfo my_info = {
150                         sizeof(ModestHeaderViewClass),
151                         NULL,           /* base init */
152                         NULL,           /* base finalize */
153                         (GClassInitFunc) modest_header_view_class_init,
154                         NULL,           /* class finalize */
155                         NULL,           /* class data */
156                         sizeof(ModestHeaderView),
157                         1,              /* n_preallocs */
158                         (GInstanceInitFunc) modest_header_view_init,
159                         NULL
160                 };
161
162                 static const GInterfaceInfo tny_folder_observer_info = 
163                 {
164                         (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
165                         NULL,         /* interface_finalize */
166                         NULL          /* interface_data */
167                 };
168                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
169                                                   "ModestHeaderView",
170                                                   &my_info, 0);
171
172                 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
173                                              &tny_folder_observer_info);
174
175
176         }
177         return my_type;
178 }
179
180 static void
181 modest_header_view_class_init (ModestHeaderViewClass *klass)
182 {
183         GObjectClass *gobject_class;
184         gobject_class = (GObjectClass*) klass;
185
186         parent_class            = g_type_class_peek_parent (klass);
187         gobject_class->finalize = modest_header_view_finalize;
188         gobject_class->dispose = modest_header_view_dispose;
189         
190         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
191         
192         signals[HEADER_SELECTED_SIGNAL] = 
193                 g_signal_new ("header_selected",
194                               G_TYPE_FROM_CLASS (gobject_class),
195                               G_SIGNAL_RUN_FIRST,
196                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
197                               NULL, NULL,
198                               g_cclosure_marshal_VOID__POINTER,
199                               G_TYPE_NONE, 1, G_TYPE_POINTER);
200
201         signals[HEADER_ACTIVATED_SIGNAL] = 
202                 g_signal_new ("header_activated",
203                               G_TYPE_FROM_CLASS (gobject_class),
204                               G_SIGNAL_RUN_FIRST,
205                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
206                               NULL, NULL,
207                               g_cclosure_marshal_VOID__POINTER,
208                               G_TYPE_NONE, 1, G_TYPE_POINTER);
209         
210         
211         signals[ITEM_NOT_FOUND_SIGNAL] = 
212                 g_signal_new ("item_not_found",
213                               G_TYPE_FROM_CLASS (gobject_class),
214                               G_SIGNAL_RUN_FIRST,
215                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
216                               NULL, NULL,
217                               g_cclosure_marshal_VOID__INT,
218                               G_TYPE_NONE, 1, G_TYPE_INT);
219
220         signals[MSG_COUNT_CHANGED_SIGNAL] =
221                 g_signal_new ("msg_count_changed",
222                               G_TYPE_FROM_CLASS (gobject_class),
223                               G_SIGNAL_RUN_FIRST,
224                               G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
225                               NULL, NULL,
226                               modest_marshal_VOID__POINTER_POINTER,
227                               G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
228 }
229
230 static void
231 tny_folder_observer_init (TnyFolderObserverIface *klass)
232 {
233         klass->update_func = folder_monitor_update;
234 }
235
236 static GtkTreeViewColumn*
237 get_new_column (const gchar *name, GtkCellRenderer *renderer,
238                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
239                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
240 {
241         GtkTreeViewColumn *column;
242
243         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
244         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
245
246         gtk_tree_view_column_set_resizable (column, resizable);
247         if (resizable) 
248                 gtk_tree_view_column_set_expand (column, TRUE);
249         
250         if (show_as_text)
251                 gtk_tree_view_column_add_attribute (column, renderer, "text",
252                                                     sort_col_id);
253         if (sort_col_id >= 0)
254                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
255
256         gtk_tree_view_column_set_sort_indicator (column, FALSE);
257         gtk_tree_view_column_set_reorderable (column, TRUE);
258         
259         if (cell_data_func)
260                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
261                                                         user_data, NULL);
262         return column;
263 }
264
265
266 static void
267 remove_all_columns (ModestHeaderView *obj)
268 {
269         GList *columns, *cursor;
270         
271         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
272
273         for (cursor = columns; cursor; cursor = cursor->next)
274                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
275                                              GTK_TREE_VIEW_COLUMN(cursor->data));
276         g_list_free (columns);  
277 }
278
279 gboolean
280 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
281 {
282         GtkTreeModel *tree_filter, *sortable;
283         GtkTreeViewColumn *column=NULL;
284         GtkTreeSelection *selection = NULL;
285         GtkCellRenderer *renderer_msgtype,*renderer_header,
286                 *renderer_attach, *renderer_compact_date;
287         GtkCellRenderer *renderer_compact_header, *renderer_recpt_box, 
288                 *renderer_subject, *renderer_subject_box, *renderer_recpt,
289                 *renderer_priority;
290         ModestHeaderViewPrivate *priv;
291         GtkTreeViewColumn *compact_column = NULL;
292         const GList *cursor;
293         
294         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
295
296         /* FIXME: check whether these renderers need to be freed */
297         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
298         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
299         renderer_priority  = gtk_cell_renderer_pixbuf_new ();
300         renderer_header  = gtk_cell_renderer_text_new ();
301
302         renderer_compact_header = modest_vbox_cell_renderer_new ();
303         renderer_recpt_box = modest_hbox_cell_renderer_new ();
304         renderer_subject_box = modest_hbox_cell_renderer_new ();
305         renderer_recpt = gtk_cell_renderer_text_new ();
306         renderer_subject = gtk_cell_renderer_text_new ();
307         renderer_compact_date  = gtk_cell_renderer_text_new ();
308
309         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
310         g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
311         modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
312         g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
313         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
314         g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
315         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
316         g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
317         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
318         g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
319         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
320         g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
321         modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date, FALSE);
322         g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date);
323
324         g_object_set(G_OBJECT(renderer_header),
325                      "ellipsize", PANGO_ELLIPSIZE_END,
326                      NULL);
327         g_object_set (G_OBJECT (renderer_subject),
328                       "ellipsize", PANGO_ELLIPSIZE_END,
329                       NULL);
330         g_object_set (G_OBJECT (renderer_recpt),
331                       "ellipsize", PANGO_ELLIPSIZE_END,
332                       NULL);
333         g_object_set(G_OBJECT(renderer_compact_date),
334                      "xalign", 1.0,
335                      NULL);
336
337         gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 32);
338         gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 32);
339         
340         remove_all_columns (self);
341
342         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
343         gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
344 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
345         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
346         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
347
348         /* Add new columns */
349         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
350                 ModestHeaderViewColumn col =
351                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
352                 
353                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
354                         g_printerr ("modest: invalid column %d in column list\n", col);
355                         continue;
356                 }
357                 
358                 switch (col) {
359                         
360                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
361                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
362                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
363                                                  FALSE,
364                                                  (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
365                                                  NULL);
366                         gtk_tree_view_column_set_fixed_width (column, 45);
367                         break;
368
369                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
370                         column = get_new_column (_("A"), renderer_attach, FALSE,
371                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
372                                                  FALSE,
373                                                  (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
374                                                  NULL);
375                         gtk_tree_view_column_set_fixed_width (column, 45);
376                         break;
377
378                         
379                 case MODEST_HEADER_VIEW_COLUMN_FROM:
380                         column = get_new_column (_("From"), renderer_header, TRUE,
381                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
382                                                  TRUE,
383                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
384                                                  GINT_TO_POINTER(TRUE));
385                         break;
386
387                 case MODEST_HEADER_VIEW_COLUMN_TO:
388                         column = get_new_column (_("To"), renderer_header, TRUE,
389                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
390                                                  TRUE,
391                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
392                                                  GINT_TO_POINTER(FALSE));
393                         break;
394                         
395                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
396                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
397                                                      TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
398                                                      FALSE,
399                                                      (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
400                                                      GINT_TO_POINTER(TRUE));
401                         compact_column = column;
402                         break;
403
404                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
405                         column = get_new_column (_("Header"), renderer_compact_header, TRUE,
406                                                      TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
407                                                      FALSE,
408                                                      (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
409                                                      GINT_TO_POINTER(FALSE));
410                         compact_column = column;
411                         break;
412
413                         
414                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
415                         column = get_new_column (_("Subject"), renderer_header, TRUE,
416                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
417                                                  TRUE,
418                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
419                                                  NULL);
420                         break;
421                         
422                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
423                         column = get_new_column (_("Received"), renderer_header, TRUE,
424                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
425                                                  TRUE,
426                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
427                                                  GINT_TO_POINTER(TRUE));
428                         break;
429                         
430                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:  
431                         column = get_new_column (_("Sent"), renderer_header, TRUE,
432                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
433                                                  TRUE,
434                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
435                                                  GINT_TO_POINTER(FALSE));
436                         break;
437                         
438                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
439                         column = get_new_column (_("Size"), renderer_header, TRUE,
440                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
441                                                  FALSE,
442                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
443                                                  NULL); 
444                         break;
445                 case MODEST_HEADER_VIEW_COLUMN_STATUS:
446                         column = get_new_column (_("Status"), renderer_compact_date, TRUE,
447                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
448                                                  FALSE,
449                                                  (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
450                                                  NULL); 
451                         break;
452
453                 default:
454                         g_return_val_if_reached(FALSE);
455                 }
456
457                 /* we keep the column id around */
458                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
459                                    GINT_TO_POINTER(col));
460                 
461                 /* we need this ptr when sorting the rows */
462                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
463                                    self);
464                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
465         }               
466
467         if (sortable) {
468                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
469                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
470                                                  (GtkTreeIterCompareFunc) cmp_rows,
471                                                  compact_column, NULL);
472                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
473                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
474                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
475                                                  compact_column, NULL);
476         }
477
478
479         return TRUE;
480 }
481
482 static void
483 modest_header_view_init (ModestHeaderView *obj)
484 {
485         ModestHeaderViewPrivate *priv;
486         guint i, j;
487
488         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
489
490         priv->folder  = NULL;
491
492         priv->monitor        = NULL;
493         priv->observers_lock = g_mutex_new ();
494
495         priv->clipboard = modest_runtime_get_email_clipboard ();
496         priv->hidding_ids = NULL;
497         priv->n_selected = 0;
498
499         /* Sort parameters */
500         for (j=0; j < 2; j++) {
501                 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
502                         priv->sort_colid[j][i] = -1;
503                         priv->sort_type[j][i] = GTK_SORT_DESCENDING;
504                 }                       
505         }
506
507         setup_drag_and_drop (GTK_TREE_VIEW (obj));
508 }
509
510 static void
511 modest_header_view_dispose (GObject *obj)
512 {
513         ModestHeaderView        *self;
514         ModestHeaderViewPrivate *priv;
515         
516         self = MODEST_HEADER_VIEW(obj);
517         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
518
519         if (priv->folder) {
520                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
521                 g_object_unref (G_OBJECT (priv->folder));
522                 priv->folder = NULL;
523         }
524
525         G_OBJECT_CLASS(parent_class)->dispose (obj);
526 }
527
528 static void
529 modest_header_view_finalize (GObject *obj)
530 {
531         ModestHeaderView        *self;
532         ModestHeaderViewPrivate *priv;
533         
534         self = MODEST_HEADER_VIEW(obj);
535         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
536
537         g_mutex_lock (priv->observers_lock);
538         if (priv->monitor) {
539                 tny_folder_monitor_stop (priv->monitor);
540                 g_object_unref (G_OBJECT (priv->monitor));
541         }
542         g_mutex_unlock (priv->observers_lock);
543         g_mutex_free (priv->observers_lock);
544
545         /* Clear hidding array created by cut operation */
546         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
547
548         G_OBJECT_CLASS(parent_class)->finalize (obj);
549 }
550
551
552 GtkWidget*
553 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
554 {
555         GObject *obj;
556         GtkTreeSelection *sel;
557         ModestHeaderView *self;
558         ModestHeaderViewPrivate *priv;
559         
560         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
561                               NULL);
562         
563         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
564         self = MODEST_HEADER_VIEW(obj);
565         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
566         
567         modest_header_view_set_style   (self, style);
568 /*      modest_header_view_set_folder (self, NULL, NULL, NULL); */
569
570         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
571         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
572         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
573         
574         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
575                                       TRUE); /* alternating row colors */
576         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
577         
578         g_signal_connect (sel, "changed",
579                           G_CALLBACK(on_selection_changed), self);
580         
581         g_signal_connect (self, "row-activated",
582                           G_CALLBACK (on_header_row_activated), NULL);
583
584         g_signal_connect (self, "focus-in-event",
585                           G_CALLBACK(on_focus_in), NULL);
586         
587         return GTK_WIDGET(self);
588 }
589
590
591 guint
592 modest_header_view_count_selected_headers (ModestHeaderView *self)
593 {
594         GtkTreeSelection *sel;
595         guint selected_rows;
596
597         g_return_val_if_fail (self, 0);
598         
599         /* Get selection object and check selected rows count */
600         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
601         selected_rows = gtk_tree_selection_count_selected_rows (sel);
602         
603         return selected_rows;
604 }
605
606 gboolean
607 modest_header_view_has_selected_headers (ModestHeaderView *self)
608 {
609         GtkTreeSelection *sel;
610         gboolean empty;
611
612         g_return_val_if_fail (self, FALSE);
613         
614         /* Get selection object and check selected rows count */
615         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
616         empty = gtk_tree_selection_count_selected_rows (sel) == 0;
617         
618         return !empty;
619 }
620
621
622 TnyList * 
623 modest_header_view_get_selected_headers (ModestHeaderView *self)
624 {
625         GtkTreeSelection *sel;
626         ModestHeaderViewPrivate *priv;
627         TnyList *header_list = NULL;
628         TnyHeader *header;
629         GList *list, *tmp = NULL;
630         GtkTreeModel *tree_model = NULL;
631         GtkTreeIter iter;
632
633         g_return_val_if_fail (self, NULL);
634         
635         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
636
637         /* Get selected rows */
638         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
639         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
640
641         if (list) {
642                 header_list = tny_simple_list_new();
643
644                 list = g_list_reverse (list);
645                 tmp = list;
646                 while (tmp) {                   
647                         /* get header from selection */
648                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
649                         gtk_tree_model_get (tree_model, &iter,
650                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
651                                             &header, -1);
652                         /* Prepend to list */
653                         tny_list_prepend (header_list, G_OBJECT (header));
654                         g_object_unref (G_OBJECT (header));
655
656                         tmp = g_list_next (tmp);
657                 }
658                 /* Clean up*/
659                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
660                 g_list_free (list);
661         }
662         return header_list;
663 }
664
665
666 /* scroll our list view so the selected item is visible */
667 static void
668 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
669 {
670 #ifdef MODEST_PLATFORM_GNOME 
671
672         GtkTreePath *selected_path;
673         GtkTreePath *start, *end;
674         
675         GtkTreeModel *model;
676         
677         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
678         selected_path = gtk_tree_model_get_path (model, iter);
679
680         start = gtk_tree_path_new ();
681         end   = gtk_tree_path_new ();
682
683         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
684
685         if (gtk_tree_path_compare (selected_path, start) < 0 ||
686             gtk_tree_path_compare (end, selected_path) < 0)
687                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
688                                               selected_path, NULL, TRUE,
689                                               up ? 0.0 : 1.0,
690                                               up ? 0.0 : 1.0);
691         gtk_tree_path_free (selected_path);
692         gtk_tree_path_free (start);
693         gtk_tree_path_free (end);
694
695 #endif /* MODEST_PLATFORM_GNOME */
696 }
697
698
699 void 
700 modest_header_view_select_next (ModestHeaderView *self)
701 {
702         GtkTreeSelection *sel;
703         GtkTreeIter iter;
704         GtkTreeModel *model;
705         GtkTreePath *path;
706
707         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
708         path = get_selected_row (GTK_TREE_VIEW(self), &model);
709         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
710                 /* Unselect previous path */
711                 gtk_tree_selection_unselect_path (sel, path);
712                 
713                 /* Move path down and selects new one  */
714                 if (gtk_tree_model_iter_next (model, &iter)) {
715                         gtk_tree_selection_select_iter (sel, &iter);
716                         scroll_to_selected (self, &iter, FALSE);        
717                 }
718                 gtk_tree_path_free(path);
719         }
720         
721 }
722
723 void 
724 modest_header_view_select_prev (ModestHeaderView *self)
725 {
726         GtkTreeSelection *sel;
727         GtkTreeIter iter;
728         GtkTreeModel *model;
729         GtkTreePath *path;
730
731         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
732         path = get_selected_row (GTK_TREE_VIEW(self), &model);
733         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
734                 /* Unselect previous path */
735                 gtk_tree_selection_unselect_path (sel, path);
736
737                 /* Move path up */
738                 if (gtk_tree_path_prev (path)) {
739                         gtk_tree_model_get_iter (model, &iter, path);
740                         
741                         /* Select the new one */
742                         gtk_tree_selection_select_iter (sel, &iter);
743                         scroll_to_selected (self, &iter, TRUE); 
744
745                 }
746                 gtk_tree_path_free (path);
747         }
748 }
749
750 GList*
751 modest_header_view_get_columns (ModestHeaderView *self)
752 {
753         g_return_val_if_fail (self, FALSE);
754         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
755 }
756
757
758 gboolean
759 modest_header_view_is_empty (ModestHeaderView *self)
760 {
761         g_return_val_if_fail (self, FALSE);
762         return FALSE; /* FIXME */
763 }
764
765
766 gboolean
767 modest_header_view_set_style (ModestHeaderView *self,
768                               ModestHeaderViewStyle style)
769 {
770         ModestHeaderViewPrivate *priv;
771         gboolean show_col_headers = FALSE;
772         ModestHeaderViewStyle old_style;
773         
774         g_return_val_if_fail (self, FALSE);
775         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
776                               FALSE);
777
778         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
779         if (priv->style == style)
780                 return TRUE; /* nothing to do */
781         
782         switch (style) {
783         case MODEST_HEADER_VIEW_STYLE_DETAILS:
784                 show_col_headers = TRUE;
785                 break;
786         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
787                 break;
788         default:
789                 g_return_val_if_reached (FALSE);
790         }
791         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
792         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
793
794         old_style   = priv->style;
795         priv->style = style;
796
797         return TRUE;
798 }
799
800
801 ModestHeaderViewStyle
802 modest_header_view_get_style (ModestHeaderView *self)
803 {
804         g_return_val_if_fail (self, FALSE);
805         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
806 }
807
808 /* 
809  * This function sets a sortable model in the header view. It's just
810  * used for developing purposes, because it only does a
811  * gtk_tree_view_set_model
812  */
813 static void
814 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
815 {
816 /*      GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
817 /*      if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
818 /*              GtkTreeModel *old_model; */
819 /*              ModestHeaderViewPrivate *priv; */
820 /*              priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
821 /*              old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
822
823 /*              /\* Set new model *\/ */
824 /*              gtk_tree_view_set_model (header_view, model); */
825 /*      } else */
826         gtk_tree_view_set_model (header_view, model);
827 }
828
829 TnyFolder*
830 modest_header_view_get_folder (ModestHeaderView *self)
831 {
832         ModestHeaderViewPrivate *priv;
833         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
834
835         if (priv->folder)
836                 g_object_ref (priv->folder);
837
838         return priv->folder;
839 }
840
841 static void
842 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
843 {
844         TnyFolderType type;
845         TnyList *headers;
846         ModestHeaderViewPrivate *priv;
847         GList *cols, *cursor;
848         GtkTreeModel *filter_model, *sortable; 
849         guint sort_colid;
850         GtkSortType sort_type;
851
852         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
853
854         headers = TNY_LIST (tny_gtk_header_list_model_new ());
855
856         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
857                                               folder, FALSE);
858
859         /* Add IDLE observer (monitor) and another folder observer for
860            new messages (self) */
861         g_mutex_lock (priv->observers_lock);
862         if (priv->monitor) {
863                 tny_folder_monitor_stop (priv->monitor);
864                 g_object_unref (G_OBJECT (priv->monitor));
865         }
866         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
867         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
868         tny_folder_monitor_start (priv->monitor);
869         g_mutex_unlock (priv->observers_lock);
870
871         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
872         g_object_unref (G_OBJECT (headers));
873
874         /* Create a tree model filter to hide and show rows for cut operations  */
875         filter_model = gtk_tree_model_filter_new (sortable, NULL);
876         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
877                                                 filter_row,
878                                                 self,
879                                                 NULL);
880         g_object_unref (G_OBJECT (sortable));
881
882         /* install our special sorting functions */
883         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
884
885         /* Restore sort column id */
886         if (cols) {
887                 type  = modest_tny_folder_guess_folder_type (folder);
888                 sort_colid = modest_header_view_get_sort_column_id (self, type); 
889                 sort_type = modest_header_view_get_sort_type (self, type); 
890                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
891                                                       sort_colid,
892                                                       sort_type);
893                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
894                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
895                                                  (GtkTreeIterCompareFunc) cmp_rows,
896                                                  cols->data, NULL);
897                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
898                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
899                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
900                                                  cols->data, NULL);
901         }
902
903         /* Set new model */
904         modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
905         g_object_unref (G_OBJECT (filter_model));
906 /*      modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
907 /*      g_object_unref (G_OBJECT (sortable)); */
908
909         /* Free */
910         g_list_free (cols);
911 }
912
913 void
914 modest_header_view_sort_by_column_id (ModestHeaderView *self, 
915                                       guint sort_colid,
916                                       GtkSortType sort_type)
917 {
918         ModestHeaderViewPrivate *priv = NULL;
919         GtkTreeModel *tree_filter, *sortable = NULL; 
920         TnyFolderType type;
921
922         /* Get model and private data */
923         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);            
924         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
925         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
926 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
927         
928         /* Sort tree model */
929         type  = modest_tny_folder_guess_folder_type (priv->folder);
930         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
931                                               sort_colid,
932                                               sort_type);
933         /* Store new sort parameters */
934         modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
935
936         /* Save GConf parameters */
937 /*      modest_widget_memory_save (modest_runtime_get_conf(), */
938 /*                                 G_OBJECT(self), "header-view"); */
939         
940 }
941
942 void
943 modest_header_view_set_sort_params (ModestHeaderView *self, 
944                                     guint sort_colid, 
945                                     GtkSortType sort_type,
946                                     TnyFolderType type)
947 {
948         ModestHeaderViewPrivate *priv;
949         ModestHeaderViewStyle style;
950
951         style = modest_header_view_get_style   (self);
952         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
953
954         priv->sort_colid[style][type] = sort_colid;
955         priv->sort_type[style][type] = sort_type;
956 }
957
958 gint
959 modest_header_view_get_sort_column_id (ModestHeaderView *self, 
960                                        TnyFolderType type)
961 {
962         ModestHeaderViewPrivate *priv;
963         ModestHeaderViewStyle style;
964
965         style = modest_header_view_get_style   (self);
966         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
967
968         return priv->sort_colid[style][type];
969 }
970
971 GtkSortType
972 modest_header_view_get_sort_type (ModestHeaderView *self, 
973                                   TnyFolderType type)
974 {
975         ModestHeaderViewPrivate *priv;
976         ModestHeaderViewStyle style;
977
978         style = modest_header_view_get_style   (self);
979         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
980
981         return priv->sort_type[style][type];
982 }
983
984 typedef struct {
985         ModestHeaderView *header_view;
986         RefreshAsyncUserCallback cb;
987         gpointer user_data;
988 } SetFolderHelper;
989
990 static void
991 folder_refreshed_cb (const GObject *obj, 
992                      TnyFolder *folder, 
993                      gpointer user_data)
994 {
995         ModestHeaderViewPrivate *priv;
996         SetFolderHelper *info;
997  
998         info = (SetFolderHelper*) user_data;
999
1000         priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1001
1002         /* User callback */
1003         if (info->cb)
1004                 info->cb (obj, folder, info->user_data);
1005
1006         /* Start the folder count changes observer. We do not need it
1007            before the refresh. Note that the monitor could still be
1008            called for this refresh but now we know that the callback
1009            was previously called */
1010         g_mutex_lock (priv->observers_lock);
1011         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1012         g_mutex_unlock (priv->observers_lock);
1013
1014         /* Frees */
1015         g_free (info);
1016 }
1017
1018 void
1019 modest_header_view_set_folder (ModestHeaderView *self, 
1020                                TnyFolder *folder,
1021                                RefreshAsyncUserCallback callback,
1022                                gpointer user_data)
1023 {
1024         ModestHeaderViewPrivate *priv;
1025         ModestWindowMgr *mgr = NULL;
1026         GObject *source = NULL;
1027         SetFolderHelper *info;
1028  
1029         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1030
1031         if (priv->folder) {
1032                 g_mutex_lock (priv->observers_lock);
1033                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1034                 g_object_unref (priv->folder);
1035                 priv->folder = NULL;
1036                 g_mutex_unlock (priv->observers_lock);
1037         }
1038
1039         if (folder) {
1040                 ModestMailOperation *mail_op = NULL;
1041
1042                 /* Get main window to use it as source of mail operation */
1043                 mgr = modest_runtime_get_window_mgr ();
1044                 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1045
1046                 /* Set folder in the model */
1047                 modest_header_view_set_folder_intern (self, folder);
1048
1049                 /* Pick my reference. Nothing to do with the mail operation */
1050                 priv->folder = g_object_ref (folder);
1051
1052                 /* no message selected */
1053                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1054
1055                 info = g_malloc0 (sizeof(SetFolderHelper));
1056                 info->header_view = self;
1057                 info->cb = callback;
1058                 info->user_data = user_data;
1059
1060                 /* Create the mail operation (source will be the parent widget) */
1061                 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, source);
1062                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1063                                                  mail_op);
1064
1065                 /* Refresh the folder asynchronously */
1066                 modest_mail_operation_refresh_folder (mail_op, 
1067                                                       folder,
1068                                                       folder_refreshed_cb,
1069                                                       info);
1070
1071                 /* Free */
1072                 g_object_unref (mail_op);
1073         } else {
1074                 g_mutex_lock (priv->observers_lock);
1075
1076                 if (priv->monitor) {
1077                         tny_folder_monitor_stop (priv->monitor);
1078                         g_object_unref (G_OBJECT (priv->monitor));
1079                         priv->monitor = NULL;
1080                 }
1081                 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL); 
1082
1083                 g_mutex_unlock (priv->observers_lock);
1084         }
1085 }
1086
1087 static void
1088 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1089                          GtkTreeViewColumn *column, gpointer userdata)
1090 {
1091         ModestHeaderView *self = NULL;
1092         ModestHeaderViewPrivate *priv = NULL;
1093         GtkTreeIter iter;
1094         GtkTreeModel *model = NULL;
1095         TnyHeader *header = NULL;
1096         TnyHeaderFlags flags;
1097
1098         self = MODEST_HEADER_VIEW (treeview);
1099         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1100
1101         model = gtk_tree_view_get_model (treeview);     
1102         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) 
1103                 goto frees;
1104
1105         /* get the first selected item */
1106         gtk_tree_model_get (model, &iter,
1107                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1108                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header, 
1109                             -1);
1110
1111         /* Dont open DELETED messages */
1112         if (flags & TNY_HEADER_FLAG_DELETED) {
1113                 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1114                 goto frees;
1115         }
1116
1117         /* Emit signal */
1118         g_signal_emit (G_OBJECT(self), 
1119                        signals[HEADER_ACTIVATED_SIGNAL], 
1120                        0, header);
1121
1122         /* Free */
1123  frees:
1124         if (header != NULL) 
1125                 g_object_unref (G_OBJECT (header));     
1126
1127 }
1128
1129 static void
1130 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1131 {
1132         GtkTreeModel *model;
1133         TnyHeader *header;
1134         GtkTreePath *path = NULL;       
1135         GtkTreeIter iter;
1136         ModestHeaderView *self;
1137         ModestHeaderViewPrivate *priv;
1138         GList *selected = NULL;
1139         
1140         g_return_if_fail (sel);
1141         g_return_if_fail (user_data);
1142         
1143         self = MODEST_HEADER_VIEW (user_data);
1144         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
1145
1146         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1147         if (selected != NULL) 
1148                 path = (GtkTreePath *) selected->data;
1149         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1150                 return; /* msg was _un_selected */
1151
1152         gtk_tree_model_get (model, &iter,
1153                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1154                             &header, -1);
1155
1156         /* Emit signal */
1157         g_signal_emit (G_OBJECT(self), 
1158                        signals[HEADER_SELECTED_SIGNAL], 
1159                        0, header);
1160
1161         g_object_unref (G_OBJECT (header));
1162
1163         /* free all items in 'selected' */
1164         g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1165         g_list_free (selected);
1166 }
1167
1168
1169 /* PROTECTED method. It's useful when we want to force a given
1170    selection to reload a msg. For example if we have selected a header
1171    in offline mode, when Modest become online, we want to reload the
1172    message automatically without an user click over the header */
1173 void 
1174 _modest_header_view_change_selection (GtkTreeSelection *selection,
1175                                       gpointer user_data)
1176 {
1177         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1178         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1179
1180         on_selection_changed (selection, user_data);
1181 }
1182
1183 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1184 {
1185         p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1186         p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1187         if (p1 == 0) 
1188                 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1189         if (p2 == 0) 
1190                 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1191         return p1 - p2;
1192 }
1193
1194 static gint
1195 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1196           gpointer user_data)
1197 {
1198         gint col_id;
1199         gint t1, t2;
1200         gint val1, val2;
1201         gint cmp;
1202 /*      static int counter = 0; */
1203
1204         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1205 /*      col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1206         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1207
1208         
1209         switch (col_id) {
1210         case TNY_HEADER_FLAG_ATTACHMENTS:
1211
1212                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1213                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1214                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1215                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1216
1217                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1218                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1219
1220                 return cmp ? cmp : t1 - t2;
1221                 
1222         case TNY_HEADER_FLAG_PRIORITY:
1223                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1224                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1225                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1226                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1227
1228                 /* This is for making priority values respect the intuitive sort relationship 
1229                  * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1230                 cmp =  compare_priorities (val1, val2);
1231
1232                 return cmp ? cmp : t1 - t2;
1233
1234         default:
1235                 return &iter1 - &iter2; /* oughhhh  */
1236         }
1237 }
1238
1239 static gint
1240 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1241                   gpointer user_data)
1242 {
1243         gint t1, t2;
1244         gchar *val1, *val2;
1245         gint cmp;
1246 /*      static int counter = 0; */
1247
1248         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1249
1250         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1251                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1252         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1253                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1254
1255         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1256                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1257                                              TRUE);
1258         g_free (val1);
1259         g_free (val2);
1260         return cmp;
1261 }
1262
1263 /* Drag and drop stuff */
1264 static void
1265 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, 
1266                   GtkSelectionData *selection_data, 
1267                   guint info,  guint time, gpointer data)
1268 {
1269         GtkTreeModel *model = NULL;
1270         GtkTreeIter iter;
1271         GtkTreePath *source_row = NULL;
1272         
1273         source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1274         if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1275
1276         switch (info) {
1277         case MODEST_HEADER_ROW:
1278                 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1279                 break;
1280         case MODEST_MSG: {
1281                 TnyHeader *hdr;
1282                 gtk_tree_model_get (model, &iter,
1283                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1284                                     -1);
1285                 if (hdr) {
1286                         g_object_unref (G_OBJECT(hdr));
1287                 }
1288                 break;
1289         }
1290         default:
1291                 g_message ("%s: default switch case.", __FUNCTION__);
1292         }
1293
1294         gtk_tree_path_free (source_row);
1295 }
1296
1297 /* Header view drag types */
1298 const GtkTargetEntry header_view_drag_types[] = {
1299         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1300         { "text/uri-list",      0,                   MODEST_MSG }, 
1301 };
1302
1303 static void
1304 setup_drag_and_drop (GtkTreeView *self)
1305 {
1306         gtk_drag_source_set (GTK_WIDGET (self),
1307                              GDK_BUTTON1_MASK,
1308                              header_view_drag_types,
1309                              G_N_ELEMENTS (header_view_drag_types),
1310                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1311
1312         g_signal_connect(G_OBJECT (self), "drag_data_get",
1313                          G_CALLBACK(drag_data_get_cb), NULL);
1314 }
1315
1316 static GtkTreePath *
1317 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1318 {
1319         GtkTreePath *path = NULL;
1320         GtkTreeSelection *sel = NULL;   
1321         GList *rows = NULL;
1322
1323         sel   = gtk_tree_view_get_selection(self);
1324         rows = gtk_tree_selection_get_selected_rows (sel, model);
1325         
1326         if ((rows == NULL) || (g_list_length(rows) != 1))
1327                 goto frees;
1328
1329         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1330         
1331
1332         /* Free */
1333  frees:
1334         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1335         g_list_free(rows);
1336
1337         return path;
1338 }
1339
1340 /*
1341  * This function moves the tree view scroll to the current selected
1342  * row when the widget grabs the focus 
1343  */
1344 static gboolean 
1345 on_focus_in (GtkWidget     *self,
1346              GdkEventFocus *event,
1347              gpointer       user_data)
1348 {
1349         GtkTreeSelection *selection;
1350         GtkTreeModel *model;
1351         GList *selected = NULL;
1352         GtkTreePath *selected_path = NULL;
1353
1354         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1355         if (!model)
1356                 return FALSE;
1357
1358         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1359         /* If none selected yet, pick the first one */
1360         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1361                 GtkTreeIter iter;
1362                 GtkTreePath *path;
1363
1364                 /* Return if the model is empty */
1365                 if (!gtk_tree_model_get_iter_first (model, &iter))
1366                         return FALSE;
1367
1368                 path = gtk_tree_model_get_path (model, &iter);
1369                 gtk_tree_selection_select_path (selection, path);
1370                 gtk_tree_path_free (path);
1371         }
1372
1373         /* Need to get the all the rows because is selection multiple */
1374         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1375         if (selected == NULL) return FALSE;
1376         selected_path = (GtkTreePath *) selected->data;
1377
1378         /* Check if we need to scroll */
1379         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1380         GtkTreePath *start_path = NULL;
1381         GtkTreePath *end_path = NULL;
1382         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1383                                              &start_path,
1384                                              &end_path)) {
1385
1386                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1387                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1388
1389                         /* Scroll to first path */
1390                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1391                                                       selected_path,
1392                                                       NULL,
1393                                                       TRUE,
1394                                                       0.5,
1395                                                       0.0);
1396                 }
1397         }
1398         #endif /* GTK_CHECK_VERSION */
1399
1400         /* Frees */     
1401         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1402         g_list_free (selected);
1403
1404         return FALSE;
1405 }
1406
1407 static void
1408 idle_notify_headers_count_changed_destroy (gpointer data)
1409 {
1410         HeadersCountChangedHelper *helper = NULL;
1411
1412         g_return_if_fail (data != NULL);
1413         helper = (HeadersCountChangedHelper *) data; 
1414
1415         g_object_unref (helper->change);
1416         g_slice_free (HeadersCountChangedHelper, helper);
1417 }
1418
1419 static gboolean
1420 idle_notify_headers_count_changed (gpointer data)
1421 {
1422         TnyFolder *folder = NULL;
1423         ModestHeaderViewPrivate *priv = NULL;
1424         HeadersCountChangedHelper *helper = NULL;
1425
1426         g_return_val_if_fail (data != NULL, FALSE);
1427         helper = (HeadersCountChangedHelper *) data; 
1428         g_return_val_if_fail (MODEST_IS_HEADER_VIEW(helper->self), FALSE);
1429         g_return_val_if_fail (TNY_FOLDER_CHANGE(helper->change), FALSE);
1430
1431         folder = tny_folder_change_get_folder (helper->change);
1432
1433         priv = MODEST_HEADER_VIEW_GET_PRIVATE (helper->self);
1434
1435         g_mutex_lock (priv->observers_lock);
1436
1437         /* Emit signal to evaluate how headers changes affects to the window view  */
1438         gdk_threads_enter ();
1439         g_signal_emit (G_OBJECT(helper->self), 
1440                        signals[MSG_COUNT_CHANGED_SIGNAL], 
1441                        0, folder, helper->change);
1442         gdk_threads_leave ();
1443                 
1444         /* Added or removed headers, so data stored on cliboard are invalid  */
1445         if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1446             modest_email_clipboard_clear (priv->clipboard);
1447             
1448         g_mutex_unlock (priv->observers_lock);
1449
1450         return FALSE;
1451 }
1452
1453 static void
1454 folder_monitor_update (TnyFolderObserver *self, 
1455                        TnyFolderChange *change)
1456 {
1457         ModestHeaderViewPrivate *priv;
1458         TnyFolderChangeChanged changed;
1459         HeadersCountChangedHelper *helper = NULL;
1460
1461         changed = tny_folder_change_get_changed (change);
1462         
1463         /* Do not notify the observers if the folder of the header
1464            view has changed before this call to the observer
1465            happens */
1466         priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1467         if (tny_folder_change_get_folder (change) != priv->folder)
1468                 return;
1469
1470         /* Check folder count */
1471         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1472             (changed & TNY_FOLDER_CHANGE_CHANGED_REMOVED_HEADERS)) {
1473                 helper = g_slice_new0 (HeadersCountChangedHelper);
1474                 helper->self = MODEST_HEADER_VIEW(self);
1475                 helper->change = g_object_ref(change);
1476                 
1477                 g_idle_add_full (G_PRIORITY_DEFAULT, 
1478                                  idle_notify_headers_count_changed, 
1479                                  helper,
1480                                  idle_notify_headers_count_changed_destroy);
1481         }       
1482 }
1483
1484 void
1485 modest_header_view_clear (ModestHeaderView *self)
1486 {
1487         modest_header_view_set_folder (self, NULL, NULL, NULL);
1488 }
1489
1490 void 
1491 modest_header_view_copy_selection (ModestHeaderView *header_view)
1492 {
1493         /* Copy selection */
1494         _clipboard_set_selected_data (header_view, FALSE);
1495 }
1496
1497 void 
1498 modest_header_view_cut_selection (ModestHeaderView *header_view)
1499 {
1500         ModestHeaderViewPrivate *priv = NULL;
1501         GtkTreeModel *model = NULL;
1502         const gchar **hidding = NULL;
1503         guint i, n_selected;
1504
1505         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1506         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1507
1508         /* Copy selection */
1509         _clipboard_set_selected_data (header_view, TRUE);
1510
1511         /* Get hidding ids */
1512         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1513         
1514         /* Clear hidding array created by previous cut operation */
1515         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1516
1517         /* Copy hidding array */
1518         priv->n_selected = n_selected;
1519         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1520         for (i=0; i < n_selected; i++) 
1521                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1522
1523         /* Hide cut headers */
1524         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1525         if (GTK_IS_TREE_MODEL_FILTER (model))
1526                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1527 }
1528
1529
1530  
1531
1532 static void
1533 _clipboard_set_selected_data (ModestHeaderView *header_view,
1534                               gboolean delete)
1535 {
1536         ModestHeaderViewPrivate *priv = NULL;
1537         TnyList *headers = NULL;
1538
1539         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1540         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1541                 
1542         /* Set selected data on clipboard   */
1543         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1544         headers = modest_header_view_get_selected_headers (header_view);
1545         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1546
1547         /* Free */
1548         g_object_unref (headers);
1549 }
1550
1551
1552
1553 static gboolean
1554 filter_row (GtkTreeModel *model,
1555             GtkTreeIter *iter,
1556             gpointer user_data)
1557 {
1558         ModestHeaderViewPrivate *priv = NULL;
1559         TnyHeaderFlags flags;
1560         TnyHeader *header = NULL;
1561         guint i;
1562         gchar *id = NULL;
1563         gboolean visible = TRUE;
1564         gboolean found = FALSE;
1565         
1566         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1567         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1568
1569         /* Get header from model */
1570         gtk_tree_model_get (model, iter,
1571                             TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1572                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1573                             -1);
1574         
1575         /* Hide mark as deleted heders */
1576         if (flags & TNY_HEADER_FLAG_DELETED) {
1577                 visible = FALSE;
1578                 goto frees;
1579         }
1580
1581         /* If no data on clipboard, return always TRUE */
1582         if (modest_email_clipboard_cleared(priv->clipboard)) {
1583                 visible = TRUE;
1584                 goto frees;
1585         }               
1586
1587         /* Get message id from header (ensure is a valid id) */
1588         if (!header) return FALSE;
1589         id = g_strdup(tny_header_get_message_id (header));
1590         
1591         /* Check hiding */
1592         if (priv->hidding_ids != NULL) {
1593                 for (i=0; i < priv->n_selected && !found; i++)
1594                         if (priv->hidding_ids[i] != NULL && id != NULL)
1595                                 found = (!strcmp (priv->hidding_ids[i], id));
1596         
1597                 visible = !found;
1598         }
1599
1600         /* Free */
1601  frees:
1602         if (header)
1603                 g_object_unref (header);
1604         g_free(id);
1605
1606         return visible;
1607 }
1608
1609 static void
1610 _clear_hidding_filter (ModestHeaderView *header_view) 
1611 {
1612         ModestHeaderViewPrivate *priv;
1613         guint i;
1614         
1615         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
1616         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1617
1618         if (priv->hidding_ids != NULL) {
1619                 for (i=0; i < priv->n_selected; i++) 
1620                         g_free (priv->hidding_ids[i]);
1621                 g_free(priv->hidding_ids);
1622         }       
1623 }