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>
41 #include <modest-debug.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);
933 sel = gtk_tree_view_get_selection(header_view);
934 if(!gtk_tree_selection_count_selected_rows(sel))
935 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
936 /* Prevent the widget from getting the focus
937 when selecting the first item */
938 g_object_set(header_view, "can-focus", FALSE, NULL);
939 gtk_tree_selection_select_iter(sel, &tree_iter);
940 g_object_set(header_view, "can-focus", TRUE, NULL);
947 * This function sets a sortable model in the header view. It's just
948 * used for developing purposes, because it only does a
949 * gtk_tree_view_set_model
952 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
954 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
955 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
956 /* GtkTreeModel *old_model; */
957 /* ModestHeaderViewPrivate *priv; */
958 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
959 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
961 /* /\* Set new model *\/ */
962 /* gtk_tree_view_set_model (header_view, model); */
964 gtk_tree_view_set_model (header_view, model);
968 modest_header_view_get_folder (ModestHeaderView *self)
970 ModestHeaderViewPrivate *priv;
972 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
974 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
977 g_object_ref (priv->folder);
983 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
987 ModestHeaderViewPrivate *priv;
988 GList *cols, *cursor;
989 GtkTreeModel *filter_model, *sortable;
991 GtkSortType sort_type;
993 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
995 headers = TNY_LIST (tny_gtk_header_list_model_new ());
997 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
998 folder, FALSE, NULL, NULL, NULL);
1000 /* Add IDLE observer (monitor) and another folder observer for
1001 new messages (self) */
1002 g_mutex_lock (priv->observers_lock);
1003 if (priv->monitor) {
1004 tny_folder_monitor_stop (priv->monitor);
1005 g_object_unref (G_OBJECT (priv->monitor));
1007 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1008 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1009 tny_folder_monitor_start (priv->monitor);
1010 g_mutex_unlock (priv->observers_lock);
1012 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
1013 g_object_unref (G_OBJECT (headers));
1015 /* Init filter_row function to examine empty status */
1016 priv->status = HEADER_VIEW_INIT;
1018 /* Create a tree model filter to hide and show rows for cut operations */
1019 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1020 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1024 g_object_unref (G_OBJECT (sortable));
1026 /* install our special sorting functions */
1027 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1029 /* Restore sort column id */
1031 type = modest_tny_folder_guess_folder_type (folder);
1032 if (type == TNY_FOLDER_TYPE_INVALID)
1033 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1035 sort_colid = modest_header_view_get_sort_column_id (self, type);
1036 sort_type = modest_header_view_get_sort_type (self, type);
1037 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1040 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1041 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1042 (GtkTreeIterCompareFunc) cmp_rows,
1044 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1045 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1046 (GtkTreeIterCompareFunc) cmp_subject_rows,
1051 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1052 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1053 tny_folder_get_id(folder));
1054 g_object_unref (G_OBJECT (filter_model));
1055 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1056 /* g_object_unref (G_OBJECT (sortable)); */
1063 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1065 GtkSortType sort_type)
1067 ModestHeaderViewPrivate *priv = NULL;
1068 GtkTreeModel *tree_filter, *sortable = NULL;
1071 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1072 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1074 /* Get model and private data */
1075 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1076 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1077 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1078 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1080 /* Sort tree model */
1081 type = modest_tny_folder_guess_folder_type (priv->folder);
1082 if (type == TNY_FOLDER_TYPE_INVALID)
1083 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1085 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1088 /* Store new sort parameters */
1089 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1094 modest_header_view_set_sort_params (ModestHeaderView *self,
1096 GtkSortType sort_type,
1099 ModestHeaderViewPrivate *priv;
1100 ModestHeaderViewStyle style;
1102 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1103 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1104 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1106 style = modest_header_view_get_style (self);
1107 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1109 priv->sort_colid[style][type] = sort_colid;
1110 priv->sort_type[style][type] = sort_type;
1114 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1117 ModestHeaderViewPrivate *priv;
1118 ModestHeaderViewStyle style;
1120 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1121 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1123 style = modest_header_view_get_style (self);
1124 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1126 return priv->sort_colid[style][type];
1130 modest_header_view_get_sort_type (ModestHeaderView *self,
1133 ModestHeaderViewPrivate *priv;
1134 ModestHeaderViewStyle style;
1136 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1137 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1139 style = modest_header_view_get_style (self);
1140 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1142 return priv->sort_type[style][type];
1146 ModestHeaderView *header_view;
1147 RefreshAsyncUserCallback cb;
1152 folder_refreshed_cb (ModestMailOperation *mail_op,
1156 ModestHeaderViewPrivate *priv;
1157 SetFolderHelper *info;
1159 info = (SetFolderHelper*) user_data;
1161 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1165 info->cb (mail_op, folder, info->user_data);
1167 /* Start the folder count changes observer. We do not need it
1168 before the refresh. Note that the monitor could still be
1169 called for this refresh but now we know that the callback
1170 was previously called */
1171 g_mutex_lock (priv->observers_lock);
1172 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1173 g_mutex_unlock (priv->observers_lock);
1175 /* Notify the observers that the update is over */
1176 g_signal_emit (G_OBJECT (info->header_view),
1177 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1180 g_object_unref (info->header_view);
1185 modest_header_view_set_folder (ModestHeaderView *self,
1187 RefreshAsyncUserCallback callback,
1190 ModestHeaderViewPrivate *priv;
1191 SetFolderHelper *info;
1192 ModestWindow *main_win;
1194 g_return_if_fail (self);
1196 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1198 main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1199 FALSE); /* don't create */
1201 g_warning ("%s: BUG: no main window", __FUNCTION__);
1206 g_mutex_lock (priv->observers_lock);
1207 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1208 g_object_unref (priv->folder);
1209 priv->folder = NULL;
1210 g_mutex_unlock (priv->observers_lock);
1214 ModestMailOperation *mail_op = NULL;
1215 GtkTreeSelection *selection;
1217 /* Set folder in the model */
1218 modest_header_view_set_folder_intern (self, folder);
1220 /* Pick my reference. Nothing to do with the mail operation */
1221 priv->folder = g_object_ref (folder);
1223 /* Clear the selection if exists */
1224 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1225 gtk_tree_selection_unselect_all(selection);
1226 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1228 /* Notify the observers that the update begins */
1229 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1232 /* create the helper */
1233 info = g_malloc0 (sizeof(SetFolderHelper));
1234 info->header_view = g_object_ref (self);
1235 info->cb = callback;
1236 info->user_data = user_data;
1238 /* Create the mail operation (source will be the parent widget) */
1239 mail_op = modest_mail_operation_new (G_OBJECT(main_win));
1240 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1243 /* Refresh the folder asynchronously */
1244 modest_mail_operation_refresh_folder (mail_op,
1246 folder_refreshed_cb,
1250 g_object_unref (mail_op);
1252 g_mutex_lock (priv->observers_lock);
1254 if (priv->monitor) {
1255 tny_folder_monitor_stop (priv->monitor);
1256 g_object_unref (G_OBJECT (priv->monitor));
1257 priv->monitor = NULL;
1259 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1261 modest_header_view_notify_observers(self, NULL, NULL);
1263 g_mutex_unlock (priv->observers_lock);
1265 /* Notify the observers that the update is over */
1266 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1272 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1273 GtkTreeViewColumn *column, gpointer userdata)
1275 ModestHeaderView *self = NULL;
1276 ModestHeaderViewPrivate *priv = NULL;
1278 GtkTreeModel *model = NULL;
1279 TnyHeader *header = NULL;
1280 TnyHeaderFlags flags;
1282 self = MODEST_HEADER_VIEW (treeview);
1283 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1285 model = gtk_tree_view_get_model (treeview);
1286 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1289 /* get the first selected item */
1290 gtk_tree_model_get (model, &iter,
1291 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1292 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1295 /* Dont open DELETED messages */
1296 if (flags & TNY_HEADER_FLAG_DELETED) {
1297 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1302 g_signal_emit (G_OBJECT(self),
1303 signals[HEADER_ACTIVATED_SIGNAL],
1309 g_object_unref (G_OBJECT (header));
1314 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1316 GtkTreeModel *model;
1317 TnyHeader *header = NULL;
1318 GtkTreePath *path = NULL;
1320 ModestHeaderView *self;
1321 ModestHeaderViewPrivate *priv;
1322 GList *selected = NULL;
1324 g_return_if_fail (sel);
1325 g_return_if_fail (user_data);
1327 self = MODEST_HEADER_VIEW (user_data);
1328 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1330 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1331 if (selected != NULL)
1332 path = (GtkTreePath *) selected->data;
1333 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1334 return; /* msg was _un_selected */
1336 gtk_tree_model_get (model, &iter,
1337 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1341 g_signal_emit (G_OBJECT(self),
1342 signals[HEADER_SELECTED_SIGNAL],
1345 g_object_unref (G_OBJECT (header));
1347 /* free all items in 'selected' */
1348 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1349 g_list_free (selected);
1353 /* PROTECTED method. It's useful when we want to force a given
1354 selection to reload a msg. For example if we have selected a header
1355 in offline mode, when Modest become online, we want to reload the
1356 message automatically without an user click over the header */
1358 _modest_header_view_change_selection (GtkTreeSelection *selection,
1361 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1362 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1364 on_selection_changed (selection, user_data);
1368 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1375 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1379 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1383 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1391 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1398 /* static int counter = 0; */
1400 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1401 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1402 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1406 case TNY_HEADER_FLAG_ATTACHMENTS:
1408 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1409 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1410 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1411 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1413 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1414 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1416 return cmp ? cmp : t1 - t2;
1418 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1419 TnyHeader *header1 = NULL, *header2 = NULL;
1421 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1422 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1423 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1424 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1426 /* This is for making priority values respect the intuitive sort relationship
1427 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1429 if (header1 && header2) {
1430 cmp = compare_priorities (tny_header_get_priority (header1),
1431 tny_header_get_priority (header2));
1432 g_object_unref (header1);
1433 g_object_unref (header2);
1435 return cmp ? cmp : t1 - t2;
1441 return &iter1 - &iter2; /* oughhhh */
1446 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1452 /* static int counter = 0; */
1454 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1456 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1457 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1458 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1459 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1461 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1462 val2 + modest_text_utils_get_subject_prefix_len(val2),
1469 /* Drag and drop stuff */
1471 drag_data_get_cb (GtkWidget *widget,
1472 GdkDragContext *context,
1473 GtkSelectionData *selection_data,
1478 ModestHeaderView *self = NULL;
1479 ModestHeaderViewPrivate *priv = NULL;
1481 self = MODEST_HEADER_VIEW (widget);
1482 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1484 /* Set the data. Do not use the current selection because it
1485 could be different than the selection at the beginning of
1487 modest_dnd_selection_data_set_paths (selection_data,
1488 priv->drag_begin_cached_selected_rows);
1492 * We're caching the selected rows at the beginning because the
1493 * selection could change between drag-begin and drag-data-get, for
1494 * example if we have a set of rows already selected, and then we
1495 * click in one of them (without SHIFT key pressed) and begin a drag,
1496 * the selection at that moment contains all the selected lines, but
1497 * after dropping the selection, the release event provokes that only
1498 * the row used to begin the drag is selected, so at the end the
1499 * drag&drop affects only one rows instead of all the selected ones.
1503 drag_begin_cb (GtkWidget *widget,
1504 GdkDragContext *context,
1507 ModestHeaderView *self = NULL;
1508 ModestHeaderViewPrivate *priv = NULL;
1509 GtkTreeSelection *selection;
1511 self = MODEST_HEADER_VIEW (widget);
1512 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1514 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1515 priv->drag_begin_cached_selected_rows =
1516 gtk_tree_selection_get_selected_rows (selection, NULL);
1520 * We use the drag-end signal to clear the cached selection, we use
1521 * this because this allways happens, whether or not the d&d was a
1525 drag_end_cb (GtkWidget *widget,
1529 ModestHeaderView *self = NULL;
1530 ModestHeaderViewPrivate *priv = NULL;
1532 self = MODEST_HEADER_VIEW (widget);
1533 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1535 /* Free cached data */
1536 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1537 g_list_free (priv->drag_begin_cached_selected_rows);
1538 priv->drag_begin_cached_selected_rows = NULL;
1541 /* Header view drag types */
1542 const GtkTargetEntry header_view_drag_types[] = {
1543 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1547 enable_drag_and_drop (GtkWidget *self)
1549 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1550 header_view_drag_types,
1551 G_N_ELEMENTS (header_view_drag_types),
1552 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1556 disable_drag_and_drop (GtkWidget *self)
1558 gtk_drag_source_unset (self);
1562 setup_drag_and_drop (GtkWidget *self)
1564 enable_drag_and_drop(self);
1565 g_signal_connect(G_OBJECT (self), "drag_data_get",
1566 G_CALLBACK(drag_data_get_cb), NULL);
1568 g_signal_connect(G_OBJECT (self), "drag_begin",
1569 G_CALLBACK(drag_begin_cb), NULL);
1571 g_signal_connect(G_OBJECT (self), "drag_end",
1572 G_CALLBACK(drag_end_cb), NULL);
1575 static GtkTreePath *
1576 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1578 GtkTreePath *path = NULL;
1579 GtkTreeSelection *sel = NULL;
1582 sel = gtk_tree_view_get_selection(self);
1583 rows = gtk_tree_selection_get_selected_rows (sel, model);
1585 if ((rows == NULL) || (g_list_length(rows) != 1))
1588 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1593 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1600 * This function moves the tree view scroll to the current selected
1601 * row when the widget grabs the focus
1604 on_focus_in (GtkWidget *self,
1605 GdkEventFocus *event,
1608 GtkTreeSelection *selection;
1609 GtkTreeModel *model;
1610 GList *selected = NULL;
1611 GtkTreePath *selected_path = NULL;
1613 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1617 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1618 /* If none selected yet, pick the first one */
1619 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1623 /* Return if the model is empty */
1624 if (!gtk_tree_model_get_iter_first (model, &iter))
1627 path = gtk_tree_model_get_path (model, &iter);
1628 gtk_tree_selection_select_path (selection, path);
1629 gtk_tree_path_free (path);
1632 /* Need to get the all the rows because is selection multiple */
1633 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1634 if (selected == NULL) return FALSE;
1635 selected_path = (GtkTreePath *) selected->data;
1637 /* Check if we need to scroll */
1638 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1639 GtkTreePath *start_path = NULL;
1640 GtkTreePath *end_path = NULL;
1641 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1645 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1646 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1648 /* Scroll to first path */
1649 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1658 gtk_tree_path_free (start_path);
1660 gtk_tree_path_free (end_path);
1662 #endif /* GTK_CHECK_VERSION */
1665 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1666 g_list_free (selected);
1672 on_focus_out (GtkWidget *self,
1673 GdkEventFocus *event,
1677 if (!gtk_widget_is_focus (self)) {
1678 GtkTreeSelection *selection = NULL;
1679 GList *selected_rows = NULL;
1680 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1681 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1682 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1683 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1684 gtk_tree_selection_unselect_all (selection);
1685 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1686 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1687 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1688 g_list_free (selected_rows);
1695 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1697 enable_drag_and_drop(self);
1702 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1704 GtkTreeSelection *selection = NULL;
1705 GtkTreePath *path = NULL;
1706 gboolean already_selected = FALSE;
1708 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1709 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1710 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1713 /* Enable drag and drop onlly if the user clicks on a row that
1714 it's already selected. If not, let him select items using
1716 if (!already_selected) {
1717 disable_drag_and_drop(self);
1721 gtk_tree_path_free(path);
1728 folder_monitor_update (TnyFolderObserver *self,
1729 TnyFolderChange *change)
1731 ModestHeaderViewPrivate *priv = NULL;
1732 TnyFolderChangeChanged changed;
1733 TnyFolder *folder = NULL;
1735 changed = tny_folder_change_get_changed (change);
1737 /* Do not notify the observers if the folder of the header
1738 view has changed before this call to the observer
1740 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1741 folder = tny_folder_change_get_folder (change);
1742 if (folder != priv->folder)
1745 MODEST_DEBUG_BLOCK (
1746 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1747 g_print ("ADDED %d/%d (r/t) \n",
1748 tny_folder_change_get_new_unread_count (change),
1749 tny_folder_change_get_new_all_count (change));
1750 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1751 g_print ("ALL COUNT %d\n",
1752 tny_folder_change_get_new_all_count (change));
1753 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1754 g_print ("UNREAD COUNT %d\n",
1755 tny_folder_change_get_new_unread_count (change));
1756 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1757 g_print ("EXPUNGED %d/%d (r/t) \n",
1758 tny_folder_change_get_new_unread_count (change),
1759 tny_folder_change_get_new_all_count (change));
1760 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1761 g_print ("FOLDER RENAME\n");
1762 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1763 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1764 tny_folder_change_get_new_unread_count (change),
1765 tny_folder_change_get_new_all_count (change));
1766 g_print ("---------------------------------------------------\n");
1769 /* Check folder count */
1770 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1771 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1773 g_mutex_lock (priv->observers_lock);
1775 /* Emit signal to evaluate how headers changes affects
1776 to the window view */
1777 g_signal_emit (G_OBJECT(self),
1778 signals[MSG_COUNT_CHANGED_SIGNAL],
1781 /* Added or removed headers, so data stored on cliboard are invalid */
1782 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1783 modest_email_clipboard_clear (priv->clipboard);
1785 g_mutex_unlock (priv->observers_lock);
1791 g_object_unref (folder);
1795 modest_header_view_is_empty (ModestHeaderView *self)
1797 ModestHeaderViewPrivate *priv;
1799 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1801 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1803 return priv->status == HEADER_VIEW_EMPTY;
1807 modest_header_view_clear (ModestHeaderView *self)
1809 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1811 modest_header_view_set_folder (self, NULL, NULL, NULL);
1815 modest_header_view_copy_selection (ModestHeaderView *header_view)
1817 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1819 /* Copy selection */
1820 _clipboard_set_selected_data (header_view, FALSE);
1824 modest_header_view_cut_selection (ModestHeaderView *header_view)
1826 ModestHeaderViewPrivate *priv = NULL;
1827 const gchar **hidding = NULL;
1828 guint i, n_selected;
1830 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1832 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1834 /* Copy selection */
1835 _clipboard_set_selected_data (header_view, TRUE);
1837 /* Get hidding ids */
1838 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1840 /* Clear hidding array created by previous cut operation */
1841 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1843 /* Copy hidding array */
1844 priv->n_selected = n_selected;
1845 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1846 for (i=0; i < n_selected; i++)
1847 priv->hidding_ids[i] = g_strdup(hidding[i]);
1849 /* Hide cut headers */
1850 modest_header_view_refilter (header_view);
1857 _clipboard_set_selected_data (ModestHeaderView *header_view,
1860 ModestHeaderViewPrivate *priv = NULL;
1861 TnyList *headers = NULL;
1863 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1864 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1866 /* Set selected data on clipboard */
1867 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1868 headers = modest_header_view_get_selected_headers (header_view);
1869 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1872 g_object_unref (headers);
1878 filter_row (GtkTreeModel *model,
1882 ModestHeaderViewPrivate *priv = NULL;
1883 TnyHeaderFlags flags;
1884 TnyHeader *header = NULL;
1887 gboolean visible = TRUE;
1888 gboolean found = FALSE;
1889 GValue value = {0,};
1891 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1892 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1894 /* Get header from model */
1895 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
1896 flags = (TnyHeaderFlags) g_value_get_int (&value);
1897 g_value_unset (&value);
1898 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
1899 header = (TnyHeader *) g_value_get_object (&value);
1900 g_value_unset (&value);
1902 /* Hide mark as deleted heders */
1903 if (flags & TNY_HEADER_FLAG_DELETED) {
1908 /* If no data on clipboard, return always TRUE */
1909 if (modest_email_clipboard_cleared(priv->clipboard)) {
1914 /* Get message id from header (ensure is a valid id) */
1921 if (priv->hidding_ids != NULL) {
1922 id = g_strdup(tny_header_get_message_id (header));
1923 for (i=0; i < priv->n_selected && !found; i++)
1924 if (priv->hidding_ids[i] != NULL && id != NULL)
1925 found = (!strcmp (priv->hidding_ids[i], id));
1932 priv->status = ((gboolean) priv->status) && !visible;
1938 _clear_hidding_filter (ModestHeaderView *header_view)
1940 ModestHeaderViewPrivate *priv = NULL;
1943 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1944 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1946 if (priv->hidding_ids != NULL) {
1947 for (i=0; i < priv->n_selected; i++)
1948 g_free (priv->hidding_ids[i]);
1949 g_free(priv->hidding_ids);
1954 modest_header_view_refilter (ModestHeaderView *header_view)
1956 GtkTreeModel *model = NULL;
1957 ModestHeaderViewPrivate *priv = NULL;
1959 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1960 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1962 /* Hide cut headers */
1963 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1964 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1965 priv->status = HEADER_VIEW_INIT;
1966 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1971 * Called when an account is removed. If I'm showing a folder of the
1972 * account that has been removed then clear the view
1975 on_account_removed (TnyAccountStore *self,
1976 TnyAccount *account,
1979 ModestHeaderViewPrivate *priv = NULL;
1981 /* Ignore changes in transport accounts */
1982 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1985 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1988 TnyAccount *my_account;
1990 my_account = tny_folder_get_account (priv->folder);
1991 if (my_account == account)
1992 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1993 g_object_unref (account);
1998 modest_header_view_add_observer(ModestHeaderView *header_view,
1999 ModestHeaderViewObserver *observer)
2001 ModestHeaderViewPrivate *priv;
2003 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2004 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2006 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2008 g_mutex_lock(priv->observer_list_lock);
2009 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2010 g_mutex_unlock(priv->observer_list_lock);
2014 modest_header_view_remove_observer(ModestHeaderView *header_view,
2015 ModestHeaderViewObserver *observer)
2017 ModestHeaderViewPrivate *priv;
2019 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2020 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2022 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2024 g_mutex_lock(priv->observer_list_lock);
2025 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2026 g_mutex_unlock(priv->observer_list_lock);
2030 modest_header_view_notify_observers(ModestHeaderView *header_view,
2031 GtkTreeModel *model,
2032 const gchar *tny_folder_id)
2034 ModestHeaderViewPrivate *priv = NULL;
2036 ModestHeaderViewObserver *observer;
2039 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2041 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2043 g_mutex_lock(priv->observer_list_lock);
2044 iter = priv->observer_list;
2045 while(iter != NULL){
2046 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2047 modest_header_view_observer_update(observer, model,
2049 iter = g_slist_next(iter);
2051 g_mutex_unlock(priv->observer_list_lock);