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