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