1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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.
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.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
36 #include <modest-header-view.h>
37 #include <modest-header-view-priv.h>
38 #include <modest-dnd.h>
39 #include <modest-tny-folder.h>
41 #include <modest-marshal.h>
42 #include <modest-text-utils.h>
43 #include <modest-icon-names.h>
44 #include <modest-runtime.h>
45 #include "modest-platform.h"
47 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
48 static void modest_header_view_init (ModestHeaderView *obj);
49 static void modest_header_view_finalize (GObject *obj);
51 static gboolean on_header_clicked (GtkWidget *widget,
52 GdkEventButton *event,
55 static gint cmp_rows (GtkTreeModel *tree_model,
60 static void on_selection_changed (GtkTreeSelection *sel,
63 static void setup_drag_and_drop (GtkTreeView *self);
65 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
67 static gboolean on_focus_in (GtkWidget *sef,
71 static void folder_monitor_update (TnyFolderObserver *self,
72 TnyFolderChange *change);
74 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
76 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
77 struct _ModestHeaderViewPrivate {
79 ModestHeaderViewStyle style;
81 TnyFolderMonitor *monitor;
82 GMutex *observers_lock;
84 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
85 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
90 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
91 MODEST_TYPE_HEADER_VIEW, \
92 ModestHeaderViewPrivate))
96 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
99 HEADER_SELECTED_SIGNAL,
100 HEADER_ACTIVATED_SIGNAL,
101 ITEM_NOT_FOUND_SIGNAL,
102 STATUS_UPDATE_SIGNAL,
107 static GObjectClass *parent_class = NULL;
109 /* uncomment the following if you have defined any signals */
110 static guint signals[LAST_SIGNAL] = {0};
113 modest_header_view_get_type (void)
115 static GType my_type = 0;
117 static const GTypeInfo my_info = {
118 sizeof(ModestHeaderViewClass),
119 NULL, /* base init */
120 NULL, /* base finalize */
121 (GClassInitFunc) modest_header_view_class_init,
122 NULL, /* class finalize */
123 NULL, /* class data */
124 sizeof(ModestHeaderView),
126 (GInstanceInitFunc) modest_header_view_init,
130 static const GInterfaceInfo tny_folder_observer_info =
132 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
133 NULL, /* interface_finalize */
134 NULL /* interface_data */
136 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
140 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
141 &tny_folder_observer_info);
149 modest_header_view_class_init (ModestHeaderViewClass *klass)
151 GObjectClass *gobject_class;
152 gobject_class = (GObjectClass*) klass;
154 parent_class = g_type_class_peek_parent (klass);
155 gobject_class->finalize = modest_header_view_finalize;
157 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
159 signals[HEADER_SELECTED_SIGNAL] =
160 g_signal_new ("header_selected",
161 G_TYPE_FROM_CLASS (gobject_class),
163 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
165 g_cclosure_marshal_VOID__POINTER,
166 G_TYPE_NONE, 1, G_TYPE_POINTER);
168 signals[HEADER_ACTIVATED_SIGNAL] =
169 g_signal_new ("header_activated",
170 G_TYPE_FROM_CLASS (gobject_class),
172 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
174 g_cclosure_marshal_VOID__POINTER,
175 G_TYPE_NONE, 1, G_TYPE_POINTER);
178 signals[ITEM_NOT_FOUND_SIGNAL] =
179 g_signal_new ("item_not_found",
180 G_TYPE_FROM_CLASS (gobject_class),
182 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
184 g_cclosure_marshal_VOID__INT,
185 G_TYPE_NONE, 1, G_TYPE_INT);
187 signals[STATUS_UPDATE_SIGNAL] =
188 g_signal_new ("status_update",
189 G_TYPE_FROM_CLASS (gobject_class),
191 G_STRUCT_OFFSET (ModestHeaderViewClass,status_update),
193 modest_marshal_VOID__STRING_INT_INT,
194 G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
198 tny_folder_observer_init (TnyFolderObserverIface *klass)
200 klass->update_func = folder_monitor_update;
203 static GtkTreeViewColumn*
204 get_new_column (const gchar *name, GtkCellRenderer *renderer,
205 gboolean resizable, gint sort_col_id, gboolean show_as_text,
206 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
208 GtkTreeViewColumn *column;
210 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
211 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
213 gtk_tree_view_column_set_resizable (column, resizable);
215 gtk_tree_view_column_set_expand (column, TRUE);
218 gtk_tree_view_column_add_attribute (column, renderer, "text",
220 if (sort_col_id >= 0)
221 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
223 gtk_tree_view_column_set_sort_indicator (column, FALSE);
224 gtk_tree_view_column_set_reorderable (column, TRUE);
227 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
236 remove_all_columns (ModestHeaderView *obj)
238 GList *columns, *cursor;
240 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
242 for (cursor = columns; cursor; cursor = cursor->next)
243 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
244 GTK_TREE_VIEW_COLUMN(cursor->data));
245 g_list_free (columns);
249 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
251 GtkTreeModel *sortable;
252 GtkTreeViewColumn *column=NULL;
253 GtkTreeSelection *selection = NULL;
254 GtkCellRenderer *renderer_msgtype,*renderer_header,
255 *renderer_attach, *renderer_comptact_flag,
256 *renderer_compact_date;
257 ModestHeaderViewPrivate *priv;
260 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
262 /* FIXME: check whether these renderers need to be freed */
263 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
264 renderer_attach = gtk_cell_renderer_pixbuf_new ();
265 renderer_header = gtk_cell_renderer_text_new ();
266 renderer_comptact_flag = gtk_cell_renderer_pixbuf_new ();
267 renderer_compact_date = gtk_cell_renderer_text_new ();
269 g_object_set(G_OBJECT(renderer_header),
270 "ellipsize", PANGO_ELLIPSIZE_END,
272 g_object_set(G_OBJECT(renderer_compact_date),
276 remove_all_columns (self);
278 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
279 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
280 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
282 /* Add new columns */
283 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
284 ModestHeaderViewColumn col =
285 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
287 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
288 g_printerr ("modest: invalid column %d in column list\n", col);
294 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
295 column = get_new_column (_("M"), renderer_msgtype, FALSE,
296 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
298 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
300 gtk_tree_view_column_set_fixed_width (column, 45);
303 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
304 column = get_new_column (_("A"), renderer_attach, FALSE,
305 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
307 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
309 gtk_tree_view_column_set_fixed_width (column, 45);
313 case MODEST_HEADER_VIEW_COLUMN_FROM:
314 column = get_new_column (_("From"), renderer_header, TRUE,
315 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
317 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
318 GINT_TO_POINTER(TRUE));
321 case MODEST_HEADER_VIEW_COLUMN_TO:
322 column = get_new_column (_("To"), renderer_header, TRUE,
323 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
325 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
326 GINT_TO_POINTER(FALSE));
329 case MODEST_HEADER_VIEW_COLUMN_COMPACT_FLAG:
330 column = get_new_column (_("F"), renderer_comptact_flag, FALSE,
331 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
333 (GtkTreeCellDataFunc)_modest_header_view_compact_flag_cell_data,
335 gtk_tree_view_column_set_fixed_width (column, 45);
338 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
339 column = get_new_column (_("Header"), renderer_header, TRUE,
340 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
342 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
343 GINT_TO_POINTER(TRUE));
346 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
347 column = get_new_column (_("Header"), renderer_header, TRUE,
348 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
350 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
351 GINT_TO_POINTER(FALSE));
355 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
356 column = get_new_column (_("Subject"), renderer_header, TRUE,
357 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
359 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
363 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
364 column = get_new_column (_("Received"), renderer_header, TRUE,
365 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
367 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
368 GINT_TO_POINTER(TRUE));
371 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
372 column = get_new_column (_("Sent"), renderer_header, TRUE,
373 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
375 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
376 GINT_TO_POINTER(FALSE));
379 case MODEST_HEADER_VIEW_COLUMN_COMPACT_RECEIVED_DATE:
380 column = get_new_column (_("Received"), renderer_compact_date, FALSE,
381 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
383 (GtkTreeCellDataFunc)_modest_header_view_compact_date_cell_data,
384 GINT_TO_POINTER(TRUE));
385 gtk_tree_view_column_set_fixed_width (column, 130);
388 case MODEST_HEADER_VIEW_COLUMN_COMPACT_SENT_DATE:
389 column = get_new_column (_("Sent"), renderer_compact_date, FALSE,
390 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
392 (GtkTreeCellDataFunc)_modest_header_view_compact_date_cell_data,
393 GINT_TO_POINTER(FALSE));
394 gtk_tree_view_column_set_fixed_width (column, 130);
396 case MODEST_HEADER_VIEW_COLUMN_SIZE:
397 column = get_new_column (_("Size"), renderer_header, TRUE,
398 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
400 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
403 case MODEST_HEADER_VIEW_COLUMN_STATUS:
404 column = get_new_column (_("Status"), renderer_compact_date, TRUE,
405 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
407 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
412 g_return_val_if_reached(FALSE);
416 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
418 (GtkTreeIterCompareFunc) cmp_rows,
422 /* we keep the column id around */
423 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
424 GINT_TO_POINTER(col));
426 /* we need this ptr when sorting the rows */
427 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
429 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
436 modest_header_view_init (ModestHeaderView *obj)
438 ModestHeaderViewPrivate *priv;
441 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
445 priv->monitor = NULL;
446 priv->observers_lock = g_mutex_new ();
448 /* Sort parameters */
449 for (j=0; j < 2; j++) {
450 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
451 priv->sort_colid[j][i] = -1;
452 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
456 setup_drag_and_drop (GTK_TREE_VIEW (obj));
460 modest_header_view_finalize (GObject *obj)
462 ModestHeaderView *self;
463 ModestHeaderViewPrivate *priv;
465 self = MODEST_HEADER_VIEW(obj);
466 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
468 g_mutex_lock (priv->observers_lock);
470 tny_folder_monitor_stop (priv->monitor);
471 g_object_unref (G_OBJECT (priv->monitor));
473 g_mutex_unlock (priv->observers_lock);
474 g_mutex_free (priv->observers_lock);
477 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
478 g_object_unref (G_OBJECT (priv->folder));
482 G_OBJECT_CLASS(parent_class)->finalize (obj);
487 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
490 GtkTreeSelection *sel;
491 ModestHeaderView *self;
492 ModestHeaderViewPrivate *priv;
494 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
497 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
498 self = MODEST_HEADER_VIEW(obj);
499 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
501 modest_header_view_set_style (self, style);
502 modest_header_view_set_folder (self, NULL);
504 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
505 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
506 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
508 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
509 TRUE); /* alternating row colors */
510 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
512 g_signal_connect (sel, "changed",
513 G_CALLBACK(on_selection_changed), self);
515 g_signal_connect (self, "button-press-event",
516 G_CALLBACK(on_header_clicked), NULL);
518 g_signal_connect (self, "focus-in-event",
519 G_CALLBACK(on_focus_in), NULL);
521 return GTK_WIDGET(self);
526 modest_header_view_get_selected_headers (ModestHeaderView *self)
528 GtkTreeSelection *sel;
529 ModestHeaderViewPrivate *priv;
530 TnyList *header_list = NULL;
532 GList *list, *tmp = NULL;
533 GtkTreeModel *tree_model = NULL;
536 g_return_val_if_fail (self, NULL);
538 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
540 /* Get selected rows */
541 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
542 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
545 header_list = tny_simple_list_new();
547 list = g_list_reverse (list);
550 /* get header from selection */
551 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
552 gtk_tree_model_get (tree_model, &iter,
553 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
555 /* Prepend to list */
556 tny_list_prepend (header_list, G_OBJECT (header));
557 g_object_unref (G_OBJECT (header));
559 tmp = g_list_next (tmp);
562 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
569 /* scroll our list view so the selected item is visible */
571 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
573 #ifdef MODEST_PLATFORM_GNOME
575 GtkTreePath *selected_path;
576 GtkTreePath *start, *end;
580 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
581 selected_path = gtk_tree_model_get_path (model, iter);
583 start = gtk_tree_path_new ();
584 end = gtk_tree_path_new ();
586 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
588 if (gtk_tree_path_compare (selected_path, start) < 0 ||
589 gtk_tree_path_compare (end, selected_path) < 0)
590 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
591 selected_path, NULL, TRUE,
594 gtk_tree_path_free (selected_path);
595 gtk_tree_path_free (start);
596 gtk_tree_path_free (end);
598 #endif /* MODEST_PLATFORM_GNOME */
603 modest_header_view_select_next (ModestHeaderView *self)
605 GtkTreeSelection *sel;
610 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
611 path = get_selected_row (GTK_TREE_VIEW(self), &model);
612 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
613 /* Unselect previous path */
614 gtk_tree_selection_unselect_path (sel, path);
616 /* Move path down and selects new one */
617 if (gtk_tree_model_iter_next (model, &iter)) {
618 gtk_tree_selection_select_iter (sel, &iter);
619 scroll_to_selected (self, &iter, FALSE);
621 gtk_tree_path_free(path);
627 modest_header_view_select_prev (ModestHeaderView *self)
629 GtkTreeSelection *sel;
634 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
635 path = get_selected_row (GTK_TREE_VIEW(self), &model);
636 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
637 /* Unselect previous path */
638 gtk_tree_selection_unselect_path (sel, path);
641 if (gtk_tree_path_prev (path)) {
642 gtk_tree_model_get_iter (model, &iter, path);
644 /* Select the new one */
645 gtk_tree_selection_select_iter (sel, &iter);
646 scroll_to_selected (self, &iter, TRUE);
649 gtk_tree_path_free (path);
654 modest_header_view_get_columns (ModestHeaderView *self)
656 g_return_val_if_fail (self, FALSE);
657 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
662 modest_header_view_is_empty (ModestHeaderView *self)
664 g_return_val_if_fail (self, FALSE);
665 return FALSE; /* FIXME */
670 modest_header_view_set_style (ModestHeaderView *self,
671 ModestHeaderViewStyle style)
673 ModestHeaderViewPrivate *priv;
674 gboolean show_col_headers = FALSE;
675 ModestHeaderViewStyle old_style;
677 g_return_val_if_fail (self, FALSE);
678 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
681 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
682 if (priv->style == style)
683 return TRUE; /* nothing to do */
686 case MODEST_HEADER_VIEW_STYLE_DETAILS:
687 show_col_headers = TRUE;
689 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
692 g_return_val_if_reached (FALSE);
694 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
695 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
697 old_style = priv->style;
704 ModestHeaderViewStyle
705 modest_header_view_get_style (ModestHeaderView *self)
707 g_return_val_if_fail (self, FALSE);
708 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
712 * This function sets a sortable model in the header view. It's just
713 * used for developing purposes, because it only does a
714 * gtk_tree_view_set_model
717 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
719 GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
721 if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) {
722 GtkTreeModel *old_model;
723 ModestHeaderViewPrivate *priv;
725 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
726 old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort));
729 gtk_tree_view_set_model (header_view, model);
731 gtk_tree_view_set_model (header_view, model);
737 modest_header_view_get_folder (ModestHeaderView *self)
739 ModestHeaderViewPrivate *priv;
740 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
743 g_object_ref (priv->folder);
749 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
753 ModestHeaderViewPrivate *priv;
754 GList *cols, *cursor;
755 GtkTreeModel *sortable;
757 GtkSortType sort_type;
759 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
761 headers = TNY_LIST (tny_gtk_header_list_model_new ());
763 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
766 /* Add IDLE observer (monitor) and another folder observer for
767 new messages (self) */
768 g_mutex_lock (priv->observers_lock);
770 tny_folder_monitor_stop (priv->monitor);
771 g_object_unref (G_OBJECT (priv->monitor));
773 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
774 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
775 tny_folder_monitor_start (priv->monitor);
776 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (self));
777 g_mutex_unlock (priv->observers_lock);
779 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
780 g_object_unref (G_OBJECT (headers));
782 /* install our special sorting functions */
783 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
785 gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data),
786 MODEST_HEADER_VIEW_COLUMN));
787 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
789 (GtkTreeIterCompareFunc) cmp_rows,
791 cursor = g_list_next(cursor);
794 /* Restore sort column id */
796 type = modest_tny_folder_guess_folder_type (folder);
797 sort_colid = modest_header_view_get_sort_column_id (self, type);
798 sort_type = modest_header_view_get_sort_type (self, type);
799 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
805 modest_header_view_set_model (GTK_TREE_VIEW (self), sortable);
806 g_object_unref (G_OBJECT (sortable));
813 modest_header_view_sort_by_column_id (ModestHeaderView *self,
815 GtkSortType sort_type)
817 ModestHeaderViewPrivate *priv = NULL;
818 GtkTreeModel *sortable = NULL;
821 /* Get model and private data */
822 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
823 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
825 /* Sort tree model */
826 type = modest_tny_folder_guess_folder_type (priv->folder);
827 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
830 /* Store new sort parameters */
831 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
833 /* Save GConf parameters */
834 /* modest_widget_memory_save (modest_runtime_get_conf(), */
835 /* G_OBJECT(self), "header-view"); */
840 modest_header_view_set_sort_params (ModestHeaderView *self,
842 GtkSortType sort_type,
845 ModestHeaderViewPrivate *priv;
846 ModestHeaderViewStyle style;
848 style = modest_header_view_get_style (self);
849 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
851 priv->sort_colid[style][type] = sort_colid;
852 priv->sort_type[style][type] = sort_type;
856 modest_header_view_get_sort_column_id (ModestHeaderView *self,
859 ModestHeaderViewPrivate *priv;
860 ModestHeaderViewStyle style;
862 style = modest_header_view_get_style (self);
863 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
865 return priv->sort_colid[style][type];
869 modest_header_view_get_sort_type (ModestHeaderView *self,
872 ModestHeaderViewPrivate *priv;
873 ModestHeaderViewStyle style;
875 style = modest_header_view_get_style (self);
876 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
878 return priv->sort_type[style][type];
882 modest_header_view_set_folder (ModestHeaderView *self, TnyFolder *folder)
884 ModestHeaderViewPrivate *priv;
886 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
889 g_mutex_lock (priv->observers_lock);
890 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
891 g_object_unref (priv->folder);
893 g_mutex_unlock (priv->observers_lock);
897 ModestMailOperation *mail_op;
899 /* Set folder in the model */
900 modest_header_view_set_folder_intern (self, folder);
902 /* Pick my reference. Nothing to do with the mail operation */
903 priv->folder = g_object_ref (folder);
905 /* no message selected */
906 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
908 /* Create the mail operation */
909 mail_op = modest_mail_operation_new (MODEST_MAIL_OPERATION_ID_RECEIVE, NULL);
910 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
913 /* Refresh the folder asynchronously */
914 modest_mail_operation_refresh_folder (mail_op, folder);
917 g_object_unref (mail_op);
920 g_mutex_lock (priv->observers_lock);
923 tny_folder_monitor_stop (priv->monitor);
924 g_object_unref (G_OBJECT (priv->monitor));
925 priv->monitor = NULL;
927 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
929 g_mutex_unlock (priv->observers_lock);
934 on_header_clicked (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
936 ModestHeaderView *self = NULL;
937 ModestHeaderViewPrivate *priv = NULL;
938 GtkTreePath *path = NULL;
940 GtkTreeModel *model = NULL;
942 /* ignore everything but doubleclick */
943 if (event->type != GDK_2BUTTON_PRESS)
946 self = MODEST_HEADER_VIEW (widget);
947 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
949 path = get_selected_row (GTK_TREE_VIEW(self), &model);
950 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
953 /* get the first selected item */
954 gtk_tree_model_get (model, &iter,
955 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
958 g_signal_emit (G_OBJECT(self),
959 signals[HEADER_ACTIVATED_SIGNAL],
963 g_object_unref (G_OBJECT (header));
964 gtk_tree_path_free(path);
971 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
975 GtkTreePath *path = NULL;
977 ModestHeaderView *self;
978 ModestHeaderViewPrivate *priv;
980 g_return_if_fail (sel);
981 g_return_if_fail (user_data);
983 self = MODEST_HEADER_VIEW (user_data);
984 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
986 path = get_selected_row (GTK_TREE_VIEW(self), &model);
987 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
988 return; /* msg was _un_selected */
990 gtk_tree_model_get (model, &iter,
991 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
995 g_signal_emit (G_OBJECT(self),
996 signals[HEADER_SELECTED_SIGNAL],
999 g_object_unref (G_OBJECT (header));
1000 gtk_tree_path_free(path);
1004 /* PROTECTED method. It's useful when we want to force a given
1005 selection to reload a msg. For example if we have selected a header
1006 in offline mode, when Modest become online, we want to reload the
1007 message automatically without an user click over the header */
1009 _modest_header_view_change_selection (GtkTreeSelection *selection,
1012 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1013 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1015 on_selection_changed (selection, user_data);
1020 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1029 static int counter = 0;
1031 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1032 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_COLUMN));
1034 if (!(++counter % 100)) {
1035 GObject *header_view = g_object_get_data(G_OBJECT(user_data),
1036 MODEST_HEADER_VIEW_PTR);
1037 g_signal_emit (header_view,
1038 signals[STATUS_UPDATE_SIGNAL],
1039 0, _("Sorting..."), 0, 0);
1042 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
1043 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
1044 sort_colid = gtk_tree_view_column_get_sort_column_id (GTK_TREE_VIEW_COLUMN(user_data));
1045 gtk_tree_model_get (tree_model, iter1,
1047 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
1049 gtk_tree_model_get (tree_model, iter2,
1051 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
1053 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
1057 return cmp ? cmp : t1 - t2;
1061 case MODEST_HEADER_VIEW_COLUMN_COMPACT_RECEIVED_DATE:
1062 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
1064 gtk_tree_model_get (tree_model, iter1,
1065 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
1067 gtk_tree_model_get (tree_model, iter2,
1068 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
1072 case MODEST_HEADER_VIEW_COLUMN_COMPACT_SENT_DATE:
1073 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
1074 gtk_tree_model_get (tree_model, iter1,
1075 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
1077 gtk_tree_model_get (tree_model, iter2,
1078 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
1083 /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */
1084 /* FIXME: what about received-date? */
1085 case MODEST_HEADER_VIEW_COLUMN_SUBJECT: {
1087 gtk_tree_model_get (tree_model, iter1,
1088 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1,
1089 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
1091 gtk_tree_model_get (tree_model, iter2,
1092 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2,
1093 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
1096 /* the prefix ('Re:', 'Fwd:' etc.) we ignore */
1097 cmp = modest_text_utils_utf8_strcmp (s1 + modest_text_utils_get_subject_prefix_len(s1),
1098 s2 + modest_text_utils_get_subject_prefix_len(s2),
1103 return cmp ? cmp : t1 - t2;
1106 case MODEST_HEADER_VIEW_COLUMN_FROM:
1108 gtk_tree_model_get (tree_model, iter1,
1109 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1,
1110 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
1112 gtk_tree_model_get (tree_model, iter2,
1113 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2,
1114 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
1116 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
1120 return cmp ? cmp : t1 - t2;
1122 case MODEST_HEADER_VIEW_COLUMN_TO:
1124 gtk_tree_model_get (tree_model, iter1,
1125 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1,
1126 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,
1128 gtk_tree_model_get (tree_model, iter2,
1129 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2,
1130 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,
1132 cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE);
1136 return cmp ? cmp : t1 - t2;
1138 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
1140 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1141 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1142 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1143 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1145 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1146 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1148 return cmp ? cmp : t1 - t2;
1150 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
1151 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1152 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1153 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1154 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1155 cmp = (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN);
1157 return cmp ? cmp : t1 - t2;
1159 case MODEST_HEADER_VIEW_COLUMN_COMPACT_FLAG:
1160 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1161 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1162 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1163 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1165 int flag_sort = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1166 switch (flag_sort) {
1167 case TNY_HEADER_FLAG_ATTACHMENTS:
1168 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1169 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1171 case TNY_HEADER_FLAG_PRIORITY:
1172 cmp = (val1 & TNY_HEADER_FLAG_PRIORITY) -
1173 (val2 & TNY_HEADER_FLAG_PRIORITY);
1176 cmp = (val1 & TNY_HEADER_FLAG_PRIORITY) -
1177 (val2 & TNY_HEADER_FLAG_PRIORITY);
1180 return cmp ? cmp : t1 - t2;
1182 return &iter1 - &iter2; /* oughhhh */
1186 /* Drag and drop stuff */
1188 drag_data_get_cb (GtkWidget *widget, GdkDragContext *context,
1189 GtkSelectionData *selection_data,
1190 guint info, guint time, gpointer data)
1192 GtkTreeModel *model = NULL;
1194 GtkTreePath *source_row = NULL;
1196 source_row = get_selected_row (GTK_TREE_VIEW (widget), &model);
1197 if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return;
1200 case MODEST_HEADER_ROW:
1201 gtk_tree_set_row_drag_data (selection_data, model, source_row);
1205 gtk_tree_model_get (model, &iter,
1206 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr,
1209 g_object_unref (G_OBJECT(hdr));
1214 g_message ("%s: default switch case.", __FUNCTION__);
1217 gtk_tree_path_free (source_row);
1220 /* Header view drag types */
1221 const GtkTargetEntry header_view_drag_types[] = {
1222 { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW },
1223 { "text/uri-list", 0, MODEST_MSG },
1227 setup_drag_and_drop (GtkTreeView *self)
1229 gtk_drag_source_set (GTK_WIDGET (self),
1231 header_view_drag_types,
1232 G_N_ELEMENTS (header_view_drag_types),
1233 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1235 g_signal_connect(G_OBJECT (self), "drag_data_get",
1236 G_CALLBACK(drag_data_get_cb), NULL);
1239 static GtkTreePath *
1240 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1242 GtkTreePath *path = NULL;
1243 GtkTreeSelection *sel = NULL;
1246 sel = gtk_tree_view_get_selection(self);
1247 rows = gtk_tree_selection_get_selected_rows (sel, model);
1249 if ((rows == NULL) || (g_list_length(rows) != 1))
1252 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1257 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1264 * This function moves the tree view scroll to the current selected
1265 * row when the widget grabs the focus
1268 on_focus_in (GtkWidget *self,
1269 GdkEventFocus *event,
1272 GtkTreeSelection *selection;
1273 GtkTreeModel *model;
1274 GList *selected = NULL;
1275 GtkTreePath *selected_path = NULL;
1277 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1281 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1282 /* If none selected yet, pick the first one */
1283 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1287 /* Return if the model is empty */
1288 if (!gtk_tree_model_get_iter_first (model, &iter))
1291 path = gtk_tree_model_get_path (model, &iter);
1292 gtk_tree_selection_select_path (selection, path);
1293 gtk_tree_path_free (path);
1296 /* Need to get the all the rows because is selection multiple */
1297 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1298 selected_path = (GtkTreePath *) selected->data;
1300 /* Check if we need to scroll */
1301 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1302 GtkTreePath *start_path = NULL;
1303 GtkTreePath *end_path = NULL;
1304 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1308 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1309 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1311 /* Scroll to first path */
1312 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1320 #endif /* GTK_CHECK_VERSION */
1323 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1324 g_list_free (selected);
1333 folder_monitor_update (TnyFolderObserver *self,
1334 TnyFolderChange *change)
1336 ModestHeaderViewPrivate *priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1337 TnyFolderChangeChanged changed;
1339 g_mutex_lock (priv->observers_lock);
1341 changed = tny_folder_change_get_changed (change);
1343 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) {
1344 /* TnyIterator *iter; */
1345 /* TnyList *list; */
1346 /* /\* The added headers *\/ */
1347 /* list = tny_simple_list_new (); */
1348 /* tny_folder_change_get_added_headers (change, list); */
1349 /* iter = tny_list_create_iterator (list); */
1350 /* while (!tny_iterator_is_done (iter)) */
1352 /* TnyHeader *header = TNY_HEADER (tny_iterator_get_current (iter)); */
1353 /* g_object_unref (G_OBJECT (header)); */
1354 /* tny_iterator_next (iter); */
1356 /* g_object_unref (G_OBJECT (iter)); */
1357 /* g_object_unref (G_OBJECT (list)); */
1358 modest_platform_on_new_msg ();
1361 g_mutex_unlock (priv->observers_lock);