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