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