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