* all:
[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 <string.h>
34
35 #include <modest-header-view.h>
36 #include <modest-header-view-priv.h>
37
38 #include <modest-marshal.h>
39 #include <modest-text-utils.h>
40 #include <modest-icon-names.h>
41
42 static void modest_header_view_class_init  (ModestHeaderViewClass *klass);
43 static void modest_header_view_init        (ModestHeaderView *obj);
44 static void modest_header_view_finalize    (GObject *obj);
45
46 static gboolean     on_header_clicked      (GtkWidget *widget, 
47                                             GdkEventButton *event, 
48                                             gpointer user_data);
49
50 static void         on_selection_changed   (GtkTreeSelection *sel, 
51                                             gpointer user_data);
52
53 static gint         cmp_rows               (GtkTreeModel *tree_model, 
54                                             GtkTreeIter *iter1, 
55                                             GtkTreeIter *iter2,
56                                             gpointer user_data);
57
58 static void         setup_drag_and_drop    (GtkTreeView *self);
59
60 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
61 struct _ModestHeaderViewPrivate {
62         TnyFolder            *folder;
63         TnyList              *headers;
64         GMutex               *lock;
65         ModestHeaderViewStyle style;
66 };
67
68 #define MODEST_HEADER_VIEW_GET_PRIVATE(o)      (G_TYPE_INSTANCE_GET_PRIVATE((o), \
69                                                 MODEST_TYPE_HEADER_VIEW, \
70                                                 ModestHeaderViewPrivate))
71
72
73
74 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
75
76 enum {
77         HEADER_SELECTED_SIGNAL,
78         HEADER_ACTIVATED_SIGNAL,
79         ITEM_NOT_FOUND_SIGNAL,
80         STATUS_UPDATE_SIGNAL,
81         LAST_SIGNAL
82 };
83
84 /* globals */
85 static GObjectClass *parent_class = NULL;
86
87 /* uncomment the following if you have defined any signals */
88 static guint signals[LAST_SIGNAL] = {0};
89
90 GType
91 modest_header_view_get_type (void)
92 {
93         static GType my_type = 0;
94         if (!my_type) {
95                 static const GTypeInfo my_info = {
96                         sizeof(ModestHeaderViewClass),
97                         NULL,           /* base init */
98                         NULL,           /* base finalize */
99                         (GClassInitFunc) modest_header_view_class_init,
100                         NULL,           /* class finalize */
101                         NULL,           /* class data */
102                         sizeof(ModestHeaderView),
103                         1,              /* n_preallocs */
104                         (GInstanceInitFunc) modest_header_view_init,
105                         NULL
106                 };
107                 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
108                                                   "ModestHeaderView",
109                                                   &my_info, 0);
110         }
111         return my_type;
112 }
113
114 static void
115 modest_header_view_class_init (ModestHeaderViewClass *klass)
116 {
117         GObjectClass *gobject_class;
118         gobject_class = (GObjectClass*) klass;
119
120         parent_class            = g_type_class_peek_parent (klass);
121         gobject_class->finalize = modest_header_view_finalize;
122         
123         g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
124         
125         signals[HEADER_SELECTED_SIGNAL] = 
126                 g_signal_new ("header_selected",
127                               G_TYPE_FROM_CLASS (gobject_class),
128                               G_SIGNAL_RUN_FIRST,
129                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
130                               NULL, NULL,
131                               g_cclosure_marshal_VOID__POINTER,
132                               G_TYPE_NONE, 1, G_TYPE_POINTER);
133
134         signals[HEADER_ACTIVATED_SIGNAL] = 
135                 g_signal_new ("header_activated",
136                               G_TYPE_FROM_CLASS (gobject_class),
137                               G_SIGNAL_RUN_FIRST,
138                               G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
139                               NULL, NULL,
140                               g_cclosure_marshal_VOID__POINTER,
141                               G_TYPE_NONE, 1, G_TYPE_POINTER);
142         
143         
144         signals[ITEM_NOT_FOUND_SIGNAL] = 
145                 g_signal_new ("item_not_found",
146                               G_TYPE_FROM_CLASS (gobject_class),
147                               G_SIGNAL_RUN_FIRST,
148                               G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
149                               NULL, NULL,
150                               g_cclosure_marshal_VOID__INT,
151                               G_TYPE_NONE, 1, G_TYPE_INT);
152
153         signals[STATUS_UPDATE_SIGNAL] =
154                 g_signal_new ("status_update",
155                               G_TYPE_FROM_CLASS (gobject_class),
156                               G_SIGNAL_RUN_FIRST,
157                               G_STRUCT_OFFSET (ModestHeaderViewClass,status_update),
158                               NULL, NULL,
159                               modest_marshal_VOID__STRING_INT_INT,
160                               G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
161 }
162
163 static GtkTreeViewColumn*
164 get_new_column (const gchar *name, GtkCellRenderer *renderer,
165                 gboolean resizable, gint sort_col_id, gboolean show_as_text,
166                 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
167 {
168         GtkTreeViewColumn *column;
169
170         column =  gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
171         gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
172
173         gtk_tree_view_column_set_resizable (column, resizable);
174         if (resizable) 
175                 gtk_tree_view_column_set_expand (column, TRUE);
176         
177         if (show_as_text) 
178                 gtk_tree_view_column_add_attribute (column, renderer, "text",
179                                                     sort_col_id);
180         if (sort_col_id >= 0)
181                 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
182
183         gtk_tree_view_column_set_sort_indicator (column, FALSE);
184         gtk_tree_view_column_set_reorderable (column, TRUE);
185         
186         if (cell_data_func)
187                 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
188                                                         user_data, NULL);
189         return column;
190 }
191
192
193
194
195 static void
196 remove_all_columns (ModestHeaderView *obj)
197 {
198         GList *columns, *cursor;
199
200         columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
201
202         for (cursor = columns; cursor; cursor = cursor->next)
203                 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
204                                              GTK_TREE_VIEW_COLUMN(cursor->data));
205         g_list_free (columns);  
206 }
207
208
209
210 gboolean
211 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns)
212 {
213         GtkTreeModel *sortable;
214         GtkTreeViewColumn *column=NULL;
215         GtkCellRenderer *renderer_msgtype,*renderer_header,
216                 *renderer_attach;
217         ModestHeaderViewPrivate *priv;
218         const GList *cursor;
219         
220         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); 
221
222         /* FIXME: check whether these renderers need to be freed */
223         renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
224         renderer_attach  = gtk_cell_renderer_pixbuf_new ();
225         renderer_header  = gtk_cell_renderer_text_new ();
226         
227         remove_all_columns (self);
228
229         if (priv->headers)
230                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
231         else
232                 sortable = NULL;
233         
234         for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
235                 ModestHeaderViewColumn col =
236                         (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
237                 
238                 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
239                         g_printerr ("modest: invalid column %d in column list\n", col);
240                         continue;
241                 }
242                 
243                 switch (col) {
244                         
245                 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
246                         column = get_new_column (_("M"), renderer_msgtype, FALSE,
247                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
248                                                  FALSE,
249                                                  (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
250                                                  NULL);
251                         gtk_tree_view_column_set_fixed_width (column, 45);
252                         break;
253
254                 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
255                         column = get_new_column (_("A"), renderer_attach, FALSE,
256                                                  TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
257                                                  FALSE,
258                                                  (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
259                                                  NULL);
260                         gtk_tree_view_column_set_fixed_width (column, 45);
261                         break;
262
263                         
264                 case MODEST_HEADER_VIEW_COLUMN_FROM:
265                         column = get_new_column (_("From"), renderer_header, TRUE,
266                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
267                                                  TRUE,
268                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
269                                                  GINT_TO_POINTER(TRUE));
270                         break;
271
272                 case MODEST_HEADER_VIEW_COLUMN_TO:
273                         column = get_new_column (_("To"), renderer_header, TRUE,
274                                                  TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
275                                                  TRUE,
276                                                  (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
277                                                  GINT_TO_POINTER(FALSE));
278                         break;
279                         
280                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
281                         column = get_new_column (_("Header"), renderer_header, TRUE,
282                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
283                                                  TRUE,
284                                                  (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
285                                                  GINT_TO_POINTER(TRUE));
286                         break;
287
288                 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
289                         column = get_new_column (_("Header"), renderer_header, TRUE,
290                                                  TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
291                                                  TRUE,
292                                                  (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
293                                                  GINT_TO_POINTER(FALSE));
294                         break;
295
296                         
297                 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
298                         column = get_new_column (_("Subject"), renderer_header, TRUE,
299                                                  TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
300                                                  TRUE,
301                                                  (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
302                                                  NULL);
303                         break;
304                         
305                 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
306                         column = get_new_column (_("Received"), renderer_header, TRUE,
307                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
308                                                  TRUE,
309                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
310                                                  GINT_TO_POINTER(TRUE));
311                         break;
312                         
313                 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
314                         column = get_new_column (_("Sent"), renderer_header, TRUE,
315                                                  TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
316                                                  TRUE,
317                                                  (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
318                                                  GINT_TO_POINTER(FALSE));
319                         break;
320                         
321                 case MODEST_HEADER_VIEW_COLUMN_SIZE:
322                         column = get_new_column (_("Size"), renderer_header, TRUE,
323                                                  TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
324                                                  FALSE,
325                                                  (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
326                                                  NULL); 
327                         break;
328
329                 default:
330                         g_return_val_if_reached(FALSE);
331                 }
332
333                 if (sortable)
334                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
335                                                          col, (GtkTreeIterCompareFunc)cmp_rows,
336                                                          column, NULL);
337                 
338                 /* we keep the column id around */
339                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
340                                    GINT_TO_POINTER(col));
341                 
342                 /* we need this ptr when sorting the rows */
343                 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
344                                    self);
345                 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);              
346         }       
347         return TRUE;
348 }
349
350 static void
351 modest_header_view_init (ModestHeaderView *obj)
352 {
353         ModestHeaderViewPrivate *priv;
354
355         priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); 
356
357         priv->lock    = g_mutex_new ();
358         priv->folder  = NULL;
359         priv->headers = NULL;
360
361         setup_drag_and_drop (GTK_TREE_VIEW (obj));
362 }
363
364 static void
365 modest_header_view_finalize (GObject *obj)
366 {
367         ModestHeaderView        *self;
368         ModestHeaderViewPrivate *priv;
369         
370         self = MODEST_HEADER_VIEW(obj);
371         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
372
373         if (priv->headers)      
374                 g_object_unref (G_OBJECT(priv->headers));
375         
376         if (priv->lock) {
377                 g_mutex_free (priv->lock);
378                 priv->lock = NULL;
379         }
380
381         priv->headers  = NULL;
382         priv->folder   = NULL;
383         
384         G_OBJECT_CLASS(parent_class)->finalize (obj);
385 }
386
387
388 GtkWidget*
389 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
390 {
391         GObject *obj;
392         GtkTreeSelection *sel;
393         ModestHeaderView *self;
394         ModestHeaderViewPrivate *priv;
395         
396         g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
397                               NULL);
398         
399         obj  = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
400         self = MODEST_HEADER_VIEW(obj);
401         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
402         
403         modest_header_view_set_style   (self, style);
404         modest_header_view_set_folder (self, NULL);
405
406         gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
407         gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
408         gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
409         
410         gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
411                                       TRUE); /* alternating row colors */
412         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
413         
414         g_signal_connect (sel, "changed",
415                           G_CALLBACK(on_selection_changed), self);
416         
417         g_signal_connect (self, "button-press-event",
418                           G_CALLBACK(on_header_clicked), NULL);
419         
420         return GTK_WIDGET(self);
421 }
422
423
424 TnyList * 
425 modest_header_view_get_selected_headers (ModestHeaderView *self)
426 {
427         GtkTreeSelection *sel;
428         ModestHeaderViewPrivate *priv;
429         TnyList *header_list = NULL;
430         TnyHeader *header;
431         GList *list, *tmp = NULL;
432         GtkTreeModel *tree_model = NULL;
433         GtkTreeIter iter;
434
435         g_return_val_if_fail (self, NULL);
436         
437         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
438
439         /* Get selected rows */
440         sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
441         list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
442
443         if (list) {
444                 header_list = tny_simple_list_new();
445
446                 list = g_list_reverse (list);
447                 tmp = list;
448                 while (tmp) {                   
449                         /* get header from selection */
450                         gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
451                         gtk_tree_model_get (tree_model, &iter,
452                                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
453                                             &header, -1);
454                         /* Prepend to list */
455                         tny_list_prepend (header_list, G_OBJECT (header));
456                         tmp = g_list_next (tmp);
457                 }
458                 /* Clean up*/
459                 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
460                 g_list_free (list);
461         }
462         return header_list;
463 }
464
465
466 /* scroll our list view so the selected item is visible */
467 static void
468 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
469 {
470 #if MODEST_PLATFORM_ID==1  /* MODES_PLATFORM_ID: 1 ==> gtk, 2==> maemo */ 
471
472         GtkTreePath *selected_path;
473         GtkTreePath *start, *end;
474         
475         GtkTreeModel *model;
476         
477         model         = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
478         selected_path = gtk_tree_model_get_path (model, iter);
479
480         start = gtk_tree_path_new ();
481         end   = gtk_tree_path_new ();
482
483         gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
484
485         if (gtk_tree_path_compare (selected_path, start) < 0 ||
486             gtk_tree_path_compare (end, selected_path) < 0)
487                 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
488                                               selected_path, NULL, TRUE,
489                                               up ? 0.0 : 1.0,
490                                               up ? 0.0 : 1.0);
491         gtk_tree_path_free (selected_path);
492         gtk_tree_path_free (start);
493         gtk_tree_path_free (end);
494
495 #endif /* MODEST_PLATFORM_ID */
496 }
497
498
499 void 
500 modest_header_view_select_next (ModestHeaderView *self)
501 {
502         GtkTreeSelection *sel;
503         GtkTreeIter iter;
504         GtkTreeModel *model;
505
506         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
507         if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
508                 if (gtk_tree_model_iter_next (model, &iter)) {
509                         gtk_tree_selection_select_iter (sel, &iter);
510                         scroll_to_selected (self, &iter, FALSE);        
511                 }
512         }
513 }
514
515 void 
516 modest_header_view_select_prev (ModestHeaderView *self)
517 {
518         GtkTreeSelection *sel;
519         GtkTreeIter iter;
520         GtkTreeModel *model;
521         GtkTreePath *path;
522
523         sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
524         if (gtk_tree_selection_get_selected (sel, &model, &iter)) {             
525                 path = gtk_tree_model_get_path (model, &iter);
526
527                 /* Move path up */
528                 if (gtk_tree_path_prev (path)) {
529                         gtk_tree_model_get_iter (model, &iter, path);
530                         
531                         /* Select the new one */
532                         gtk_tree_selection_select_iter (sel, &iter);
533                         scroll_to_selected (self, &iter, TRUE); 
534
535                 }
536                 gtk_tree_path_free (path);
537         }
538 }
539
540 GList*
541 modest_header_view_get_columns (ModestHeaderView *self)
542 {
543         g_return_val_if_fail (self, FALSE);
544         return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); 
545 }
546
547 gboolean
548 modest_header_view_is_empty (ModestHeaderView *self)
549 {
550         g_return_val_if_fail (self, FALSE);
551         return FALSE; /* FIXME */
552 }
553
554
555 gboolean
556 modest_header_view_set_style (ModestHeaderView *self,
557                               ModestHeaderViewStyle style)
558 {
559         ModestHeaderViewPrivate *priv;
560         gboolean show_col_headers = FALSE;
561         ModestHeaderViewStyle old_style;
562         
563         g_return_val_if_fail (self, FALSE);
564         g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
565                               FALSE);
566
567         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
568         if (priv->style == style)
569                 return TRUE; /* nothing to do */
570         
571         switch (style) {
572         case MODEST_HEADER_VIEW_STYLE_DETAILS:
573                 show_col_headers = TRUE;
574                 break;
575         case MODEST_HEADER_VIEW_STYLE_TWOLINES:
576                 break;
577         default:
578                 g_return_val_if_reached (FALSE);
579         }
580         gtk_tree_view_set_headers_visible   (GTK_TREE_VIEW(self), show_col_headers);
581         gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);    
582
583         old_style   = priv->style;
584         priv->style = style;
585
586         return TRUE;
587 }
588
589
590 ModestHeaderViewStyle
591 modest_header_view_get_style (ModestHeaderView *self)
592 {
593         g_return_val_if_fail (self, FALSE);
594         return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
595 }
596
597
598 static void
599 on_refresh_folder (TnyFolder *folder, gboolean cancelled, GError **err,
600                    gpointer user_data)
601 {
602         GtkTreeModel *sortable; 
603         ModestHeaderView *self;
604         ModestHeaderViewPrivate *priv;
605         GError *error = NULL;
606
607         if (cancelled) {
608                 GtkTreeSelection *selection;
609
610                 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (user_data));
611                 gtk_tree_selection_unselect_all (selection);
612                 return;
613         }
614         
615         self = MODEST_HEADER_VIEW(user_data);
616         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
617
618         priv->folder = folder;
619                 
620         if (folder) { /* it's a new one or a refresh */
621                 GList *cols, *cursor;
622                 
623                 if (priv->headers)
624                         g_object_unref (priv->headers);
625
626                 priv->headers = TNY_LIST(tny_gtk_header_list_model_new ());
627                 tny_folder_get_headers (folder, priv->headers, FALSE, &error); /* FIXME */
628                 if (error) {
629                         g_signal_emit (G_OBJECT(self), signals[ITEM_NOT_FOUND_SIGNAL],
630                                        0, MODEST_ITEM_TYPE_MESSAGE);
631                         g_print (error->message);
632                         g_error_free (error);
633                         return;
634                 }
635
636                 tny_gtk_header_list_model_set_folder
637                         (TNY_GTK_HEADER_LIST_MODEL(priv->headers),folder, TRUE); /*async*/
638
639                 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers));
640
641                 /* install our special sorting functions */
642                 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
643                 while (cursor) {
644                         gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data),
645                                                                          MODEST_HEADER_VIEW_COLUMN));
646                         gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
647                                                          col_id,
648                                                          (GtkTreeIterCompareFunc)cmp_rows,
649                                                          cursor->data, NULL);
650                         cursor = g_list_next(cursor);
651                 }
652                 g_list_free (cols);             
653                 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
654         }
655 }
656
657
658 static void
659 on_refresh_folder_status_update (TnyFolder *folder, const gchar *msg,
660                                  gint num, gint total,  gpointer user_data)
661 {
662         ModestHeaderView *self;
663         ModestHeaderViewPrivate *priv;
664
665         self = MODEST_HEADER_VIEW(user_data);
666         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
667
668         /* FIXME: this is a hack ==> tinymail gives us this when
669          * it has nothing better to do */
670         if (num == 1 && total == 100)
671                 return;
672         
673         g_signal_emit (G_OBJECT(self), signals[STATUS_UPDATE_SIGNAL],
674                        0, msg, num, total);
675 }
676
677
678 TnyFolder*
679 modest_header_view_get_folder (ModestHeaderView *self)
680 {
681         ModestHeaderViewPrivate *priv;
682         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
683
684         return priv->folder;
685 }
686
687
688 void
689 modest_header_view_set_folder (ModestHeaderView *self, TnyFolder *folder)
690 {
691         ModestHeaderViewPrivate *priv;
692         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
693
694         priv->folder = folder;
695
696         if (folder) {
697                 tny_folder_refresh_async (folder,
698                                           on_refresh_folder,
699                                           on_refresh_folder_status_update,
700                                           self);
701
702                 /* no message selected */               
703                 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0,
704                                NULL);
705         }
706 }
707
708 static gboolean
709 on_header_clicked (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
710 {
711         ModestHeaderView *self;
712         ModestHeaderViewPrivate *priv;
713         GtkTreeIter iter;
714         GtkTreeSelection *sel;
715         GtkTreeModel *model;
716         TnyHeader *header;
717
718         /* ignore everything but doubleclick */
719         if (event->type != GDK_2BUTTON_PRESS)
720                 return FALSE;
721
722         self = MODEST_HEADER_VIEW (widget);
723         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
724         
725         sel   = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
726         model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
727         
728         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) 
729                 return FALSE; /* msg was _un_selected */
730
731         /* get the first selected item */
732         gtk_tree_model_get (model, &iter,
733                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
734                             &header, -1);
735         /* Emit signal */
736         g_signal_emit (G_OBJECT(self), 
737                        signals[HEADER_ACTIVATED_SIGNAL], 
738                        0, header);
739
740         return TRUE;
741 }
742
743
744 static void
745 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
746 {
747         GtkTreeModel *model;
748         TnyHeader *header;
749         GtkTreeIter iter;
750         ModestHeaderView *self;
751         ModestHeaderViewPrivate *priv;
752         
753         g_return_if_fail (sel);
754         g_return_if_fail (user_data);
755         
756         self = MODEST_HEADER_VIEW (user_data);
757         priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);    
758         
759         if (!gtk_tree_selection_get_selected (sel, &model, &iter)) 
760                 return; /* msg was _un_selected */
761
762         gtk_tree_model_get (model, &iter,
763                             TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
764                             &header, -1);
765
766         /* Emit signal */
767         g_signal_emit (G_OBJECT(self), 
768                        signals[HEADER_SELECTED_SIGNAL], 
769                        0, header);
770 }
771
772
773 /* PROTECTED method. It's useful when we want to force a given
774    selection to reload a msg. For example if we have selected a header
775    in offline mode, when Modest become online, we want to reload the
776    message automatically without an user click over the header */
777 void 
778 _modest_header_view_change_selection (GtkTreeSelection *selection,
779                                       gpointer user_data)
780 {
781         g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
782         g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
783
784         on_selection_changed (selection, user_data);
785 }
786
787
788 static gint
789 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
790           gpointer user_data)
791 {
792         gint col_id;
793         gint t1, t2;
794         gint val1, val2;
795         gchar *s1, *s2;
796         gint cmp;
797         
798         static int counter = 0;
799         col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_COLUMN));
800         
801         if (!(++counter % 100)) {
802                 GObject *header_view = g_object_get_data(G_OBJECT(user_data),
803                                                          MODEST_HEADER_VIEW_PTR);
804                 g_signal_emit (header_view,
805                                signals[STATUS_UPDATE_SIGNAL],
806                                0, _("Sorting..."), 0, 0);
807         }       
808         switch (col_id) {
809
810                 /* first one, we decide based on the time */
811         case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
812         case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
813
814                 gtk_tree_model_get (tree_model, iter1,
815                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
816                                     &t1,-1);
817                 gtk_tree_model_get (tree_model, iter2,
818                                     TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
819                                     &t2,-1);
820                 return t1 - t2;
821
822         case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
823         case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
824                 gtk_tree_model_get (tree_model, iter1,
825                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
826                                     &t1,-1);
827                 gtk_tree_model_get (tree_model, iter2,
828                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
829                                     &t2,-1);
830                 return t1 - t2;
831
832                 
833                 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
834                 /* FIXME: what about received-date? */
835         case MODEST_HEADER_VIEW_COLUMN_SUBJECT: {
836
837                 gtk_tree_model_get (tree_model, iter1,
838                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
839                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
840                                     -1);
841                 gtk_tree_model_get (tree_model, iter2,
842                                     TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
843                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
844                                     -1);
845
846                 /* the prefix ('Re:', 'Fwd:' etc.) we ignore */ 
847                 cmp = modest_text_utils_utf8_strcmp (s1 + modest_text_utils_get_subject_prefix_len(s1),
848                                                      s2 + modest_text_utils_get_subject_prefix_len(s2),
849                                                      TRUE);
850                 g_free (s1);
851                 g_free (s2);
852                 
853                 return cmp ? cmp : t1 - t2;
854         }
855                 
856         case MODEST_HEADER_VIEW_COLUMN_FROM:
857                 
858                 gtk_tree_model_get (tree_model, iter1,
859                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
860                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
861                                     -1);
862                 gtk_tree_model_get (tree_model, iter2,
863                                     TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
864                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
865                                     -1);
866                 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
867                 g_free (s1);
868                 g_free (s2);
869                 
870                 return cmp ? cmp : t1 - t2;
871                 
872         case MODEST_HEADER_VIEW_COLUMN_TO: 
873                 
874                 gtk_tree_model_get (tree_model, iter1,
875                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1,
876                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
877                                     -1);
878                 gtk_tree_model_get (tree_model, iter2,
879                                     TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2,
880                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
881                                     -1);
882                 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
883                 g_free (s1);
884                 g_free (s2);
885                 
886                 return cmp ? cmp : t1 - t2;
887
888         case MODEST_HEADER_VIEW_COLUMN_ATTACH:
889
890                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
891                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
892                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
893                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
894                 
895                 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
896                         (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
897
898                 return cmp ? cmp : t1 - t2;
899                 
900         case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
901                 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
902                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
903                 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
904                                     TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
905                 cmp =  (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN);
906
907                 return cmp ? cmp : t1 - t2;
908
909         default:
910                 return &iter1 - &iter2; /* oughhhh  */
911         }
912 }
913
914 static const GtkTargetEntry drag_types[] =
915 {
916         { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, 0 }
917 };
918
919 static void
920 drag_data_get_cb (GtkWidget *widget, 
921                   GdkDragContext *context, 
922                   GtkSelectionData *selection_data, 
923                   guint info, 
924                   guint time, 
925                   gpointer data)
926 {
927         GtkTreeSelection *selection;
928         GtkTreeModel *model;
929         GtkTreeIter iter;
930         GtkTreePath *source_row;
931
932         selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
933         gtk_tree_selection_get_selected (selection, &model, &iter);
934         source_row = gtk_tree_model_get_path (model, &iter);
935
936         gtk_tree_set_row_drag_data (selection_data,
937                                     model,
938                                     source_row);
939
940         gtk_tree_path_free (source_row);
941 }
942
943 static void
944 setup_drag_and_drop (GtkTreeView *self)
945 {
946         gtk_drag_source_set (GTK_WIDGET (self),
947                              GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
948                              drag_types,
949                              G_N_ELEMENTS (drag_types),
950                              GDK_ACTION_MOVE);
951
952         gtk_signal_connect(GTK_OBJECT (self),
953                            "drag_data_get",
954                            GTK_SIGNAL_FUNC(drag_data_get_cb),
955                            NULL);
956 }