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 gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
84 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
87 static void setup_drag_and_drop (GtkWidget *self);
89 static void enable_drag_and_drop (GtkWidget *self);
91 static void disable_drag_and_drop (GtkWidget *self);
93 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
95 static gboolean on_focus_in (GtkWidget *sef,
99 static gboolean on_focus_out (GtkWidget *self,
100 GdkEventFocus *event,
103 static void folder_monitor_update (TnyFolderObserver *self,
104 TnyFolderChange *change);
106 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
108 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
110 static void _clear_hidding_filter (ModestHeaderView *header_view);
112 static void modest_header_view_notify_observers(
113 ModestHeaderView *header_view,
115 const gchar *tny_folder_id);
117 static gboolean modest_header_view_on_expose_event(
118 GtkTreeView *header_view,
119 GdkEventExpose *event,
123 HEADER_VIEW_NON_EMPTY,
128 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
129 struct _ModestHeaderViewPrivate {
131 ModestHeaderViewStyle style;
133 TnyFolderMonitor *monitor;
134 GMutex *observers_lock;
136 /*header-view-observer observer*/
137 GMutex *observer_list_lock;
138 GSList *observer_list;
140 /* not unref this object, its a singlenton */
141 ModestEmailClipboard *clipboard;
143 /* Filter tree model */
147 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
148 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
150 gulong selection_changed_handler;
151 gulong acc_removed_handler;
153 HeaderViewStatus status;
154 GList *drag_begin_cached_selected_rows;
157 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
158 struct _HeadersCountChangedHelper {
159 ModestHeaderView *self;
160 TnyFolderChange *change;
164 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
165 MODEST_TYPE_HEADER_VIEW, \
166 ModestHeaderViewPrivate))
170 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
173 HEADER_SELECTED_SIGNAL,
174 HEADER_ACTIVATED_SIGNAL,
175 ITEM_NOT_FOUND_SIGNAL,
176 MSG_COUNT_CHANGED_SIGNAL,
177 UPDATING_MSG_LIST_SIGNAL,
182 static GObjectClass *parent_class = NULL;
184 /* uncomment the following if you have defined any signals */
185 static guint signals[LAST_SIGNAL] = {0};
188 modest_header_view_get_type (void)
190 static GType my_type = 0;
192 static const GTypeInfo my_info = {
193 sizeof(ModestHeaderViewClass),
194 NULL, /* base init */
195 NULL, /* base finalize */
196 (GClassInitFunc) modest_header_view_class_init,
197 NULL, /* class finalize */
198 NULL, /* class data */
199 sizeof(ModestHeaderView),
201 (GInstanceInitFunc) modest_header_view_init,
205 static const GInterfaceInfo tny_folder_observer_info =
207 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
208 NULL, /* interface_finalize */
209 NULL /* interface_data */
211 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
215 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
216 &tny_folder_observer_info);
224 modest_header_view_class_init (ModestHeaderViewClass *klass)
226 GObjectClass *gobject_class;
227 gobject_class = (GObjectClass*) klass;
229 parent_class = g_type_class_peek_parent (klass);
230 gobject_class->finalize = modest_header_view_finalize;
231 gobject_class->dispose = modest_header_view_dispose;
233 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
235 signals[HEADER_SELECTED_SIGNAL] =
236 g_signal_new ("header_selected",
237 G_TYPE_FROM_CLASS (gobject_class),
239 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
241 g_cclosure_marshal_VOID__POINTER,
242 G_TYPE_NONE, 1, G_TYPE_POINTER);
244 signals[HEADER_ACTIVATED_SIGNAL] =
245 g_signal_new ("header_activated",
246 G_TYPE_FROM_CLASS (gobject_class),
248 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
250 g_cclosure_marshal_VOID__POINTER,
251 G_TYPE_NONE, 1, G_TYPE_POINTER);
254 signals[ITEM_NOT_FOUND_SIGNAL] =
255 g_signal_new ("item_not_found",
256 G_TYPE_FROM_CLASS (gobject_class),
258 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
260 g_cclosure_marshal_VOID__INT,
261 G_TYPE_NONE, 1, G_TYPE_INT);
263 signals[MSG_COUNT_CHANGED_SIGNAL] =
264 g_signal_new ("msg_count_changed",
265 G_TYPE_FROM_CLASS (gobject_class),
267 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
269 modest_marshal_VOID__POINTER_POINTER,
270 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
272 signals[UPDATING_MSG_LIST_SIGNAL] =
273 g_signal_new ("updating-msg-list",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
278 g_cclosure_marshal_VOID__BOOLEAN,
279 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
283 tny_folder_observer_init (TnyFolderObserverIface *klass)
285 klass->update_func = folder_monitor_update;
288 static GtkTreeViewColumn*
289 get_new_column (const gchar *name, GtkCellRenderer *renderer,
290 gboolean resizable, gint sort_col_id, gboolean show_as_text,
291 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
293 GtkTreeViewColumn *column;
295 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
296 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
298 gtk_tree_view_column_set_resizable (column, resizable);
300 gtk_tree_view_column_set_expand (column, TRUE);
303 gtk_tree_view_column_add_attribute (column, renderer, "text",
305 if (sort_col_id >= 0)
306 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
308 gtk_tree_view_column_set_sort_indicator (column, FALSE);
309 gtk_tree_view_column_set_reorderable (column, TRUE);
312 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
319 remove_all_columns (ModestHeaderView *obj)
321 GList *columns, *cursor;
323 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
325 for (cursor = columns; cursor; cursor = cursor->next)
326 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
327 GTK_TREE_VIEW_COLUMN(cursor->data));
328 g_list_free (columns);
332 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
334 GtkTreeModel *tree_filter, *sortable;
335 GtkTreeViewColumn *column=NULL;
336 GtkTreeSelection *selection = NULL;
337 GtkCellRenderer *renderer_msgtype,*renderer_header,
338 *renderer_attach, *renderer_compact_date_or_status;
339 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
340 *renderer_subject, *renderer_subject_box, *renderer_recpt,
342 ModestHeaderViewPrivate *priv;
343 GtkTreeViewColumn *compact_column = NULL;
346 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
347 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
349 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
351 /* FIXME: check whether these renderers need to be freed */
352 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
353 renderer_attach = gtk_cell_renderer_pixbuf_new ();
354 renderer_priority = gtk_cell_renderer_pixbuf_new ();
355 renderer_header = gtk_cell_renderer_text_new ();
357 renderer_compact_header = modest_vbox_cell_renderer_new ();
358 renderer_recpt_box = modest_hbox_cell_renderer_new ();
359 renderer_subject_box = modest_hbox_cell_renderer_new ();
360 renderer_recpt = gtk_cell_renderer_text_new ();
361 renderer_subject = gtk_cell_renderer_text_new ();
362 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
364 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
365 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
366 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
367 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
368 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
369 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
370 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
371 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
372 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
373 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
374 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
375 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
376 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
377 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
379 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
380 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
381 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
382 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
383 g_object_set(G_OBJECT(renderer_header),
384 "ellipsize", PANGO_ELLIPSIZE_END,
386 g_object_set (G_OBJECT (renderer_subject),
387 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
389 g_object_set (G_OBJECT (renderer_recpt),
390 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
392 g_object_set(G_OBJECT(renderer_compact_date_or_status),
393 "xalign", 1.0, "yalign", 0.0,
395 g_object_set (G_OBJECT (renderer_priority),
396 "yalign", 1.0, NULL);
397 g_object_set (G_OBJECT (renderer_attach),
398 "yalign", 0.0, NULL);
400 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
401 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
402 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
404 remove_all_columns (self);
406 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
407 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
408 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
409 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
411 /* Add new columns */
412 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
413 ModestHeaderViewColumn col =
414 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
416 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
417 g_printerr ("modest: invalid column %d in column list\n", col);
423 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
424 column = get_new_column (_("M"), renderer_msgtype, FALSE,
425 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
427 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
429 gtk_tree_view_column_set_fixed_width (column, 45);
432 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
433 column = get_new_column (_("A"), renderer_attach, FALSE,
434 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
436 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
438 gtk_tree_view_column_set_fixed_width (column, 45);
442 case MODEST_HEADER_VIEW_COLUMN_FROM:
443 column = get_new_column (_("From"), renderer_header, TRUE,
444 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
446 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
447 GINT_TO_POINTER(TRUE));
450 case MODEST_HEADER_VIEW_COLUMN_TO:
451 column = get_new_column (_("To"), renderer_header, TRUE,
452 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
454 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
455 GINT_TO_POINTER(FALSE));
458 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
459 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
460 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
462 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
463 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
464 compact_column = column;
467 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
468 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
469 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
471 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
472 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
473 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
474 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
475 compact_column = column;
479 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
480 column = get_new_column (_("Subject"), renderer_header, TRUE,
481 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
483 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
487 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
488 column = get_new_column (_("Received"), renderer_header, TRUE,
489 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
491 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
492 GINT_TO_POINTER(TRUE));
495 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
496 column = get_new_column (_("Sent"), renderer_header, TRUE,
497 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
499 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
500 GINT_TO_POINTER(FALSE));
503 case MODEST_HEADER_VIEW_COLUMN_SIZE:
504 column = get_new_column (_("Size"), renderer_header, TRUE,
505 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
507 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
510 case MODEST_HEADER_VIEW_COLUMN_STATUS:
511 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
512 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
514 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
519 g_return_val_if_reached(FALSE);
522 /* we keep the column id around */
523 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
524 GINT_TO_POINTER(col));
526 /* we need this ptr when sorting the rows */
527 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
529 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
533 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
534 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
535 (GtkTreeIterCompareFunc) cmp_rows,
536 compact_column, NULL);
537 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
538 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
539 (GtkTreeIterCompareFunc) cmp_subject_rows,
540 compact_column, NULL);
548 modest_header_view_init (ModestHeaderView *obj)
550 ModestHeaderViewPrivate *priv;
553 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
557 priv->monitor = NULL;
558 priv->observers_lock = g_mutex_new ();
560 priv->status = HEADER_VIEW_INIT;
562 priv->observer_list_lock = g_mutex_new();
563 priv->observer_list = NULL;
565 priv->clipboard = modest_runtime_get_email_clipboard ();
566 priv->hidding_ids = NULL;
567 priv->n_selected = 0;
568 priv->selection_changed_handler = 0;
569 priv->acc_removed_handler = 0;
571 /* Sort parameters */
572 for (j=0; j < 2; j++) {
573 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
574 priv->sort_colid[j][i] = -1;
575 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
579 setup_drag_and_drop (GTK_WIDGET(obj));
583 modest_header_view_dispose (GObject *obj)
585 ModestHeaderView *self;
586 ModestHeaderViewPrivate *priv;
587 GtkTreeSelection *sel;
589 self = MODEST_HEADER_VIEW(obj);
590 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
592 /* Free in the dispose to avoid unref cycles */
594 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
595 g_object_unref (G_OBJECT (priv->folder));
599 /* We need to do this here in the dispose because the
600 selection won't exist when finalizing */
601 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
602 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
603 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
604 priv->selection_changed_handler = 0;
607 G_OBJECT_CLASS(parent_class)->dispose (obj);
611 modest_header_view_finalize (GObject *obj)
613 ModestHeaderView *self;
614 ModestHeaderViewPrivate *priv;
616 self = MODEST_HEADER_VIEW(obj);
617 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
619 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
620 priv->acc_removed_handler)) {
621 g_signal_handler_disconnect (modest_runtime_get_account_store (),
622 priv->acc_removed_handler);
625 /* There is no need to lock because there should not be any
626 * reference to self now. */
627 g_mutex_free(priv->observer_list_lock);
628 g_slist_free(priv->observer_list);
630 g_mutex_lock (priv->observers_lock);
632 tny_folder_monitor_stop (priv->monitor);
633 g_object_unref (G_OBJECT (priv->monitor));
635 g_mutex_unlock (priv->observers_lock);
636 g_mutex_free (priv->observers_lock);
638 /* Clear hidding array created by cut operation */
639 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
641 G_OBJECT_CLASS(parent_class)->finalize (obj);
646 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
649 GtkTreeSelection *sel;
650 ModestHeaderView *self;
651 ModestHeaderViewPrivate *priv;
653 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
656 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
657 self = MODEST_HEADER_VIEW(obj);
658 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
660 modest_header_view_set_style (self, style);
662 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
663 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
664 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
666 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
667 TRUE); /* alternating row colors */
669 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
670 priv->selection_changed_handler =
671 g_signal_connect_after (sel, "changed",
672 G_CALLBACK(on_selection_changed), self);
674 g_signal_connect (self, "row-activated",
675 G_CALLBACK (on_header_row_activated), NULL);
677 g_signal_connect (self, "focus-in-event",
678 G_CALLBACK(on_focus_in), NULL);
679 g_signal_connect (self, "focus-out-event",
680 G_CALLBACK(on_focus_out), NULL);
682 g_signal_connect (self, "button-press-event",
683 G_CALLBACK(on_button_press_event), NULL);
684 g_signal_connect (self, "button-release-event",
685 G_CALLBACK(on_button_release_event), NULL);
687 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
689 G_CALLBACK (on_account_removed),
692 g_signal_connect (self, "expose-event",
693 G_CALLBACK(modest_header_view_on_expose_event),
696 return GTK_WIDGET(self);
701 modest_header_view_count_selected_headers (ModestHeaderView *self)
703 GtkTreeSelection *sel;
706 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
708 /* Get selection object and check selected rows count */
709 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
710 selected_rows = gtk_tree_selection_count_selected_rows (sel);
712 return selected_rows;
716 modest_header_view_has_selected_headers (ModestHeaderView *self)
718 GtkTreeSelection *sel;
721 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
723 /* Get selection object and check selected rows count */
724 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
725 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
732 modest_header_view_get_selected_headers (ModestHeaderView *self)
734 GtkTreeSelection *sel;
735 ModestHeaderViewPrivate *priv;
736 TnyList *header_list = NULL;
738 GList *list, *tmp = NULL;
739 GtkTreeModel *tree_model = NULL;
742 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
744 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
746 /* Get selected rows */
747 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
748 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
751 header_list = tny_simple_list_new();
753 list = g_list_reverse (list);
756 /* get header from selection */
757 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
758 gtk_tree_model_get (tree_model, &iter,
759 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
761 /* Prepend to list */
762 tny_list_prepend (header_list, G_OBJECT (header));
763 g_object_unref (G_OBJECT (header));
765 tmp = g_list_next (tmp);
768 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
775 /* scroll our list view so the selected item is visible */
777 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
779 #ifdef MODEST_PLATFORM_GNOME
781 GtkTreePath *selected_path;
782 GtkTreePath *start, *end;
786 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
787 selected_path = gtk_tree_model_get_path (model, iter);
789 start = gtk_tree_path_new ();
790 end = gtk_tree_path_new ();
792 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
794 if (gtk_tree_path_compare (selected_path, start) < 0 ||
795 gtk_tree_path_compare (end, selected_path) < 0)
796 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
797 selected_path, NULL, TRUE,
800 gtk_tree_path_free (selected_path);
801 gtk_tree_path_free (start);
802 gtk_tree_path_free (end);
804 #endif /* MODEST_PLATFORM_GNOME */
809 modest_header_view_select_next (ModestHeaderView *self)
811 GtkTreeSelection *sel;
816 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
818 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
819 path = get_selected_row (GTK_TREE_VIEW(self), &model);
820 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
821 /* Unselect previous path */
822 gtk_tree_selection_unselect_path (sel, path);
824 /* Move path down and selects new one */
825 if (gtk_tree_model_iter_next (model, &iter)) {
826 gtk_tree_selection_select_iter (sel, &iter);
827 scroll_to_selected (self, &iter, FALSE);
829 gtk_tree_path_free(path);
835 modest_header_view_select_prev (ModestHeaderView *self)
837 GtkTreeSelection *sel;
842 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
844 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
845 path = get_selected_row (GTK_TREE_VIEW(self), &model);
846 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
847 /* Unselect previous path */
848 gtk_tree_selection_unselect_path (sel, path);
851 if (gtk_tree_path_prev (path)) {
852 gtk_tree_model_get_iter (model, &iter, path);
854 /* Select the new one */
855 gtk_tree_selection_select_iter (sel, &iter);
856 scroll_to_selected (self, &iter, TRUE);
859 gtk_tree_path_free (path);
864 modest_header_view_get_columns (ModestHeaderView *self)
866 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
868 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
874 modest_header_view_set_style (ModestHeaderView *self,
875 ModestHeaderViewStyle style)
877 ModestHeaderViewPrivate *priv;
878 gboolean show_col_headers = FALSE;
879 ModestHeaderViewStyle old_style;
881 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
882 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
885 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
886 if (priv->style == style)
887 return TRUE; /* nothing to do */
890 case MODEST_HEADER_VIEW_STYLE_DETAILS:
891 show_col_headers = TRUE;
893 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
896 g_return_val_if_reached (FALSE);
898 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
899 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
901 old_style = priv->style;
908 ModestHeaderViewStyle
909 modest_header_view_get_style (ModestHeaderView *self)
911 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
913 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
916 /* This is used to automatically select the first header if the user
917 * has not selected any header yet.
920 modest_header_view_on_expose_event(GtkTreeView *header_view,
921 GdkEventExpose *event,
924 GtkTreeSelection *sel;
926 GtkTreeIter tree_iter;
928 model = gtk_tree_view_get_model(header_view);
930 sel = gtk_tree_view_get_selection(header_view);
931 if(!gtk_tree_selection_count_selected_rows(sel))
932 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
933 /* Prevent the widget from getting the focus
934 when selecting the first item */
935 g_object_set(header_view, "can-focus", FALSE, NULL);
936 gtk_tree_selection_select_iter(sel, &tree_iter);
937 g_object_set(header_view, "can-focus", TRUE, NULL);
944 * This function sets a sortable model in the header view. It's just
945 * used for developing purposes, because it only does a
946 * gtk_tree_view_set_model
949 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
951 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
952 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
953 /* GtkTreeModel *old_model; */
954 /* ModestHeaderViewPrivate *priv; */
955 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
956 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
958 /* /\* Set new model *\/ */
959 /* gtk_tree_view_set_model (header_view, model); */
961 gtk_tree_view_set_model (header_view, model);
965 modest_header_view_get_folder (ModestHeaderView *self)
967 ModestHeaderViewPrivate *priv;
969 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
971 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
974 g_object_ref (priv->folder);
980 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
984 ModestHeaderViewPrivate *priv;
985 GList *cols, *cursor;
986 GtkTreeModel *filter_model, *sortable;
988 GtkSortType sort_type;
990 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
992 headers = TNY_LIST (tny_gtk_header_list_model_new ());
994 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
995 folder, FALSE, NULL, NULL, NULL);
997 /* Add IDLE observer (monitor) and another folder observer for
998 new messages (self) */
999 g_mutex_lock (priv->observers_lock);
1000 if (priv->monitor) {
1001 tny_folder_monitor_stop (priv->monitor);
1002 g_object_unref (G_OBJECT (priv->monitor));
1004 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1005 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1006 tny_folder_monitor_start (priv->monitor);
1007 g_mutex_unlock (priv->observers_lock);
1009 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
1010 g_object_unref (G_OBJECT (headers));
1012 /* Init filter_row function to examine empty status */
1013 priv->status = HEADER_VIEW_INIT;
1015 /* Create a tree model filter to hide and show rows for cut operations */
1016 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1017 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1021 g_object_unref (G_OBJECT (sortable));
1023 /* install our special sorting functions */
1024 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1026 /* Restore sort column id */
1028 type = modest_tny_folder_guess_folder_type (folder);
1029 if (type == TNY_FOLDER_TYPE_INVALID)
1030 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1032 sort_colid = modest_header_view_get_sort_column_id (self, type);
1033 sort_type = modest_header_view_get_sort_type (self, type);
1034 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1037 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1038 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1039 (GtkTreeIterCompareFunc) cmp_rows,
1041 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1042 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1043 (GtkTreeIterCompareFunc) cmp_subject_rows,
1048 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1049 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1050 tny_folder_get_id(folder));
1051 g_object_unref (G_OBJECT (filter_model));
1052 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1053 /* g_object_unref (G_OBJECT (sortable)); */
1060 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1062 GtkSortType sort_type)
1064 ModestHeaderViewPrivate *priv = NULL;
1065 GtkTreeModel *tree_filter, *sortable = NULL;
1068 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1069 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1071 /* Get model and private data */
1072 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1073 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1074 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1075 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1077 /* Sort tree model */
1078 type = modest_tny_folder_guess_folder_type (priv->folder);
1079 if (type == TNY_FOLDER_TYPE_INVALID)
1080 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1082 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1085 /* Store new sort parameters */
1086 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1091 modest_header_view_set_sort_params (ModestHeaderView *self,
1093 GtkSortType sort_type,
1096 ModestHeaderViewPrivate *priv;
1097 ModestHeaderViewStyle style;
1099 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1100 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1101 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1103 style = modest_header_view_get_style (self);
1104 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1106 priv->sort_colid[style][type] = sort_colid;
1107 priv->sort_type[style][type] = sort_type;
1111 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1114 ModestHeaderViewPrivate *priv;
1115 ModestHeaderViewStyle style;
1117 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1118 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1120 style = modest_header_view_get_style (self);
1121 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1123 return priv->sort_colid[style][type];
1127 modest_header_view_get_sort_type (ModestHeaderView *self,
1130 ModestHeaderViewPrivate *priv;
1131 ModestHeaderViewStyle style;
1133 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1134 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1136 style = modest_header_view_get_style (self);
1137 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1139 return priv->sort_type[style][type];
1143 ModestHeaderView *header_view;
1144 RefreshAsyncUserCallback cb;
1149 folder_refreshed_cb (ModestMailOperation *mail_op,
1153 ModestHeaderViewPrivate *priv;
1154 SetFolderHelper *info;
1156 info = (SetFolderHelper*) user_data;
1158 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1162 info->cb (mail_op, folder, info->user_data);
1164 /* Start the folder count changes observer. We do not need it
1165 before the refresh. Note that the monitor could still be
1166 called for this refresh but now we know that the callback
1167 was previously called */
1168 g_mutex_lock (priv->observers_lock);
1169 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1170 g_mutex_unlock (priv->observers_lock);
1172 /* Notify the observers that the update is over */
1173 g_signal_emit (G_OBJECT (info->header_view),
1174 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1181 modest_header_view_set_folder (ModestHeaderView *self,
1183 RefreshAsyncUserCallback callback,
1186 ModestHeaderViewPrivate *priv;
1187 SetFolderHelper *info;
1188 ModestWindow *main_win;
1190 g_return_if_fail (self);
1192 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1194 main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1195 FALSE); /* don't create */
1197 g_warning ("%s: BUG: no main window", __FUNCTION__);
1202 g_mutex_lock (priv->observers_lock);
1203 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1204 g_object_unref (priv->folder);
1205 priv->folder = NULL;
1206 g_mutex_unlock (priv->observers_lock);
1210 ModestMailOperation *mail_op = NULL;
1211 GtkTreeSelection *selection;
1213 /* Set folder in the model */
1214 modest_header_view_set_folder_intern (self, folder);
1216 /* Pick my reference. Nothing to do with the mail operation */
1217 priv->folder = g_object_ref (folder);
1219 /* Clear the selection if exists */
1220 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1221 gtk_tree_selection_unselect_all(selection);
1222 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1224 /* Notify the observers that the update begins */
1225 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1228 /* create the helper */
1229 info = g_malloc0 (sizeof(SetFolderHelper));
1230 info->header_view = self;
1231 info->cb = callback;
1232 info->user_data = user_data;
1234 /* Create the mail operation (source will be the parent widget) */
1235 mail_op = modest_mail_operation_new (G_OBJECT(main_win));
1236 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1239 /* Refresh the folder asynchronously */
1240 modest_mail_operation_refresh_folder (mail_op,
1242 folder_refreshed_cb,
1246 g_object_unref (mail_op);
1248 g_mutex_lock (priv->observers_lock);
1250 if (priv->monitor) {
1251 tny_folder_monitor_stop (priv->monitor);
1252 g_object_unref (G_OBJECT (priv->monitor));
1253 priv->monitor = NULL;
1255 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1257 modest_header_view_notify_observers(self, NULL, NULL);
1259 g_mutex_unlock (priv->observers_lock);
1261 /* Notify the observers that the update is over */
1262 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1268 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1269 GtkTreeViewColumn *column, gpointer userdata)
1271 ModestHeaderView *self = NULL;
1272 ModestHeaderViewPrivate *priv = NULL;
1274 GtkTreeModel *model = NULL;
1275 TnyHeader *header = NULL;
1276 TnyHeaderFlags flags;
1278 self = MODEST_HEADER_VIEW (treeview);
1279 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1281 model = gtk_tree_view_get_model (treeview);
1282 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1285 /* get the first selected item */
1286 gtk_tree_model_get (model, &iter,
1287 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1288 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1291 /* Dont open DELETED messages */
1292 if (flags & TNY_HEADER_FLAG_DELETED) {
1293 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1298 g_signal_emit (G_OBJECT(self),
1299 signals[HEADER_ACTIVATED_SIGNAL],
1305 g_object_unref (G_OBJECT (header));
1310 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1312 GtkTreeModel *model;
1313 TnyHeader *header = NULL;
1314 GtkTreePath *path = NULL;
1316 ModestHeaderView *self;
1317 ModestHeaderViewPrivate *priv;
1318 GList *selected = NULL;
1320 g_return_if_fail (sel);
1321 g_return_if_fail (user_data);
1323 self = MODEST_HEADER_VIEW (user_data);
1324 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1326 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1327 if (selected != NULL)
1328 path = (GtkTreePath *) selected->data;
1329 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1330 return; /* msg was _un_selected */
1332 gtk_tree_model_get (model, &iter,
1333 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1337 g_signal_emit (G_OBJECT(self),
1338 signals[HEADER_SELECTED_SIGNAL],
1341 g_object_unref (G_OBJECT (header));
1343 /* free all items in 'selected' */
1344 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1345 g_list_free (selected);
1349 /* PROTECTED method. It's useful when we want to force a given
1350 selection to reload a msg. For example if we have selected a header
1351 in offline mode, when Modest become online, we want to reload the
1352 message automatically without an user click over the header */
1354 _modest_header_view_change_selection (GtkTreeSelection *selection,
1357 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1358 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1360 on_selection_changed (selection, user_data);
1364 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1371 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1375 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1379 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1387 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1394 /* static int counter = 0; */
1396 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1397 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1398 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1402 case TNY_HEADER_FLAG_ATTACHMENTS:
1404 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1405 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1406 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1407 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1409 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1410 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1412 return cmp ? cmp : t1 - t2;
1414 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1415 TnyHeader *header1 = NULL, *header2 = NULL;
1417 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1418 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1419 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1420 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1422 /* This is for making priority values respect the intuitive sort relationship
1423 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1425 if (header1 && header2) {
1426 cmp = compare_priorities (tny_header_get_priority (header1),
1427 tny_header_get_priority (header2));
1428 g_object_unref (header1);
1429 g_object_unref (header2);
1431 return cmp ? cmp : t1 - t2;
1437 return &iter1 - &iter2; /* oughhhh */
1442 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1448 /* static int counter = 0; */
1450 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1452 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1453 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1454 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1455 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1457 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1458 val2 + modest_text_utils_get_subject_prefix_len(val2),
1465 /* Drag and drop stuff */
1467 drag_data_get_cb (GtkWidget *widget,
1468 GdkDragContext *context,
1469 GtkSelectionData *selection_data,
1474 ModestHeaderView *self = NULL;
1475 ModestHeaderViewPrivate *priv = NULL;
1477 self = MODEST_HEADER_VIEW (widget);
1478 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1480 /* Set the data. Do not use the current selection because it
1481 could be different than the selection at the beginning of
1483 modest_dnd_selection_data_set_paths (selection_data,
1484 priv->drag_begin_cached_selected_rows);
1488 * We're caching the selected rows at the beginning because the
1489 * selection could change between drag-begin and drag-data-get, for
1490 * example if we have a set of rows already selected, and then we
1491 * click in one of them (without SHIFT key pressed) and begin a drag,
1492 * the selection at that moment contains all the selected lines, but
1493 * after dropping the selection, the release event provokes that only
1494 * the row used to begin the drag is selected, so at the end the
1495 * drag&drop affects only one rows instead of all the selected ones.
1499 drag_begin_cb (GtkWidget *widget,
1500 GdkDragContext *context,
1503 ModestHeaderView *self = NULL;
1504 ModestHeaderViewPrivate *priv = NULL;
1505 GtkTreeSelection *selection;
1507 self = MODEST_HEADER_VIEW (widget);
1508 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1510 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1511 priv->drag_begin_cached_selected_rows =
1512 gtk_tree_selection_get_selected_rows (selection, NULL);
1516 * We use the drag-end signal to clear the cached selection, we use
1517 * this because this allways happens, whether or not the d&d was a
1521 drag_end_cb (GtkWidget *widget,
1525 ModestHeaderView *self = NULL;
1526 ModestHeaderViewPrivate *priv = NULL;
1528 self = MODEST_HEADER_VIEW (widget);
1529 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1531 /* Free cached data */
1532 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1533 g_list_free (priv->drag_begin_cached_selected_rows);
1534 priv->drag_begin_cached_selected_rows = NULL;
1537 /* Header view drag types */
1538 const GtkTargetEntry header_view_drag_types[] = {
1539 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1543 enable_drag_and_drop (GtkWidget *self)
1545 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1546 header_view_drag_types,
1547 G_N_ELEMENTS (header_view_drag_types),
1548 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1552 disable_drag_and_drop (GtkWidget *self)
1554 gtk_drag_source_unset (self);
1558 setup_drag_and_drop (GtkWidget *self)
1560 enable_drag_and_drop(self);
1561 g_signal_connect(G_OBJECT (self), "drag_data_get",
1562 G_CALLBACK(drag_data_get_cb), NULL);
1564 g_signal_connect(G_OBJECT (self), "drag_begin",
1565 G_CALLBACK(drag_begin_cb), NULL);
1567 g_signal_connect(G_OBJECT (self), "drag_end",
1568 G_CALLBACK(drag_end_cb), NULL);
1571 static GtkTreePath *
1572 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1574 GtkTreePath *path = NULL;
1575 GtkTreeSelection *sel = NULL;
1578 sel = gtk_tree_view_get_selection(self);
1579 rows = gtk_tree_selection_get_selected_rows (sel, model);
1581 if ((rows == NULL) || (g_list_length(rows) != 1))
1584 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1589 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1596 * This function moves the tree view scroll to the current selected
1597 * row when the widget grabs the focus
1600 on_focus_in (GtkWidget *self,
1601 GdkEventFocus *event,
1604 GtkTreeSelection *selection;
1605 GtkTreeModel *model;
1606 GList *selected = NULL;
1607 GtkTreePath *selected_path = NULL;
1609 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1613 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1614 /* If none selected yet, pick the first one */
1615 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1619 /* Return if the model is empty */
1620 if (!gtk_tree_model_get_iter_first (model, &iter))
1623 path = gtk_tree_model_get_path (model, &iter);
1624 gtk_tree_selection_select_path (selection, path);
1625 gtk_tree_path_free (path);
1628 /* Need to get the all the rows because is selection multiple */
1629 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1630 if (selected == NULL) return FALSE;
1631 selected_path = (GtkTreePath *) selected->data;
1633 /* Check if we need to scroll */
1634 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1635 GtkTreePath *start_path = NULL;
1636 GtkTreePath *end_path = NULL;
1637 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1641 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1642 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1644 /* Scroll to first path */
1645 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1654 gtk_tree_path_free (start_path);
1656 gtk_tree_path_free (end_path);
1658 #endif /* GTK_CHECK_VERSION */
1661 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1662 g_list_free (selected);
1668 on_focus_out (GtkWidget *self,
1669 GdkEventFocus *event,
1673 if (!gtk_widget_is_focus (self)) {
1674 GtkTreeSelection *selection = NULL;
1675 GList *selected_rows = NULL;
1676 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1677 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1678 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1679 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1680 gtk_tree_selection_unselect_all (selection);
1681 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1682 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1683 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1684 g_list_free (selected_rows);
1691 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1693 enable_drag_and_drop(self);
1698 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1700 GtkTreeSelection *selection = NULL;
1701 GtkTreePath *path = NULL;
1702 gboolean already_selected = FALSE;
1704 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1705 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1706 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1709 /* Enable drag and drop onlly if the user clicks on a row that
1710 it's already selected. If not, let him select items using
1712 if (!already_selected) {
1713 disable_drag_and_drop(self);
1717 gtk_tree_path_free(path);
1724 folder_monitor_update (TnyFolderObserver *self,
1725 TnyFolderChange *change)
1727 ModestHeaderViewPrivate *priv = NULL;
1728 TnyFolderChangeChanged changed;
1729 TnyFolder *folder = NULL;
1731 changed = tny_folder_change_get_changed (change);
1733 /* Do not notify the observers if the folder of the header
1734 view has changed before this call to the observer
1736 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1737 folder = tny_folder_change_get_folder (change);
1738 if (folder != priv->folder)
1741 /* Check folder count */
1742 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1743 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1745 g_mutex_lock (priv->observers_lock);
1747 /* Emit signal to evaluate how headers changes affects
1748 to the window view */
1749 g_signal_emit (G_OBJECT(self),
1750 signals[MSG_COUNT_CHANGED_SIGNAL],
1753 /* Added or removed headers, so data stored on cliboard are invalid */
1754 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1755 modest_email_clipboard_clear (priv->clipboard);
1757 g_mutex_unlock (priv->observers_lock);
1763 g_object_unref (folder);
1767 modest_header_view_is_empty (ModestHeaderView *self)
1769 ModestHeaderViewPrivate *priv;
1771 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1773 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1775 return priv->status == HEADER_VIEW_EMPTY;
1779 modest_header_view_clear (ModestHeaderView *self)
1781 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1783 modest_header_view_set_folder (self, NULL, NULL, NULL);
1787 modest_header_view_copy_selection (ModestHeaderView *header_view)
1789 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1791 /* Copy selection */
1792 _clipboard_set_selected_data (header_view, FALSE);
1796 modest_header_view_cut_selection (ModestHeaderView *header_view)
1798 ModestHeaderViewPrivate *priv = NULL;
1799 const gchar **hidding = NULL;
1800 guint i, n_selected;
1802 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1804 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1806 /* Copy selection */
1807 _clipboard_set_selected_data (header_view, TRUE);
1809 /* Get hidding ids */
1810 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1812 /* Clear hidding array created by previous cut operation */
1813 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1815 /* Copy hidding array */
1816 priv->n_selected = n_selected;
1817 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1818 for (i=0; i < n_selected; i++)
1819 priv->hidding_ids[i] = g_strdup(hidding[i]);
1821 /* Hide cut headers */
1822 modest_header_view_refilter (header_view);
1829 _clipboard_set_selected_data (ModestHeaderView *header_view,
1832 ModestHeaderViewPrivate *priv = NULL;
1833 TnyList *headers = NULL;
1835 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1836 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1838 /* Set selected data on clipboard */
1839 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1840 headers = modest_header_view_get_selected_headers (header_view);
1841 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1844 g_object_unref (headers);
1850 filter_row (GtkTreeModel *model,
1854 ModestHeaderViewPrivate *priv = NULL;
1855 TnyHeaderFlags flags;
1856 TnyHeader *header = NULL;
1859 gboolean visible = TRUE;
1860 gboolean found = FALSE;
1861 GValue value = {0,};
1863 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1864 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1866 /* Get header from model */
1867 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
1868 flags = (TnyHeaderFlags) g_value_get_int (&value);
1869 g_value_unset (&value);
1870 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
1871 header = (TnyHeader *) g_value_get_object (&value);
1872 g_value_unset (&value);
1874 /* Hide mark as deleted heders */
1875 if (flags & TNY_HEADER_FLAG_DELETED) {
1880 /* If no data on clipboard, return always TRUE */
1881 if (modest_email_clipboard_cleared(priv->clipboard)) {
1886 /* Get message id from header (ensure is a valid id) */
1887 if (!header) return FALSE;
1890 if (priv->hidding_ids != NULL) {
1891 id = g_strdup(tny_header_get_message_id (header));
1892 for (i=0; i < priv->n_selected && !found; i++)
1893 if (priv->hidding_ids[i] != NULL && id != NULL)
1894 found = (!strcmp (priv->hidding_ids[i], id));
1901 priv->status = ((gboolean) priv->status) && !visible;
1907 _clear_hidding_filter (ModestHeaderView *header_view)
1909 ModestHeaderViewPrivate *priv = NULL;
1912 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1913 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1915 if (priv->hidding_ids != NULL) {
1916 for (i=0; i < priv->n_selected; i++)
1917 g_free (priv->hidding_ids[i]);
1918 g_free(priv->hidding_ids);
1923 modest_header_view_refilter (ModestHeaderView *header_view)
1925 GtkTreeModel *model = NULL;
1926 ModestHeaderViewPrivate *priv = NULL;
1928 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1929 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1931 /* Hide cut headers */
1932 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1933 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1934 priv->status = HEADER_VIEW_INIT;
1935 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1940 * Called when an account is removed. If I'm showing a folder of the
1941 * account that has been removed then clear the view
1944 on_account_removed (TnyAccountStore *self,
1945 TnyAccount *account,
1948 ModestHeaderViewPrivate *priv = NULL;
1950 /* Ignore changes in transport accounts */
1951 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1954 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1957 TnyAccount *my_account;
1959 my_account = tny_folder_get_account (priv->folder);
1960 if (my_account == account)
1961 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1962 g_object_unref (account);
1967 modest_header_view_add_observer(ModestHeaderView *header_view,
1968 ModestHeaderViewObserver *observer)
1970 ModestHeaderViewPrivate *priv;
1972 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1973 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1975 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1977 g_mutex_lock(priv->observer_list_lock);
1978 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1979 g_mutex_unlock(priv->observer_list_lock);
1983 modest_header_view_remove_observer(ModestHeaderView *header_view,
1984 ModestHeaderViewObserver *observer)
1986 ModestHeaderViewPrivate *priv;
1988 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1989 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1991 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1993 g_mutex_lock(priv->observer_list_lock);
1994 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1995 g_mutex_unlock(priv->observer_list_lock);
1999 modest_header_view_notify_observers(ModestHeaderView *header_view,
2000 GtkTreeModel *model,
2001 const gchar *tny_folder_id)
2003 ModestHeaderViewPrivate *priv = NULL;
2005 ModestHeaderViewObserver *observer;
2008 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2010 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2012 g_mutex_lock(priv->observer_list_lock);
2013 iter = priv->observer_list;
2014 while(iter != NULL){
2015 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2016 modest_header_view_observer_update(observer, model,
2018 iter = g_slist_next(iter);
2020 g_mutex_unlock(priv->observer_list_lock);