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>
34 #include <tny-folder-change.h>
37 #include <modest-header-view.h>
38 #include <modest-header-view-priv.h>
39 #include <modest-dnd.h>
40 #include <modest-tny-folder.h>
42 #include <modest-main-window.h>
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>
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);
57 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
58 GtkTreeViewColumn *column, gpointer userdata);
60 static gint cmp_rows (GtkTreeModel *tree_model,
65 static gint cmp_subject_rows (GtkTreeModel *tree_model,
70 static gboolean filter_row (GtkTreeModel *model,
74 static void on_account_removed (TnyAccountStore *self,
78 static void on_selection_changed (GtkTreeSelection *sel,
81 static void setup_drag_and_drop (GtkTreeView *self);
83 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
85 static gboolean on_focus_in (GtkWidget *sef,
89 static gboolean on_focus_out (GtkWidget *self,
93 static void folder_monitor_update (TnyFolderObserver *self,
94 TnyFolderChange *change);
96 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
98 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
100 static void _clear_hidding_filter (ModestHeaderView *header_view);
102 static void modest_header_view_notify_observers(
103 ModestHeaderView *header_view,
105 const gchar *tny_folder_id);
107 static gboolean modest_header_view_on_expose_event(
108 GtkTreeView *header_view,
109 GdkEventExpose *event,
113 HEADER_VIEW_NON_EMPTY,
118 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
119 struct _ModestHeaderViewPrivate {
121 ModestHeaderViewStyle style;
123 TnyFolderMonitor *monitor;
124 GMutex *observers_lock;
126 /*header-view-observer observer*/
127 GMutex *observer_list_lock;
128 GSList *observer_list;
130 /* not unref this object, its a singlenton */
131 ModestEmailClipboard *clipboard;
133 /* Filter tree model */
137 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
138 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
140 gulong selection_changed_handler;
141 gulong acc_removed_handler;
143 HeaderViewStatus status;
144 GList *drag_begin_cached_selected_rows;
147 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
148 struct _HeadersCountChangedHelper {
149 ModestHeaderView *self;
150 TnyFolderChange *change;
154 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
155 MODEST_TYPE_HEADER_VIEW, \
156 ModestHeaderViewPrivate))
160 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
163 HEADER_SELECTED_SIGNAL,
164 HEADER_ACTIVATED_SIGNAL,
165 ITEM_NOT_FOUND_SIGNAL,
166 MSG_COUNT_CHANGED_SIGNAL,
171 static GObjectClass *parent_class = NULL;
173 /* uncomment the following if you have defined any signals */
174 static guint signals[LAST_SIGNAL] = {0};
177 modest_header_view_get_type (void)
179 static GType my_type = 0;
181 static const GTypeInfo my_info = {
182 sizeof(ModestHeaderViewClass),
183 NULL, /* base init */
184 NULL, /* base finalize */
185 (GClassInitFunc) modest_header_view_class_init,
186 NULL, /* class finalize */
187 NULL, /* class data */
188 sizeof(ModestHeaderView),
190 (GInstanceInitFunc) modest_header_view_init,
194 static const GInterfaceInfo tny_folder_observer_info =
196 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
197 NULL, /* interface_finalize */
198 NULL /* interface_data */
200 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
204 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
205 &tny_folder_observer_info);
213 modest_header_view_class_init (ModestHeaderViewClass *klass)
215 GObjectClass *gobject_class;
216 gobject_class = (GObjectClass*) klass;
218 parent_class = g_type_class_peek_parent (klass);
219 gobject_class->finalize = modest_header_view_finalize;
220 gobject_class->dispose = modest_header_view_dispose;
222 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
224 signals[HEADER_SELECTED_SIGNAL] =
225 g_signal_new ("header_selected",
226 G_TYPE_FROM_CLASS (gobject_class),
228 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
230 g_cclosure_marshal_VOID__POINTER,
231 G_TYPE_NONE, 1, G_TYPE_POINTER);
233 signals[HEADER_ACTIVATED_SIGNAL] =
234 g_signal_new ("header_activated",
235 G_TYPE_FROM_CLASS (gobject_class),
237 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
239 g_cclosure_marshal_VOID__POINTER,
240 G_TYPE_NONE, 1, G_TYPE_POINTER);
243 signals[ITEM_NOT_FOUND_SIGNAL] =
244 g_signal_new ("item_not_found",
245 G_TYPE_FROM_CLASS (gobject_class),
247 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
249 g_cclosure_marshal_VOID__INT,
250 G_TYPE_NONE, 1, G_TYPE_INT);
252 signals[MSG_COUNT_CHANGED_SIGNAL] =
253 g_signal_new ("msg_count_changed",
254 G_TYPE_FROM_CLASS (gobject_class),
256 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
258 modest_marshal_VOID__POINTER_POINTER,
259 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
263 tny_folder_observer_init (TnyFolderObserverIface *klass)
265 klass->update_func = folder_monitor_update;
268 static GtkTreeViewColumn*
269 get_new_column (const gchar *name, GtkCellRenderer *renderer,
270 gboolean resizable, gint sort_col_id, gboolean show_as_text,
271 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
273 GtkTreeViewColumn *column;
275 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
276 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
278 gtk_tree_view_column_set_resizable (column, resizable);
280 gtk_tree_view_column_set_expand (column, TRUE);
283 gtk_tree_view_column_add_attribute (column, renderer, "text",
285 if (sort_col_id >= 0)
286 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
288 gtk_tree_view_column_set_sort_indicator (column, FALSE);
289 gtk_tree_view_column_set_reorderable (column, TRUE);
292 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
299 remove_all_columns (ModestHeaderView *obj)
301 GList *columns, *cursor;
303 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
305 for (cursor = columns; cursor; cursor = cursor->next)
306 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
307 GTK_TREE_VIEW_COLUMN(cursor->data));
308 g_list_free (columns);
312 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
314 GtkTreeModel *tree_filter, *sortable;
315 GtkTreeViewColumn *column=NULL;
316 GtkTreeSelection *selection = NULL;
317 GtkCellRenderer *renderer_msgtype,*renderer_header,
318 *renderer_attach, *renderer_compact_date_or_status;
319 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
320 *renderer_subject, *renderer_subject_box, *renderer_recpt,
322 ModestHeaderViewPrivate *priv;
323 GtkTreeViewColumn *compact_column = NULL;
326 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
328 /* FIXME: check whether these renderers need to be freed */
329 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
330 renderer_attach = gtk_cell_renderer_pixbuf_new ();
331 renderer_priority = gtk_cell_renderer_pixbuf_new ();
332 renderer_header = gtk_cell_renderer_text_new ();
334 renderer_compact_header = modest_vbox_cell_renderer_new ();
335 renderer_recpt_box = modest_hbox_cell_renderer_new ();
336 renderer_subject_box = modest_hbox_cell_renderer_new ();
337 renderer_recpt = gtk_cell_renderer_text_new ();
338 renderer_subject = gtk_cell_renderer_text_new ();
339 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
341 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
342 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
343 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
344 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
345 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
346 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
347 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
348 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
349 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
350 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
351 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
352 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
353 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
354 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
356 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
357 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
358 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
359 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
360 g_object_set(G_OBJECT(renderer_header),
361 "ellipsize", PANGO_ELLIPSIZE_END,
363 g_object_set (G_OBJECT (renderer_subject),
364 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
366 g_object_set (G_OBJECT (renderer_recpt),
367 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
369 g_object_set(G_OBJECT(renderer_compact_date_or_status),
370 "xalign", 1.0, "yalign", 0.0,
372 g_object_set (G_OBJECT (renderer_priority),
373 "yalign", 1.0, NULL);
374 g_object_set (G_OBJECT (renderer_attach),
375 "yalign", 0.0, NULL);
377 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
378 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
379 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
381 remove_all_columns (self);
383 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
384 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
385 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
386 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
388 /* Add new columns */
389 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
390 ModestHeaderViewColumn col =
391 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
393 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
394 g_printerr ("modest: invalid column %d in column list\n", col);
400 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
401 column = get_new_column (_("M"), renderer_msgtype, FALSE,
402 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
404 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
406 gtk_tree_view_column_set_fixed_width (column, 45);
409 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
410 column = get_new_column (_("A"), renderer_attach, FALSE,
411 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
413 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
415 gtk_tree_view_column_set_fixed_width (column, 45);
419 case MODEST_HEADER_VIEW_COLUMN_FROM:
420 column = get_new_column (_("From"), renderer_header, TRUE,
421 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
423 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
424 GINT_TO_POINTER(TRUE));
427 case MODEST_HEADER_VIEW_COLUMN_TO:
428 column = get_new_column (_("To"), renderer_header, TRUE,
429 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
431 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
432 GINT_TO_POINTER(FALSE));
435 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
436 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
437 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
439 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
440 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
441 compact_column = column;
444 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
445 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
446 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
448 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
449 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
450 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
451 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
452 compact_column = column;
456 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
457 column = get_new_column (_("Subject"), renderer_header, TRUE,
458 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
460 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
464 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
465 column = get_new_column (_("Received"), renderer_header, TRUE,
466 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
468 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
469 GINT_TO_POINTER(TRUE));
472 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
473 column = get_new_column (_("Sent"), renderer_header, TRUE,
474 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
476 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
477 GINT_TO_POINTER(FALSE));
480 case MODEST_HEADER_VIEW_COLUMN_SIZE:
481 column = get_new_column (_("Size"), renderer_header, TRUE,
482 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
484 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
487 case MODEST_HEADER_VIEW_COLUMN_STATUS:
488 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
489 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
491 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
496 g_return_val_if_reached(FALSE);
499 /* we keep the column id around */
500 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
501 GINT_TO_POINTER(col));
503 /* we need this ptr when sorting the rows */
504 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
506 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
510 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
511 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
512 (GtkTreeIterCompareFunc) cmp_rows,
513 compact_column, NULL);
514 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
515 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
516 (GtkTreeIterCompareFunc) cmp_subject_rows,
517 compact_column, NULL);
525 modest_header_view_init (ModestHeaderView *obj)
527 ModestHeaderViewPrivate *priv;
530 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
534 priv->monitor = NULL;
535 priv->observers_lock = g_mutex_new ();
537 priv->status = HEADER_VIEW_INIT;
539 priv->observer_list_lock = g_mutex_new();
540 priv->observer_list = NULL;
542 priv->clipboard = modest_runtime_get_email_clipboard ();
543 priv->hidding_ids = NULL;
544 priv->n_selected = 0;
545 priv->selection_changed_handler = 0;
546 priv->acc_removed_handler = 0;
548 /* Sort parameters */
549 for (j=0; j < 2; j++) {
550 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
551 priv->sort_colid[j][i] = -1;
552 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
556 setup_drag_and_drop (GTK_TREE_VIEW (obj));
560 modest_header_view_dispose (GObject *obj)
562 ModestHeaderView *self;
563 ModestHeaderViewPrivate *priv;
564 GtkTreeSelection *sel;
566 self = MODEST_HEADER_VIEW(obj);
567 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
569 /* Free in the dispose to avoid unref cycles */
571 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
572 g_object_unref (G_OBJECT (priv->folder));
576 /* We need to do this here in the dispose because the
577 selection won't exist when finalizing */
578 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
579 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
580 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
581 priv->selection_changed_handler = 0;
584 G_OBJECT_CLASS(parent_class)->dispose (obj);
588 modest_header_view_finalize (GObject *obj)
590 ModestHeaderView *self;
591 ModestHeaderViewPrivate *priv;
593 self = MODEST_HEADER_VIEW(obj);
594 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
596 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
597 priv->acc_removed_handler)) {
598 g_signal_handler_disconnect (modest_runtime_get_account_store (),
599 priv->acc_removed_handler);
602 /* There is no need to lock because there should not be any
603 * reference to self now. */
604 g_mutex_free(priv->observer_list_lock);
605 g_slist_free(priv->observer_list);
607 g_mutex_lock (priv->observers_lock);
609 tny_folder_monitor_stop (priv->monitor);
610 g_object_unref (G_OBJECT (priv->monitor));
612 g_mutex_unlock (priv->observers_lock);
613 g_mutex_free (priv->observers_lock);
615 /* Clear hidding array created by cut operation */
616 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
618 G_OBJECT_CLASS(parent_class)->finalize (obj);
623 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
626 GtkTreeSelection *sel;
627 ModestHeaderView *self;
628 ModestHeaderViewPrivate *priv;
630 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
633 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
634 self = MODEST_HEADER_VIEW(obj);
635 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
637 modest_header_view_set_style (self, style);
638 /* modest_header_view_set_folder (self, NULL, NULL, NULL); */
640 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
641 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
642 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
644 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
645 TRUE); /* alternating row colors */
647 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
648 priv->selection_changed_handler =
649 g_signal_connect_after (sel, "changed",
650 G_CALLBACK(on_selection_changed), self);
652 g_signal_connect (self, "row-activated",
653 G_CALLBACK (on_header_row_activated), NULL);
655 g_signal_connect (self, "focus-in-event",
656 G_CALLBACK(on_focus_in), NULL);
657 g_signal_connect (self, "focus-out-event",
658 G_CALLBACK(on_focus_out), NULL);
660 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
662 G_CALLBACK (on_account_removed),
665 g_signal_connect (self, "expose-event",
666 G_CALLBACK(modest_header_view_on_expose_event),
669 return GTK_WIDGET(self);
674 modest_header_view_count_selected_headers (ModestHeaderView *self)
676 GtkTreeSelection *sel;
679 g_return_val_if_fail (self, 0);
681 /* Get selection object and check selected rows count */
682 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
683 selected_rows = gtk_tree_selection_count_selected_rows (sel);
685 return selected_rows;
689 modest_header_view_has_selected_headers (ModestHeaderView *self)
691 GtkTreeSelection *sel;
694 g_return_val_if_fail (self, FALSE);
696 /* Get selection object and check selected rows count */
697 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
698 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
705 modest_header_view_get_selected_headers (ModestHeaderView *self)
707 GtkTreeSelection *sel;
708 ModestHeaderViewPrivate *priv;
709 TnyList *header_list = NULL;
711 GList *list, *tmp = NULL;
712 GtkTreeModel *tree_model = NULL;
715 g_return_val_if_fail (self, NULL);
717 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
719 /* Get selected rows */
720 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
721 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
724 header_list = tny_simple_list_new();
726 list = g_list_reverse (list);
729 /* get header from selection */
730 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
731 gtk_tree_model_get (tree_model, &iter,
732 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
734 /* Prepend to list */
735 tny_list_prepend (header_list, G_OBJECT (header));
736 g_object_unref (G_OBJECT (header));
738 tmp = g_list_next (tmp);
741 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
748 /* scroll our list view so the selected item is visible */
750 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
752 #ifdef MODEST_PLATFORM_GNOME
754 GtkTreePath *selected_path;
755 GtkTreePath *start, *end;
759 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
760 selected_path = gtk_tree_model_get_path (model, iter);
762 start = gtk_tree_path_new ();
763 end = gtk_tree_path_new ();
765 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
767 if (gtk_tree_path_compare (selected_path, start) < 0 ||
768 gtk_tree_path_compare (end, selected_path) < 0)
769 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
770 selected_path, NULL, TRUE,
773 gtk_tree_path_free (selected_path);
774 gtk_tree_path_free (start);
775 gtk_tree_path_free (end);
777 #endif /* MODEST_PLATFORM_GNOME */
782 modest_header_view_select_next (ModestHeaderView *self)
784 GtkTreeSelection *sel;
789 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
790 path = get_selected_row (GTK_TREE_VIEW(self), &model);
791 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
792 /* Unselect previous path */
793 gtk_tree_selection_unselect_path (sel, path);
795 /* Move path down and selects new one */
796 if (gtk_tree_model_iter_next (model, &iter)) {
797 gtk_tree_selection_select_iter (sel, &iter);
798 scroll_to_selected (self, &iter, FALSE);
800 gtk_tree_path_free(path);
806 modest_header_view_select_prev (ModestHeaderView *self)
808 GtkTreeSelection *sel;
813 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
814 path = get_selected_row (GTK_TREE_VIEW(self), &model);
815 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
816 /* Unselect previous path */
817 gtk_tree_selection_unselect_path (sel, path);
820 if (gtk_tree_path_prev (path)) {
821 gtk_tree_model_get_iter (model, &iter, path);
823 /* Select the new one */
824 gtk_tree_selection_select_iter (sel, &iter);
825 scroll_to_selected (self, &iter, TRUE);
828 gtk_tree_path_free (path);
833 modest_header_view_get_columns (ModestHeaderView *self)
835 g_return_val_if_fail (self, FALSE);
836 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
842 modest_header_view_set_style (ModestHeaderView *self,
843 ModestHeaderViewStyle style)
845 ModestHeaderViewPrivate *priv;
846 gboolean show_col_headers = FALSE;
847 ModestHeaderViewStyle old_style;
849 g_return_val_if_fail (self, FALSE);
850 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
853 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
854 if (priv->style == style)
855 return TRUE; /* nothing to do */
858 case MODEST_HEADER_VIEW_STYLE_DETAILS:
859 show_col_headers = TRUE;
861 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
864 g_return_val_if_reached (FALSE);
866 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
867 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
869 old_style = priv->style;
876 ModestHeaderViewStyle
877 modest_header_view_get_style (ModestHeaderView *self)
879 g_return_val_if_fail (self, FALSE);
880 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
883 /* This is used to automatically select the first header if the user
884 * has not selected any header yet.
887 modest_header_view_on_expose_event(GtkTreeView *header_view,
888 GdkEventExpose *event,
891 GtkTreeSelection *sel;
893 GtkTreeIter tree_iter;
895 /* I'm invalidating this method because it causes an annoying
896 efect, the focus changes to the header view when selecting
897 a folder in the folder view because of this code and it
898 shouldn't. We need to find another way to set the passive
899 focus on it. Sergio. */
902 model = gtk_tree_view_get_model(header_view);
904 sel = gtk_tree_view_get_selection(header_view);
905 if(!gtk_tree_selection_count_selected_rows(sel))
906 if (gtk_tree_model_get_iter_first(model, &tree_iter))
907 gtk_tree_selection_select_iter(sel, &tree_iter);
913 * This function sets a sortable model in the header view. It's just
914 * used for developing purposes, because it only does a
915 * gtk_tree_view_set_model
918 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
920 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
921 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
922 /* GtkTreeModel *old_model; */
923 /* ModestHeaderViewPrivate *priv; */
924 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
925 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
927 /* /\* Set new model *\/ */
928 /* gtk_tree_view_set_model (header_view, model); */
930 gtk_tree_view_set_model (header_view, model);
934 modest_header_view_get_folder (ModestHeaderView *self)
936 ModestHeaderViewPrivate *priv;
937 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
940 g_object_ref (priv->folder);
946 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
950 ModestHeaderViewPrivate *priv;
951 GList *cols, *cursor;
952 GtkTreeModel *filter_model, *sortable;
954 GtkSortType sort_type;
956 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
958 headers = TNY_LIST (tny_gtk_header_list_model_new ());
960 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
961 folder, FALSE, NULL, NULL, NULL);
963 /* Add IDLE observer (monitor) and another folder observer for
964 new messages (self) */
965 g_mutex_lock (priv->observers_lock);
967 tny_folder_monitor_stop (priv->monitor);
968 g_object_unref (G_OBJECT (priv->monitor));
970 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
971 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
972 tny_folder_monitor_start (priv->monitor);
973 g_mutex_unlock (priv->observers_lock);
975 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
976 g_object_unref (G_OBJECT (headers));
978 /* Init filter_row function to examine empty status */
979 priv->status = HEADER_VIEW_INIT;
981 /* Create a tree model filter to hide and show rows for cut operations */
982 filter_model = gtk_tree_model_filter_new (sortable, NULL);
983 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
987 g_object_unref (G_OBJECT (sortable));
989 /* install our special sorting functions */
990 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
992 /* Restore sort column id */
994 type = modest_tny_folder_guess_folder_type (folder);
995 sort_colid = modest_header_view_get_sort_column_id (self, type);
996 sort_type = modest_header_view_get_sort_type (self, type);
997 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1000 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1001 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1002 (GtkTreeIterCompareFunc) cmp_rows,
1004 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1005 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1006 (GtkTreeIterCompareFunc) cmp_subject_rows,
1011 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1012 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1013 tny_folder_get_id(folder));
1014 g_object_unref (G_OBJECT (filter_model));
1015 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1016 /* g_object_unref (G_OBJECT (sortable)); */
1023 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1025 GtkSortType sort_type)
1027 ModestHeaderViewPrivate *priv = NULL;
1028 GtkTreeModel *tree_filter, *sortable = NULL;
1031 /* Get model and private data */
1032 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1033 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1034 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1035 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1037 /* Sort tree model */
1038 type = modest_tny_folder_guess_folder_type (priv->folder);
1039 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1042 /* Store new sort parameters */
1043 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1045 /* Save GConf parameters */
1046 /* modest_widget_memory_save (modest_runtime_get_conf(), */
1047 /* G_OBJECT(self), "header-view"); */
1052 modest_header_view_set_sort_params (ModestHeaderView *self,
1054 GtkSortType sort_type,
1057 ModestHeaderViewPrivate *priv;
1058 ModestHeaderViewStyle style;
1060 style = modest_header_view_get_style (self);
1061 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1063 priv->sort_colid[style][type] = sort_colid;
1064 priv->sort_type[style][type] = sort_type;
1068 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1071 ModestHeaderViewPrivate *priv;
1072 ModestHeaderViewStyle style;
1074 style = modest_header_view_get_style (self);
1075 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1077 return priv->sort_colid[style][type];
1081 modest_header_view_get_sort_type (ModestHeaderView *self,
1084 ModestHeaderViewPrivate *priv;
1085 ModestHeaderViewStyle style;
1087 style = modest_header_view_get_style (self);
1088 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1090 return priv->sort_type[style][type];
1094 ModestHeaderView *header_view;
1095 RefreshAsyncUserCallback cb;
1100 folder_refreshed_cb (ModestMailOperation *mail_op,
1104 ModestHeaderViewPrivate *priv;
1105 SetFolderHelper *info;
1107 info = (SetFolderHelper*) user_data;
1109 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1113 info->cb (mail_op, folder, info->user_data);
1115 /* Start the folder count changes observer. We do not need it
1116 before the refresh. Note that the monitor could still be
1117 called for this refresh but now we know that the callback
1118 was previously called */
1119 g_mutex_lock (priv->observers_lock);
1120 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1121 g_mutex_unlock (priv->observers_lock);
1128 modest_header_view_set_folder (ModestHeaderView *self,
1130 RefreshAsyncUserCallback callback,
1133 ModestHeaderViewPrivate *priv;
1134 ModestWindowMgr *mgr = NULL;
1135 GObject *source = NULL;
1136 SetFolderHelper *info;
1138 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1141 g_mutex_lock (priv->observers_lock);
1142 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1143 g_object_unref (priv->folder);
1144 priv->folder = NULL;
1145 g_mutex_unlock (priv->observers_lock);
1149 ModestMailOperation *mail_op = NULL;
1150 GtkTreeSelection *selection;
1152 /* Get main window to use it as source of mail operation */
1153 mgr = modest_runtime_get_window_mgr ();
1154 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1156 /* Set folder in the model */
1157 modest_header_view_set_folder_intern (self, folder);
1159 /* Pick my reference. Nothing to do with the mail operation */
1160 priv->folder = g_object_ref (folder);
1162 /* Clear the selection if exists */
1163 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1164 gtk_tree_selection_unselect_all(selection);
1165 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1167 /* create the helper */
1168 info = g_malloc0 (sizeof(SetFolderHelper));
1169 info->header_view = self;
1170 info->cb = callback;
1171 info->user_data = user_data;
1173 /* Create the mail operation (source will be the parent widget) */
1174 mail_op = modest_mail_operation_new (source);
1175 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1178 /* Refresh the folder asynchronously */
1179 modest_mail_operation_refresh_folder (mail_op,
1181 folder_refreshed_cb,
1185 g_object_unref (mail_op);
1187 g_mutex_lock (priv->observers_lock);
1189 if (priv->monitor) {
1190 tny_folder_monitor_stop (priv->monitor);
1191 g_object_unref (G_OBJECT (priv->monitor));
1192 priv->monitor = NULL;
1194 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1196 modest_header_view_notify_observers(self, NULL, NULL);
1198 g_mutex_unlock (priv->observers_lock);
1203 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1204 GtkTreeViewColumn *column, gpointer userdata)
1206 ModestHeaderView *self = NULL;
1207 ModestHeaderViewPrivate *priv = NULL;
1209 GtkTreeModel *model = NULL;
1210 TnyHeader *header = NULL;
1211 TnyHeaderFlags flags;
1213 self = MODEST_HEADER_VIEW (treeview);
1214 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1216 model = gtk_tree_view_get_model (treeview);
1217 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1220 /* get the first selected item */
1221 gtk_tree_model_get (model, &iter,
1222 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1223 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1226 /* Dont open DELETED messages */
1227 if (flags & TNY_HEADER_FLAG_DELETED) {
1228 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1233 g_signal_emit (G_OBJECT(self),
1234 signals[HEADER_ACTIVATED_SIGNAL],
1240 g_object_unref (G_OBJECT (header));
1245 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1247 GtkTreeModel *model;
1248 TnyHeader *header = NULL;
1249 GtkTreePath *path = NULL;
1251 ModestHeaderView *self;
1252 ModestHeaderViewPrivate *priv;
1253 GList *selected = NULL;
1255 g_return_if_fail (sel);
1256 g_return_if_fail (user_data);
1258 self = MODEST_HEADER_VIEW (user_data);
1259 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1261 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1262 if (selected != NULL)
1263 path = (GtkTreePath *) selected->data;
1264 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1265 return; /* msg was _un_selected */
1267 gtk_tree_model_get (model, &iter,
1268 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1272 g_signal_emit (G_OBJECT(self),
1273 signals[HEADER_SELECTED_SIGNAL],
1276 g_object_unref (G_OBJECT (header));
1278 /* free all items in 'selected' */
1279 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1280 g_list_free (selected);
1284 /* PROTECTED method. It's useful when we want to force a given
1285 selection to reload a msg. For example if we have selected a header
1286 in offline mode, when Modest become online, we want to reload the
1287 message automatically without an user click over the header */
1289 _modest_header_view_change_selection (GtkTreeSelection *selection,
1292 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1293 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1295 on_selection_changed (selection, user_data);
1298 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1300 p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1301 p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1303 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1305 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1310 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1317 /* static int counter = 0; */
1319 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1320 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1321 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1325 case TNY_HEADER_FLAG_ATTACHMENTS:
1327 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1328 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1329 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1330 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1332 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1333 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1335 return cmp ? cmp : t1 - t2;
1337 case TNY_HEADER_FLAG_PRIORITY:
1338 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1339 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1340 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1341 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1343 /* This is for making priority values respect the intuitive sort relationship
1344 * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1345 cmp = compare_priorities (val1, val2);
1347 return cmp ? cmp : t1 - t2;
1350 return &iter1 - &iter2; /* oughhhh */
1355 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1361 /* static int counter = 0; */
1363 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1365 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1366 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1367 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1368 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1370 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1371 val2 + modest_text_utils_get_subject_prefix_len(val2),
1378 /* Drag and drop stuff */
1380 drag_data_get_cb (GtkWidget *widget,
1381 GdkDragContext *context,
1382 GtkSelectionData *selection_data,
1387 ModestHeaderView *self = NULL;
1388 ModestHeaderViewPrivate *priv = NULL;
1390 self = MODEST_HEADER_VIEW (widget);
1391 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1393 /* Set the data. Do not use the current selection because it
1394 could be different than the selection at the beginning of
1396 modest_dnd_selection_data_set_paths (selection_data,
1397 priv->drag_begin_cached_selected_rows);
1401 * We're caching the selected rows at the beginning because the
1402 * selection could change between drag-begin and drag-data-get, for
1403 * example if we have a set of rows already selected, and then we
1404 * click in one of them (without SHIFT key pressed) and begin a drag,
1405 * the selection at that moment contains all the selected lines, but
1406 * after dropping the selection, the release event provokes that only
1407 * the row used to begin the drag is selected, so at the end the
1408 * drag&drop affects only one rows instead of all the selected ones.
1412 drag_begin_cb (GtkWidget *widget,
1413 GdkDragContext *context,
1416 ModestHeaderView *self = NULL;
1417 ModestHeaderViewPrivate *priv = NULL;
1418 GtkTreeSelection *selection;
1420 self = MODEST_HEADER_VIEW (widget);
1421 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1423 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1424 priv->drag_begin_cached_selected_rows =
1425 gtk_tree_selection_get_selected_rows (selection, NULL);
1429 * We use the drag-end signal to clear the cached selection, we use
1430 * this because this allways happens, whether or not the d&d was a
1434 drag_end_cb (GtkWidget *widget,
1438 ModestHeaderView *self = NULL;
1439 ModestHeaderViewPrivate *priv = NULL;
1441 self = MODEST_HEADER_VIEW (widget);
1442 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1444 /* Free cached data */
1445 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1446 g_list_free (priv->drag_begin_cached_selected_rows);
1447 priv->drag_begin_cached_selected_rows = NULL;
1450 /* Header view drag types */
1451 const GtkTargetEntry header_view_drag_types[] = {
1452 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1456 setup_drag_and_drop (GtkTreeView *self)
1458 gtk_drag_source_set (GTK_WIDGET (self),
1460 header_view_drag_types,
1461 G_N_ELEMENTS (header_view_drag_types),
1462 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1464 g_signal_connect(G_OBJECT (self), "drag_data_get",
1465 G_CALLBACK(drag_data_get_cb), NULL);
1467 g_signal_connect(G_OBJECT (self), "drag_begin",
1468 G_CALLBACK(drag_begin_cb), NULL);
1470 g_signal_connect(G_OBJECT (self), "drag_end",
1471 G_CALLBACK(drag_end_cb), NULL);
1474 static GtkTreePath *
1475 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1477 GtkTreePath *path = NULL;
1478 GtkTreeSelection *sel = NULL;
1481 sel = gtk_tree_view_get_selection(self);
1482 rows = gtk_tree_selection_get_selected_rows (sel, model);
1484 if ((rows == NULL) || (g_list_length(rows) != 1))
1487 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1492 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1499 * This function moves the tree view scroll to the current selected
1500 * row when the widget grabs the focus
1503 on_focus_in (GtkWidget *self,
1504 GdkEventFocus *event,
1507 GtkTreeSelection *selection;
1508 GtkTreeModel *model;
1509 GList *selected = NULL;
1510 GtkTreePath *selected_path = NULL;
1512 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1516 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1517 /* If none selected yet, pick the first one */
1518 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1522 /* Return if the model is empty */
1523 if (!gtk_tree_model_get_iter_first (model, &iter))
1526 path = gtk_tree_model_get_path (model, &iter);
1527 gtk_tree_selection_select_path (selection, path);
1528 gtk_tree_path_free (path);
1531 /* Need to get the all the rows because is selection multiple */
1532 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1533 if (selected == NULL) return FALSE;
1534 selected_path = (GtkTreePath *) selected->data;
1536 /* Check if we need to scroll */
1537 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1538 GtkTreePath *start_path = NULL;
1539 GtkTreePath *end_path = NULL;
1540 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1544 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1545 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1547 /* Scroll to first path */
1548 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1557 gtk_tree_path_free (start_path);
1559 gtk_tree_path_free (end_path);
1561 #endif /* GTK_CHECK_VERSION */
1564 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1565 g_list_free (selected);
1571 on_focus_out (GtkWidget *self,
1572 GdkEventFocus *event,
1576 if (!gtk_widget_is_focus (self)) {
1577 GtkTreeSelection *selection = NULL;
1578 GList *selected_rows = NULL;
1579 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1580 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1581 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1582 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1583 gtk_tree_selection_unselect_all (selection);
1584 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1585 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1586 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1587 g_list_free (selected_rows);
1594 folder_monitor_update (TnyFolderObserver *self,
1595 TnyFolderChange *change)
1597 ModestHeaderViewPrivate *priv = NULL;
1598 TnyFolderChangeChanged changed;
1599 TnyFolder *folder = NULL;
1601 changed = tny_folder_change_get_changed (change);
1603 /* Do not notify the observers if the folder of the header
1604 view has changed before this call to the observer
1606 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1607 folder = tny_folder_change_get_folder (change);
1608 if (folder != priv->folder)
1611 /* Check folder count */
1612 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1613 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1615 g_mutex_lock (priv->observers_lock);
1617 /* Emit signal to evaluate how headers changes affects
1618 to the window view */
1619 g_signal_emit (G_OBJECT(self),
1620 signals[MSG_COUNT_CHANGED_SIGNAL],
1623 /* Added or removed headers, so data stored on cliboard are invalid */
1624 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1625 modest_email_clipboard_clear (priv->clipboard);
1627 g_mutex_unlock (priv->observers_lock);
1633 g_object_unref (folder);
1637 modest_header_view_is_empty (ModestHeaderView *self)
1639 ModestHeaderViewPrivate *priv = NULL;
1641 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1643 return priv->status == HEADER_VIEW_EMPTY;
1647 modest_header_view_clear (ModestHeaderView *self)
1649 modest_header_view_set_folder (self, NULL, NULL, NULL);
1653 modest_header_view_copy_selection (ModestHeaderView *header_view)
1655 /* Copy selection */
1656 _clipboard_set_selected_data (header_view, FALSE);
1660 modest_header_view_cut_selection (ModestHeaderView *header_view)
1662 ModestHeaderViewPrivate *priv = NULL;
1663 const gchar **hidding = NULL;
1664 guint i, n_selected;
1666 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1667 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1669 /* Copy selection */
1670 _clipboard_set_selected_data (header_view, TRUE);
1672 /* Get hidding ids */
1673 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1675 /* Clear hidding array created by previous cut operation */
1676 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1678 /* Copy hidding array */
1679 priv->n_selected = n_selected;
1680 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1681 for (i=0; i < n_selected; i++)
1682 priv->hidding_ids[i] = g_strdup(hidding[i]);
1684 /* Hide cut headers */
1685 modest_header_view_refilter (header_view);
1692 _clipboard_set_selected_data (ModestHeaderView *header_view,
1695 ModestHeaderViewPrivate *priv = NULL;
1696 TnyList *headers = NULL;
1698 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1699 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1701 /* Set selected data on clipboard */
1702 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1703 headers = modest_header_view_get_selected_headers (header_view);
1704 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1707 g_object_unref (headers);
1713 filter_row (GtkTreeModel *model,
1717 ModestHeaderViewPrivate *priv = NULL;
1718 TnyHeaderFlags flags;
1719 TnyHeader *header = NULL;
1722 gboolean visible = TRUE;
1723 gboolean found = FALSE;
1725 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1726 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1728 /* Get header from model */
1729 gtk_tree_model_get (model, iter,
1730 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1731 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1734 /* Hide mark as deleted heders */
1735 if (flags & TNY_HEADER_FLAG_DELETED) {
1740 /* If no data on clipboard, return always TRUE */
1741 if (modest_email_clipboard_cleared(priv->clipboard)) {
1746 /* Get message id from header (ensure is a valid id) */
1747 if (!header) return FALSE;
1748 id = g_strdup(tny_header_get_message_id (header));
1751 if (priv->hidding_ids != NULL) {
1752 for (i=0; i < priv->n_selected && !found; i++)
1753 if (priv->hidding_ids[i] != NULL && id != NULL)
1754 found = (!strcmp (priv->hidding_ids[i], id));
1760 priv->status = ((gboolean) priv->status) && !visible;
1764 g_object_unref (header);
1771 _clear_hidding_filter (ModestHeaderView *header_view)
1773 ModestHeaderViewPrivate *priv = NULL;
1776 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1777 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1779 if (priv->hidding_ids != NULL) {
1780 for (i=0; i < priv->n_selected; i++)
1781 g_free (priv->hidding_ids[i]);
1782 g_free(priv->hidding_ids);
1787 modest_header_view_refilter (ModestHeaderView *header_view)
1789 GtkTreeModel *model = NULL;
1790 ModestHeaderViewPrivate *priv = NULL;
1792 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1793 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1795 /* Hide cut headers */
1796 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1797 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1799 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1804 * Called when an account is removed. If I'm showing a folder of the
1805 * account that has been removed then clear the view
1808 on_account_removed (TnyAccountStore *self,
1809 TnyAccount *account,
1812 ModestHeaderViewPrivate *priv = NULL;
1814 /* Ignore changes in transport accounts */
1815 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1818 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1821 TnyAccount *my_account;
1823 my_account = tny_folder_get_account (priv->folder);
1824 if (my_account == account)
1825 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1826 g_object_unref (account);
1830 void modest_header_view_add_observer(
1831 ModestHeaderView *header_view,
1832 ModestHeaderViewObserver *observer)
1834 ModestHeaderViewPrivate *priv = NULL;
1836 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1837 g_assert(observer != NULL);
1838 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1840 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1842 g_mutex_lock(priv->observer_list_lock);
1843 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1844 g_mutex_unlock(priv->observer_list_lock);
1848 modest_header_view_remove_observer(ModestHeaderView *header_view,
1849 ModestHeaderViewObserver *observer)
1851 ModestHeaderViewPrivate *priv = NULL;
1853 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1854 g_assert(observer != NULL);
1855 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1857 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1859 g_mutex_lock(priv->observer_list_lock);
1860 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1861 g_mutex_unlock(priv->observer_list_lock);
1865 modest_header_view_notify_observers(ModestHeaderView *header_view,
1866 GtkTreeModel *model,
1867 const gchar *tny_folder_id)
1869 ModestHeaderViewPrivate *priv = NULL;
1871 ModestHeaderViewObserver *observer;
1873 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1875 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1877 g_mutex_lock(priv->observer_list_lock);
1878 iter = priv->observer_list;
1879 while(iter != NULL){
1880 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1881 modest_header_view_observer_update(observer, model,
1883 iter = g_slist_next(iter);
1885 g_mutex_unlock(priv->observer_list_lock);