8fea86a363802373daeab39056283d83ff57b489
[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
529
530 static void
531 modest_header_view_finalize (GObject *obj)
532 {
533         ModestHeaderView        *self;
534         ModestHeaderViewPrivate *priv;
535         
536         self = MODEST_HEADER_VIEW(obj);
537         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
538
539         g_mutex_lock (priv->observers_lock);
540         if (priv->monitor) {
541                 tny_folder_monitor_stop (priv->monitor);
542                 g_object_unref (G_OBJECT (priv->monitor));
543         }
544         g_mutex_unlock (priv->observers_lock);
545         g_mutex_free (priv->observers_lock);
546
547         /* Clear hidding array created by cut operation */
548         _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
549
550         G_OBJECT_CLASS(parent_class)->finalize (obj);
551 }
552
553
554 GtkWidget*
555 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
556 {
557         GObject *obj;
558         GtkTreeSelection *sel;
559         ModestHeaderView *self;
560         ModestHeaderViewPrivate *priv;
561         
562         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
563                               NULL);
564         
565         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
566         self = MODEST_HEADER_VIEW(obj);
567         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
568         
569         modest_header_view_set_style   (self, style);
570 /*      modest_header_view_set_folder (self, NULL, NULL, NULL); */
571
572         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
573         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
574         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
575         
576         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
577                                       TRUE); /* alternating row colors */
578         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
579         
580         g_signal_connect (sel, "changed",
581                           G_CALLBACK(on_selection_changed), self);
582         
583         g_signal_connect (self, "row-activated",
584                           G_CALLBACK (on_header_row_activated), NULL);
585
586         g_signal_connect (self, "focus-in-event",
587                           G_CALLBACK(on_focus_in), NULL);
588         
589         return GTK_WIDGET(self);
590 }
591
592
593 TnyList * 
594 modest_header_view_get_selected_headers (ModestHeaderView *self)
595 {
596         GtkTreeSelection *sel;
597         ModestHeaderViewPrivate *priv;
598         TnyList *header_list = NULL;
599         TnyHeader *header;
600         GList *list, *tmp = NULL;
601         GtkTreeModel *tree_model = NULL;
602         GtkTreeIter iter;
603
604         g_return_val_if_fail (self, NULL);
605         
606         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
607
608         /* Get selected rows */
609         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
610         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
611
612         if (list) {
613                 header_list = tny_simple_list_new();
614
615                 list = g_list_reverse (list);
616                 tmp = list;
617                 while (tmp) {                   
618                         /* get header from selection */
619                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
620                         gtk_tree_model_get (tree_model, &iter,
621                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
622                                             &header, -1);
623                         /* Prepend to list */
624                         tny_list_prepend (header_list, G_OBJECT (header));
625                         g_object_unref (G_OBJECT (header));
626
627                         tmp = g_list_next (tmp);
628                 }
629                 /* Clean up*/
630                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
631                 g_list_free (list);
632         }
633         return header_list;
634 }
635
636
637 /* scroll our list view so the selected item is visible */
638 static void
639 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
640 {
641 #ifdef MODEST_PLATFORM_GNOME 
642
643         GtkTreePath *selected_path;
644         GtkTreePath *start, *end;
645         
646         GtkTreeModel *model;
647         
648         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
649         selected_path = gtk_tree_model_get_path (model, iter);
650
651         start = gtk_tree_path_new ();
652         end   = gtk_tree_path_new ();
653
654         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
655
656         if (gtk_tree_path_compare (selected_path, start) < 0 ||
657             gtk_tree_path_compare (end, selected_path) < 0)
658                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
659                                               selected_path, NULL, TRUE,
660                                               up ? 0.0 : 1.0,
661                                               up ? 0.0 : 1.0);
662         gtk_tree_path_free (selected_path);
663         gtk_tree_path_free (start);
664         gtk_tree_path_free (end);
665
666 #endif /* MODEST_PLATFORM_GNOME */
667 }
668
669
670 void 
671 modest_header_view_select_next (ModestHeaderView *self)
672 {
673         GtkTreeSelection *sel;
674         GtkTreeIter iter;
675         GtkTreeModel *model;
676         GtkTreePath *path;
677
678         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
679         path = get_selected_row (GTK_TREE_VIEW(self), &model);
680         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
681                 /* Unselect previous path */
682                 gtk_tree_selection_unselect_path (sel, path);
683                 
684                 /* Move path down and selects new one  */
685                 if (gtk_tree_model_iter_next (model, &iter)) {
686                         gtk_tree_selection_select_iter (sel, &iter);
687                         scroll_to_selected (self, &iter, FALSE);        
688                 }
689                 gtk_tree_path_free(path);
690         }
691         
692 }
693
694 void 
695 modest_header_view_select_prev (ModestHeaderView *self)
696 {
697         GtkTreeSelection *sel;
698         GtkTreeIter iter;
699         GtkTreeModel *model;
700         GtkTreePath *path;
701
702         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
703         path = get_selected_row (GTK_TREE_VIEW(self), &model);
704         if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
705                 /* Unselect previous path */
706                 gtk_tree_selection_unselect_path (sel, path);
707
708                 /* Move path up */
709                 if (gtk_tree_path_prev (path)) {
710                         gtk_tree_model_get_iter (model, &iter, path);
711                         
712                         /* Select the new one */
713                         gtk_tree_selection_select_iter (sel, &iter);
714                         scroll_to_selected (self, &iter, TRUE); 
715
716                 }
717                 gtk_tree_path_free (path);
718         }
719 }
720
721 GList*
722 modest_header_view_get_columns (ModestHeaderView *self)
723 {
724         g_return_val_if_fail (self, FALSE);
725         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
726 }
727
728
729 gboolean
730 modest_header_view_is_empty (ModestHeaderView *self)
731 {
732         g_return_val_if_fail (self, FALSE);
733         return FALSE; /* FIXME */
734 }
735
736
737 gboolean
738 modest_header_view_set_style (ModestHeaderView *self,
739                               ModestHeaderViewStyle style)
740 {
741         ModestHeaderViewPrivate *priv;
742         gboolean show_col_headers = FALSE;
743         ModestHeaderViewStyle old_style;
744         
745         g_return_val_if_fail (self, FALSE);
746         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
747                               FALSE);
748
749         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
750         if (priv->style == style)
751                 return TRUE; /* nothing to do */
752         
753         switch (style) {
754         case MODEST_HEADER_VIEW_STYLE_DETAILS:
755                 show_col_headers = TRUE;
756                 break;
757         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
758                 break;
759         default:
760                 g_return_val_if_reached (FALSE);
761         }
762         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
763         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
764
765         old_style   = priv->style;
766         priv->style = style;
767
768         return TRUE;
769 }
770
771
772 ModestHeaderViewStyle
773 modest_header_view_get_style (ModestHeaderView *self)
774 {
775         g_return_val_if_fail (self, FALSE);
776         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
777 }
778
779 /* 
780  * This function sets a sortable model in the header view. It's just
781  * used for developing purposes, because it only does a
782  * gtk_tree_view_set_model
783  */
784 static void
785 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
786 {
787 /*      GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
788 /*      if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
789 /*              GtkTreeModel *old_model; */
790 /*              ModestHeaderViewPrivate *priv; */
791 /*              priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
792 /*              old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
793
794 /*              /\* Set new model *\/ */
795 /*              gtk_tree_view_set_model (header_view, model); */
796 /*      } else */
797         gtk_tree_view_set_model (header_view, model);
798 }
799
800 TnyFolder*
801 modest_header_view_get_folder (ModestHeaderView *self)
802 {
803         ModestHeaderViewPrivate *priv;
804         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
805
806         if (priv->folder)
807                 g_object_ref (priv->folder);
808
809         return priv->folder;
810 }
811
812 static void
813 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
814 {
815         TnyFolderType type;
816         TnyList *headers;
817         ModestHeaderViewPrivate *priv;
818         GList *cols, *cursor;
819         GtkTreeModel *filter_model, *sortable; 
820         guint sort_colid;
821         GtkSortType sort_type;
822
823         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
824
825         headers = TNY_LIST (tny_gtk_header_list_model_new ());
826
827         tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
828                                               folder, FALSE);
829
830         /* Add IDLE observer (monitor) and another folder observer for
831            new messages (self) */
832         g_mutex_lock (priv->observers_lock);
833         if (priv->monitor) {
834                 tny_folder_monitor_stop (priv->monitor);
835                 g_object_unref (G_OBJECT (priv->monitor));
836         }
837         priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
838         tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
839         tny_folder_monitor_start (priv->monitor);
840         tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (self));
841         g_mutex_unlock (priv->observers_lock);
842
843         sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
844         g_object_unref (G_OBJECT (headers));
845
846         /* Create a tree model filter to hide and show rows for cut operations  */
847         filter_model = gtk_tree_model_filter_new (sortable, NULL);
848         gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
849                                                 filter_row,
850                                                 self,
851                                                 NULL);
852         g_object_unref (G_OBJECT (sortable));
853
854         /* install our special sorting functions */
855         cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
856
857         /* Restore sort column id */
858         if (cols) {
859                 type  = modest_tny_folder_guess_folder_type (folder);
860                 sort_colid = modest_header_view_get_sort_column_id (self, type); 
861                 sort_type = modest_header_view_get_sort_type (self, type); 
862                 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
863                                                       sort_colid,
864                                                       sort_type);
865                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
866                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
867                                                  (GtkTreeIterCompareFunc) cmp_rows,
868                                                  cols->data, NULL);
869                 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
870                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
871                                                  (GtkTreeIterCompareFunc) cmp_subject_rows,
872                                                  cols->data, NULL);
873         }
874
875         /* Set new model */
876         modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
877         g_object_unref (G_OBJECT (filter_model));
878 /*      modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
879 /*      g_object_unref (G_OBJECT (sortable)); */
880
881         /* Free */
882         g_list_free (cols);
883 }
884
885 void
886 modest_header_view_sort_by_column_id (ModestHeaderView *self, 
887                                       guint sort_colid,
888                                       GtkSortType sort_type)
889 {
890         ModestHeaderViewPrivate *priv = NULL;
891         GtkTreeModel *tree_filter, *sortable = NULL; 
892         TnyFolderType type;
893
894         /* Get model and private data */
895         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);            
896         tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
897         sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
898 /*      sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
899         
900         /* Sort tree model */
901         type  = modest_tny_folder_guess_folder_type (priv->folder);
902         gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
903                                               sort_colid,
904                                               sort_type);
905         /* Store new sort parameters */
906         modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
907
908         /* Save GConf parameters */
909 /*      modest_widget_memory_save (modest_runtime_get_conf(), */
910 /*                                 G_OBJECT(self), "header-view"); */
911         
912 }
913
914 void
915 modest_header_view_set_sort_params (ModestHeaderView *self, 
916                                     guint sort_colid, 
917                                     GtkSortType sort_type,
918                                     TnyFolderType type)
919 {
920         ModestHeaderViewPrivate *priv;
921         ModestHeaderViewStyle style;
922
923         style = modest_header_view_get_style   (self);
924         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
925
926         priv->sort_colid[style][type] = sort_colid;
927         priv->sort_type[style][type] = sort_type;
928 }
929
930 gint
931 modest_header_view_get_sort_column_id (ModestHeaderView *self, 
932                                        TnyFolderType type)
933 {
934         ModestHeaderViewPrivate *priv;
935         ModestHeaderViewStyle style;
936
937         style = modest_header_view_get_style   (self);
938         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
939
940         return priv->sort_colid[style][type];
941 }
942
943 GtkSortType
944 modest_header_view_get_sort_type (ModestHeaderView *self, 
945                                   TnyFolderType type)
946 {
947         ModestHeaderViewPrivate *priv;
948         ModestHeaderViewStyle style;
949
950         style = modest_header_view_get_style   (self);
951         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
952
953         return priv->sort_type[style][type];
954 }
955
956 void
957 modest_header_view_set_folder (ModestHeaderView *self, 
958                                TnyFolder *folder,
959                                RefreshAsyncUserCallback callback,
960                                gpointer user_data)
961 {
962         ModestHeaderViewPrivate *priv;
963         ModestWindowMgr *mgr = NULL;
964         GObject *source = NULL;
965  
966         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
967
968         if (priv->folder) {
969                 g_mutex_lock (priv->observers_lock);
970                 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
971                 g_object_unref (priv->folder);
972 /*              g_print ("---------- REMAINING %d\n", ((GObject*)priv->folder)->ref_count); */
973                 priv->folder = NULL;
974                 g_mutex_unlock (priv->observers_lock);
975         }
976
977         if (folder) {
978                 ModestMailOperation *mail_op = NULL;
979
980                 /* Get main window to use it as source of mail operation */
981                 mgr = modest_runtime_get_window_mgr ();
982                 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
983
984                 /* Set folder in the model */
985                 modest_header_view_set_folder_intern (self, folder);
986
987                 /* Pick my reference. Nothing to do with the mail operation */
988                 priv->folder = g_object_ref (folder);
989
990                 /* no message selected */
991                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
992
993                 /* Create the mail operation (source will be the parent widget) */
994                 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_TYPE_RECEIVE, source);
995                 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
996                                                  mail_op);
997
998                 /* Refresh the folder asynchronously */
999                 modest_mail_operation_refresh_folder (mail_op, folder,
1000                                                       callback,
1001                                                       user_data);
1002
1003                 /* Free */
1004                 g_object_unref (mail_op);
1005         } else {
1006                 g_mutex_lock (priv->observers_lock);
1007
1008                 if (priv->monitor) {
1009                         tny_folder_monitor_stop (priv->monitor);
1010                         g_object_unref (G_OBJECT (priv->monitor));
1011                         priv->monitor = NULL;
1012                 }
1013                 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL); 
1014
1015                 g_mutex_unlock (priv->observers_lock);
1016         }
1017 }
1018
1019 static void
1020 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1021                          GtkTreeViewColumn *column, gpointer userdata)
1022 {
1023         ModestHeaderView *self = NULL;
1024         ModestHeaderViewPrivate *priv = NULL;
1025         GtkTreeIter iter;
1026         GtkTreeModel *model = NULL;
1027         TnyHeader *header;
1028
1029         self = MODEST_HEADER_VIEW (treeview);
1030         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1031
1032         model = gtk_tree_view_get_model (treeview);
1033         
1034         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) 
1035                 return;
1036                         
1037         /* get the first selected item */
1038         gtk_tree_model_get (model, &iter,
1039                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1040                             &header, -1);
1041         /* Emit signal */
1042         g_signal_emit (G_OBJECT(self), 
1043                        signals[HEADER_ACTIVATED_SIGNAL], 
1044                        0, header);
1045
1046         /* Free */
1047         g_object_unref (G_OBJECT (header));
1048
1049 }
1050
1051 static void
1052 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1053 {
1054         GtkTreeModel *model;
1055         TnyHeader *header;
1056         GtkTreePath *path = NULL;       
1057         GtkTreeIter iter;
1058         ModestHeaderView *self;
1059         ModestHeaderViewPrivate *priv;
1060         GList *selected = NULL;
1061         
1062         g_return_if_fail (sel);
1063         g_return_if_fail (user_data);
1064         
1065         self = MODEST_HEADER_VIEW (user_data);
1066         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
1067
1068         selected = gtk_tree_selection_get_selected_rows (sel, &model);
1069         if (selected != NULL) 
1070                 path = (GtkTreePath *) selected->data;
1071         if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1072                 return; /* msg was _un_selected */
1073
1074         gtk_tree_model_get (model, &iter,
1075                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1076                             &header, -1);
1077
1078         /* Emit signal */
1079         g_signal_emit (G_OBJECT(self), 
1080                        signals[HEADER_SELECTED_SIGNAL], 
1081                        0, header);
1082
1083         g_object_unref (G_OBJECT (header));
1084         gtk_tree_path_free(path);
1085 }
1086
1087
1088 /* PROTECTED method. It's useful when we want to force a given
1089    selection to reload a msg. For example if we have selected a header
1090    in offline mode, when Modest become online, we want to reload the
1091    message automatically without an user click over the header */
1092 void 
1093 _modest_header_view_change_selection (GtkTreeSelection *selection,
1094                                       gpointer user_data)
1095 {
1096         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1097         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1098
1099         on_selection_changed (selection, user_data);
1100 }
1101
1102
1103 static gint
1104 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1105           gpointer user_data)
1106 {
1107         gint col_id;
1108         gint t1, t2;
1109         gint val1, val2;
1110         gint cmp;
1111 /*      static int counter = 0; */
1112
1113         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1114 /*      col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1115         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1116         
1117 /*      if (!(++counter % 100)) { */
1118 /*              GObject *header_view = g_object_get_data(G_OBJECT(user_data), */
1119 /*                                                       MODEST_HEADER_VIEW_PTR); */
1120 /*              g_signal_emit (header_view, */
1121 /*                             signals[STATUS_UPDATE_SIGNAL], */
1122 /*                             0, _("Sorting..."), 0, 0); */
1123 /*      } */
1124         switch (col_id) {
1125         case TNY_HEADER_FLAG_ATTACHMENTS:
1126
1127                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1128                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1129                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1130                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1131
1132                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1133                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1134
1135                 return cmp ? cmp : t1 - t2;
1136                 
1137         case TNY_HEADER_FLAG_PRIORITY:
1138                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1139                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1140                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1141                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1142
1143                 /* This is for making priority values respect the intuitive sort relationship 
1144                  * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1145                 if (val1 == 0) val1 = 2;
1146                 if (val2 == 0) val2 = 2;
1147
1148                 cmp =  (val1 & TNY_HEADER_FLAG_PRIORITY) - (val2 & TNY_HEADER_FLAG_PRIORITY);
1149
1150                 return cmp ? cmp : t1 - t2;
1151
1152         default:
1153                 return &iter1 - &iter2; /* oughhhh  */
1154         }
1155 }
1156
1157 static gint
1158 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1159                   gpointer user_data)
1160 {
1161         gint t1, t2;
1162         gchar *val1, *val2;
1163         gint cmp;
1164 /*      static int counter = 0; */
1165
1166         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1167
1168         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1169                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1170         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1171                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1172
1173         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1174                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1175                                              TRUE);
1176         g_free (val1);
1177         g_free (val2);
1178         return cmp;
1179 }
1180
1181 /* Drag and drop stuff */
1182 static void
1183 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, 
1184                   GtkSelectionData *selection_data, 
1185                   guint info,  guint time, gpointer data)
1186 {
1187         GtkTreeModel *model = NULL;
1188         GtkTreeIter iter;
1189         GtkTreePath *source_row = NULL;
1190         
1191         source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1192         if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1193
1194         switch (info) {
1195         case MODEST_HEADER_ROW:
1196                 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1197                 break;
1198         case MODEST_MSG: {
1199                 TnyHeader *hdr;
1200                 gtk_tree_model_get (model, &iter,
1201                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1202                                     -1);
1203                 if (hdr) {
1204                         g_object_unref (G_OBJECT(hdr));
1205                 }
1206                 break;
1207         }
1208         default:
1209                 g_message ("%s: default switch case.", __FUNCTION__);
1210         }
1211
1212         gtk_tree_path_free (source_row);
1213 }
1214
1215 /* Header view drag types */
1216 const GtkTargetEntry header_view_drag_types[] = {
1217         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1218         { "text/uri-list",      0,                   MODEST_MSG }, 
1219 };
1220
1221 static void
1222 setup_drag_and_drop (GtkTreeView *self)
1223 {
1224         gtk_drag_source_set (GTK_WIDGET (self),
1225                              GDK_BUTTON1_MASK,
1226                              header_view_drag_types,
1227                              G_N_ELEMENTS (header_view_drag_types),
1228                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1229
1230         g_signal_connect(G_OBJECT (self), "drag_data_get",
1231                          G_CALLBACK(drag_data_get_cb), NULL);
1232 }
1233
1234 static GtkTreePath *
1235 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1236 {
1237         GtkTreePath *path = NULL;
1238         GtkTreeSelection *sel = NULL;   
1239         GList *rows = NULL;
1240
1241         sel   = gtk_tree_view_get_selection(self);
1242         rows = gtk_tree_selection_get_selected_rows (sel, model);
1243         
1244         if ((rows == NULL) || (g_list_length(rows) != 1))
1245                 goto frees;
1246
1247         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1248         
1249
1250         /* Free */
1251  frees:
1252         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1253         g_list_free(rows);
1254
1255         return path;
1256 }
1257
1258 /*
1259  * This function moves the tree view scroll to the current selected
1260  * row when the widget grabs the focus 
1261  */
1262 static gboolean 
1263 on_focus_in (GtkWidget     *self,
1264              GdkEventFocus *event,
1265              gpointer       user_data)
1266 {
1267         GtkTreeSelection *selection;
1268         GtkTreeModel *model;
1269         GList *selected = NULL;
1270         GtkTreePath *selected_path = NULL;
1271
1272         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1273         if (!model)
1274                 return FALSE;
1275
1276         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1277         /* If none selected yet, pick the first one */
1278         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1279                 GtkTreeIter iter;
1280                 GtkTreePath *path;
1281
1282                 /* Return if the model is empty */
1283                 if (!gtk_tree_model_get_iter_first (model, &iter))
1284                         return FALSE;
1285
1286                 path = gtk_tree_model_get_path (model, &iter);
1287                 gtk_tree_selection_select_path (selection, path);
1288                 gtk_tree_path_free (path);
1289         }
1290
1291         /* Need to get the all the rows because is selection multiple */
1292         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1293         if (selected == NULL) return FALSE;
1294         selected_path = (GtkTreePath *) selected->data;
1295
1296         /* Check if we need to scroll */
1297         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1298         GtkTreePath *start_path = NULL;
1299         GtkTreePath *end_path = NULL;
1300         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1301                                              &start_path,
1302                                              &end_path)) {
1303
1304                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1305                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1306
1307                         /* Scroll to first path */
1308                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1309                                                       selected_path,
1310                                                       NULL,
1311                                                       TRUE,
1312                                                       0.5,
1313                                                       0.0);
1314                 }
1315         }
1316         #endif /* GTK_CHECK_VERSION */
1317
1318         /* Frees */     
1319         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1320         g_list_free (selected);
1321
1322         return FALSE;
1323 }
1324
1325 static gboolean
1326 idle_notify_added_headers (gpointer data)
1327 {
1328         modest_platform_on_new_msg ();
1329
1330         return FALSE;
1331 }
1332
1333 static void
1334 idle_notify_headers_count_changed_destroy (gpointer data)
1335 {
1336         HeadersCountChangedHelper *helper = NULL;
1337
1338         g_return_if_fail (data != NULL);
1339         helper = (HeadersCountChangedHelper *) data; 
1340
1341         g_object_unref (helper->change);
1342         g_slice_free (HeadersCountChangedHelper, helper);
1343 }
1344
1345 static gboolean
1346 idle_notify_headers_count_changed (gpointer data)
1347 {
1348         TnyFolder *folder = NULL;
1349         ModestHeaderViewPrivate *priv = NULL;
1350         HeadersCountChangedHelper *helper = NULL;
1351
1352         g_return_val_if_fail (data != NULL, FALSE);
1353         helper = (HeadersCountChangedHelper *) data; 
1354         g_return_val_if_fail (MODEST_IS_HEADER_VIEW(helper->self), FALSE);
1355         g_return_val_if_fail (TNY_FOLDER_CHANGE(helper->change), FALSE);
1356
1357         folder = tny_folder_change_get_folder (helper->change);
1358
1359         priv = MODEST_HEADER_VIEW_GET_PRIVATE (helper->self);
1360         g_mutex_lock (priv->observers_lock);
1361
1362         /* Emmit signal to evaluate how headers changes affects to the window view  */
1363         g_signal_emit (G_OBJECT(helper->self), signals[MSG_COUNT_CHANGED_SIGNAL], 0, folder, helper->change);
1364                 
1365         /* Added or removed headers, so data sotred on cliboard are invalid  */
1366         modest_email_clipboard_clear (priv->clipboard);
1367
1368         g_mutex_unlock (priv->observers_lock);
1369
1370         return FALSE;
1371 }
1372
1373 static void
1374 folder_monitor_update (TnyFolderObserver *self, 
1375                        TnyFolderChange *change)
1376 {
1377         TnyFolderChangeChanged changed;
1378         HeadersCountChangedHelper *helper = NULL;
1379
1380         changed = tny_folder_change_get_changed (change);
1381
1382         /* We need an idle because this function is called from within
1383            a thread, so it could cause problems if the modest platform
1384            code calls dbus for example */
1385         if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1386                 g_idle_add (idle_notify_added_headers, NULL);
1387         
1388         /* Check folder count */
1389         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1390             (changed & TNY_FOLDER_CHANGE_CHANGED_REMOVED_HEADERS)) {
1391                 helper = g_slice_new0 (HeadersCountChangedHelper);
1392                 helper->self = MODEST_HEADER_VIEW(self);
1393                 helper->change = g_object_ref(change);
1394                 
1395                 g_idle_add_full (G_PRIORITY_DEFAULT, 
1396                                  idle_notify_headers_count_changed, 
1397                                  helper,
1398                                  idle_notify_headers_count_changed_destroy);
1399         }       
1400 }
1401
1402 void
1403 modest_header_view_clear (ModestHeaderView *self)
1404 {
1405         modest_header_view_set_folder (self, NULL, NULL, NULL);
1406 }
1407
1408 void 
1409 modest_header_view_copy_selection (ModestHeaderView *header_view)
1410 {
1411         /* Copy selection */
1412         _clipboard_set_selected_data (header_view, FALSE);
1413 }
1414
1415 void 
1416 modest_header_view_cut_selection (ModestHeaderView *header_view)
1417 {
1418         ModestHeaderViewPrivate *priv = NULL;
1419         GtkTreeModel *model = NULL;
1420         const gchar **hidding = NULL;
1421         guint i, n_selected;
1422
1423         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1424         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1425
1426         /* Copy selection */
1427         _clipboard_set_selected_data (header_view, TRUE);
1428
1429         /* Get hidding ids */
1430         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1431         
1432         /* Clear hidding array created by previous cut operation */
1433         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1434
1435         /* Copy hidding array */
1436         priv->n_selected = n_selected;
1437         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1438         for (i=0; i < n_selected; i++) 
1439                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1440
1441         /* Hide cut headers */
1442         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1443         if (GTK_IS_TREE_MODEL_FILTER (model))
1444                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1445 }
1446
1447
1448  
1449
1450 static void
1451 _clipboard_set_selected_data (ModestHeaderView *header_view,
1452                               gboolean delete)
1453 {
1454         ModestHeaderViewPrivate *priv = NULL;
1455         TnyList *headers = NULL;
1456
1457         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1458         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1459                 
1460         /* Set selected data on clipboard   */
1461         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1462         headers = modest_header_view_get_selected_headers (header_view);
1463         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1464
1465         /* Free */
1466         g_object_unref (headers);
1467 }
1468
1469
1470
1471 static gboolean
1472 filter_row (GtkTreeModel *model,
1473             GtkTreeIter *iter,
1474             gpointer user_data)
1475 {
1476         ModestHeaderViewPrivate *priv = NULL;
1477         TnyHeader *header = NULL;
1478         guint i;
1479         gchar *id = NULL;
1480         gboolean found = FALSE;
1481         
1482         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1483         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1484
1485         /* If no data on clipboard, return always TRUE */
1486         if (modest_email_clipboard_cleared(priv->clipboard)) return TRUE;
1487                 
1488         /* Get header from model */
1489         gtk_tree_model_get (model, iter,
1490                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1491                             -1);
1492         
1493         /* Get message id from header (ensure is a valid id) */
1494         if (!header) return FALSE;
1495         id = g_strdup(tny_header_get_message_id (header));
1496         
1497         /* Check hiding */
1498         if (priv->hidding_ids != NULL)
1499                 for (i=0; i < priv->n_selected && !found; i++)
1500                         if (priv->hidding_ids[i] != NULL && id != NULL)
1501                                 found = (!strcmp (priv->hidding_ids[i], id));
1502         
1503         /* Free */
1504         g_object_unref (header);
1505         g_free(id);
1506
1507         return !found;
1508 }
1509
1510 static void
1511 _clear_hidding_filter (ModestHeaderView *header_view) 
1512 {
1513         ModestHeaderViewPrivate *priv;
1514         guint i;
1515         
1516         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
1517         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1518
1519         if (priv->hidding_ids != NULL) {
1520                 for (i=0; i < priv->n_selected; i++) 
1521                         g_free (priv->hidding_ids[i]);
1522                 g_free(priv->hidding_ids);
1523         }       
1524 }