a4b35106a46bd0de5618a9d5fd6599ad316338dc
[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         
1118         switch (col_id) {
1119         case TNY_HEADER_FLAG_ATTACHMENTS:
1120
1121                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1122                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1123                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1124                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1125
1126                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1127                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1128
1129                 return cmp ? cmp : t1 - t2;
1130                 
1131         case TNY_HEADER_FLAG_PRIORITY:
1132                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1133                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1134                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1135                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1136
1137                 /* This is for making priority values respect the intuitive sort relationship 
1138                  * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1139                 if (val1 == 0) val1 = 2;
1140                 if (val2 == 0) val2 = 2;
1141
1142                 cmp =  (val1 & TNY_HEADER_FLAG_PRIORITY) - (val2 & TNY_HEADER_FLAG_PRIORITY);
1143
1144                 return cmp ? cmp : t1 - t2;
1145
1146         default:
1147                 return &iter1 - &iter2; /* oughhhh  */
1148         }
1149 }
1150
1151 static gint
1152 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1153                   gpointer user_data)
1154 {
1155         gint t1, t2;
1156         gchar *val1, *val2;
1157         gint cmp;
1158 /*      static int counter = 0; */
1159
1160         g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1161
1162         gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1163                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1164         gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1165                             TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1166
1167         cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1168                                              val2 + modest_text_utils_get_subject_prefix_len(val2),
1169                                              TRUE);
1170         g_free (val1);
1171         g_free (val2);
1172         return cmp;
1173 }
1174
1175 /* Drag and drop stuff */
1176 static void
1177 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, 
1178                   GtkSelectionData *selection_data, 
1179                   guint info,  guint time, gpointer data)
1180 {
1181         GtkTreeModel *model = NULL;
1182         GtkTreeIter iter;
1183         GtkTreePath *source_row = NULL;
1184         
1185         source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1186         if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1187
1188         switch (info) {
1189         case MODEST_HEADER_ROW:
1190                 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1191                 break;
1192         case MODEST_MSG: {
1193                 TnyHeader *hdr;
1194                 gtk_tree_model_get (model, &iter,
1195                                     TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1196                                     -1);
1197                 if (hdr) {
1198                         g_object_unref (G_OBJECT(hdr));
1199                 }
1200                 break;
1201         }
1202         default:
1203                 g_message ("%s: default switch case.", __FUNCTION__);
1204         }
1205
1206         gtk_tree_path_free (source_row);
1207 }
1208
1209 /* Header view drag types */
1210 const GtkTargetEntry header_view_drag_types[] = {
1211         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1212         { "text/uri-list",      0,                   MODEST_MSG }, 
1213 };
1214
1215 static void
1216 setup_drag_and_drop (GtkTreeView *self)
1217 {
1218         gtk_drag_source_set (GTK_WIDGET (self),
1219                              GDK_BUTTON1_MASK,
1220                              header_view_drag_types,
1221                              G_N_ELEMENTS (header_view_drag_types),
1222                              GDK_ACTION_MOVE | GDK_ACTION_COPY);
1223
1224         g_signal_connect(G_OBJECT (self), "drag_data_get",
1225                          G_CALLBACK(drag_data_get_cb), NULL);
1226 }
1227
1228 static GtkTreePath *
1229 get_selected_row (GtkTreeView *self, GtkTreeModel **model) 
1230 {
1231         GtkTreePath *path = NULL;
1232         GtkTreeSelection *sel = NULL;   
1233         GList *rows = NULL;
1234
1235         sel   = gtk_tree_view_get_selection(self);
1236         rows = gtk_tree_selection_get_selected_rows (sel, model);
1237         
1238         if ((rows == NULL) || (g_list_length(rows) != 1))
1239                 goto frees;
1240
1241         path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1242         
1243
1244         /* Free */
1245  frees:
1246         g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1247         g_list_free(rows);
1248
1249         return path;
1250 }
1251
1252 /*
1253  * This function moves the tree view scroll to the current selected
1254  * row when the widget grabs the focus 
1255  */
1256 static gboolean 
1257 on_focus_in (GtkWidget     *self,
1258              GdkEventFocus *event,
1259              gpointer       user_data)
1260 {
1261         GtkTreeSelection *selection;
1262         GtkTreeModel *model;
1263         GList *selected = NULL;
1264         GtkTreePath *selected_path = NULL;
1265
1266         model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1267         if (!model)
1268                 return FALSE;
1269
1270         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1271         /* If none selected yet, pick the first one */
1272         if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1273                 GtkTreeIter iter;
1274                 GtkTreePath *path;
1275
1276                 /* Return if the model is empty */
1277                 if (!gtk_tree_model_get_iter_first (model, &iter))
1278                         return FALSE;
1279
1280                 path = gtk_tree_model_get_path (model, &iter);
1281                 gtk_tree_selection_select_path (selection, path);
1282                 gtk_tree_path_free (path);
1283         }
1284
1285         /* Need to get the all the rows because is selection multiple */
1286         selected = gtk_tree_selection_get_selected_rows (selection, &model);
1287         if (selected == NULL) return FALSE;
1288         selected_path = (GtkTreePath *) selected->data;
1289
1290         /* Check if we need to scroll */
1291         #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1292         GtkTreePath *start_path = NULL;
1293         GtkTreePath *end_path = NULL;
1294         if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1295                                              &start_path,
1296                                              &end_path)) {
1297
1298                 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1299                     (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1300
1301                         /* Scroll to first path */
1302                         gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1303                                                       selected_path,
1304                                                       NULL,
1305                                                       TRUE,
1306                                                       0.5,
1307                                                       0.0);
1308                 }
1309         }
1310         #endif /* GTK_CHECK_VERSION */
1311
1312         /* Frees */     
1313         g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1314         g_list_free (selected);
1315
1316         return FALSE;
1317 }
1318
1319 static gboolean
1320 idle_notify_added_headers (gpointer data)
1321 {
1322         modest_platform_on_new_msg ();
1323
1324         return FALSE;
1325 }
1326
1327 static void
1328 idle_notify_headers_count_changed_destroy (gpointer data)
1329 {
1330         HeadersCountChangedHelper *helper = NULL;
1331
1332         g_return_if_fail (data != NULL);
1333         helper = (HeadersCountChangedHelper *) data; 
1334
1335         g_object_unref (helper->change);
1336         g_slice_free (HeadersCountChangedHelper, helper);
1337 }
1338
1339 static gboolean
1340 idle_notify_headers_count_changed (gpointer data)
1341 {
1342         TnyFolder *folder = NULL;
1343         ModestHeaderViewPrivate *priv = NULL;
1344         HeadersCountChangedHelper *helper = NULL;
1345
1346         g_return_val_if_fail (data != NULL, FALSE);
1347         helper = (HeadersCountChangedHelper *) data; 
1348         g_return_val_if_fail (MODEST_IS_HEADER_VIEW(helper->self), FALSE);
1349         g_return_val_if_fail (TNY_FOLDER_CHANGE(helper->change), FALSE);
1350
1351         folder = tny_folder_change_get_folder (helper->change);
1352
1353         priv = MODEST_HEADER_VIEW_GET_PRIVATE (helper->self);
1354         g_mutex_lock (priv->observers_lock);
1355
1356         /* Emmit signal to evaluate how headers changes affects to the window view  */
1357         g_signal_emit (G_OBJECT(helper->self), signals[MSG_COUNT_CHANGED_SIGNAL], 0, folder, helper->change);
1358                 
1359         /* Added or removed headers, so data sotred on cliboard are invalid  */
1360         modest_email_clipboard_clear (priv->clipboard);
1361
1362         g_mutex_unlock (priv->observers_lock);
1363
1364         return FALSE;
1365 }
1366
1367 static void
1368 folder_monitor_update (TnyFolderObserver *self, 
1369                        TnyFolderChange *change)
1370 {
1371         TnyFolderChangeChanged changed;
1372         HeadersCountChangedHelper *helper = NULL;
1373
1374         changed = tny_folder_change_get_changed (change);
1375
1376         /* We need an idle because this function is called from within
1377            a thread, so it could cause problems if the modest platform
1378            code calls dbus for example */
1379         if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1380                 g_idle_add (idle_notify_added_headers, NULL);
1381         
1382         /* Check folder count */
1383         if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1384             (changed & TNY_FOLDER_CHANGE_CHANGED_REMOVED_HEADERS)) {
1385                 helper = g_slice_new0 (HeadersCountChangedHelper);
1386                 helper->self = MODEST_HEADER_VIEW(self);
1387                 helper->change = g_object_ref(change);
1388                 
1389                 g_idle_add_full (G_PRIORITY_DEFAULT, 
1390                                  idle_notify_headers_count_changed, 
1391                                  helper,
1392                                  idle_notify_headers_count_changed_destroy);
1393         }       
1394 }
1395
1396 void
1397 modest_header_view_clear (ModestHeaderView *self)
1398 {
1399         modest_header_view_set_folder (self, NULL, NULL, NULL);
1400 }
1401
1402 void 
1403 modest_header_view_copy_selection (ModestHeaderView *header_view)
1404 {
1405         /* Copy selection */
1406         _clipboard_set_selected_data (header_view, FALSE);
1407 }
1408
1409 void 
1410 modest_header_view_cut_selection (ModestHeaderView *header_view)
1411 {
1412         ModestHeaderViewPrivate *priv = NULL;
1413         GtkTreeModel *model = NULL;
1414         const gchar **hidding = NULL;
1415         guint i, n_selected;
1416
1417         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1418         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1419
1420         /* Copy selection */
1421         _clipboard_set_selected_data (header_view, TRUE);
1422
1423         /* Get hidding ids */
1424         hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected); 
1425         
1426         /* Clear hidding array created by previous cut operation */
1427         _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1428
1429         /* Copy hidding array */
1430         priv->n_selected = n_selected;
1431         priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1432         for (i=0; i < n_selected; i++) 
1433                 priv->hidding_ids[i] = g_strdup(hidding[i]);            
1434
1435         /* Hide cut headers */
1436         model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1437         if (GTK_IS_TREE_MODEL_FILTER (model))
1438                 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1439 }
1440
1441
1442  
1443
1444 static void
1445 _clipboard_set_selected_data (ModestHeaderView *header_view,
1446                               gboolean delete)
1447 {
1448         ModestHeaderViewPrivate *priv = NULL;
1449         TnyList *headers = NULL;
1450
1451         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1452         priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1453                 
1454         /* Set selected data on clipboard   */
1455         g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1456         headers = modest_header_view_get_selected_headers (header_view);
1457         modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1458
1459         /* Free */
1460         g_object_unref (headers);
1461 }
1462
1463
1464
1465 static gboolean
1466 filter_row (GtkTreeModel *model,
1467             GtkTreeIter *iter,
1468             gpointer user_data)
1469 {
1470         ModestHeaderViewPrivate *priv = NULL;
1471         TnyHeader *header = NULL;
1472         guint i;
1473         gchar *id = NULL;
1474         gboolean found = FALSE;
1475         
1476         g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1477         priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1478
1479         /* If no data on clipboard, return always TRUE */
1480         if (modest_email_clipboard_cleared(priv->clipboard)) return TRUE;
1481                 
1482         /* Get header from model */
1483         gtk_tree_model_get (model, iter,
1484                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1485                             -1);
1486         
1487         /* Get message id from header (ensure is a valid id) */
1488         if (!header) return FALSE;
1489         id = g_strdup(tny_header_get_message_id (header));
1490         
1491         /* Check hiding */
1492         if (priv->hidding_ids != NULL)
1493                 for (i=0; i < priv->n_selected && !found; i++)
1494                         if (priv->hidding_ids[i] != NULL && id != NULL)
1495                                 found = (!strcmp (priv->hidding_ids[i], id));
1496         
1497         /* Free */
1498         g_object_unref (header);
1499         g_free(id);
1500
1501         return !found;
1502 }
1503
1504 static void
1505 _clear_hidding_filter (ModestHeaderView *header_view) 
1506 {
1507         ModestHeaderViewPrivate *priv;
1508         guint i;
1509         
1510         g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view)); 
1511         priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1512
1513         if (priv->hidding_ids != NULL) {
1514                 for (i=0; i < priv->n_selected; i++) 
1515                         g_free (priv->hidding_ids[i]);
1516                 g_free(priv->hidding_ids);
1517         }       
1518 }