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);
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);
1184 modest_header_view_set_folder (ModestHeaderView *self,
1186 RefreshAsyncUserCallback callback,
1189 ModestHeaderViewPrivate *priv;
1190 SetFolderHelper *info;
1191 ModestWindow *main_win;
1193 g_return_if_fail (self);
1195 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1197 main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1198 FALSE); /* don't create */
1200 g_warning ("%s: BUG: no main window", __FUNCTION__);
1205 g_mutex_lock (priv->observers_lock);
1206 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1207 g_object_unref (priv->folder);
1208 priv->folder = NULL;
1209 g_mutex_unlock (priv->observers_lock);
1213 ModestMailOperation *mail_op = NULL;
1214 GtkTreeSelection *selection;
1216 /* Set folder in the model */
1217 modest_header_view_set_folder_intern (self, folder);
1219 /* Pick my reference. Nothing to do with the mail operation */
1220 priv->folder = g_object_ref (folder);
1222 /* Clear the selection if exists */
1223 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1224 gtk_tree_selection_unselect_all(selection);
1225 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1227 /* Notify the observers that the update begins */
1228 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1231 /* create the helper */
1232 info = g_malloc0 (sizeof(SetFolderHelper));
1233 info->header_view = self;
1234 info->cb = callback;
1235 info->user_data = user_data;
1237 /* Create the mail operation (source will be the parent widget) */
1238 mail_op = modest_mail_operation_new (G_OBJECT(main_win));
1239 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1242 /* Refresh the folder asynchronously */
1243 modest_mail_operation_refresh_folder (mail_op,
1245 folder_refreshed_cb,
1249 g_object_unref (mail_op);
1251 g_mutex_lock (priv->observers_lock);
1253 if (priv->monitor) {
1254 tny_folder_monitor_stop (priv->monitor);
1255 g_object_unref (G_OBJECT (priv->monitor));
1256 priv->monitor = NULL;
1258 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1260 modest_header_view_notify_observers(self, NULL, NULL);
1262 g_mutex_unlock (priv->observers_lock);
1264 /* Notify the observers that the update is over */
1265 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1271 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1272 GtkTreeViewColumn *column, gpointer userdata)
1274 ModestHeaderView *self = NULL;
1275 ModestHeaderViewPrivate *priv = NULL;
1277 GtkTreeModel *model = NULL;
1278 TnyHeader *header = NULL;
1279 TnyHeaderFlags flags;
1281 self = MODEST_HEADER_VIEW (treeview);
1282 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1284 model = gtk_tree_view_get_model (treeview);
1285 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1288 /* get the first selected item */
1289 gtk_tree_model_get (model, &iter,
1290 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1291 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1294 /* Dont open DELETED messages */
1295 if (flags & TNY_HEADER_FLAG_DELETED) {
1296 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1301 g_signal_emit (G_OBJECT(self),
1302 signals[HEADER_ACTIVATED_SIGNAL],
1308 g_object_unref (G_OBJECT (header));
1313 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1315 GtkTreeModel *model;
1316 TnyHeader *header = NULL;
1317 GtkTreePath *path = NULL;
1319 ModestHeaderView *self;
1320 ModestHeaderViewPrivate *priv;
1321 GList *selected = NULL;
1323 g_return_if_fail (sel);
1324 g_return_if_fail (user_data);
1326 self = MODEST_HEADER_VIEW (user_data);
1327 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1329 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1330 if (selected != NULL)
1331 path = (GtkTreePath *) selected->data;
1332 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1333 return; /* msg was _un_selected */
1335 gtk_tree_model_get (model, &iter,
1336 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1340 g_signal_emit (G_OBJECT(self),
1341 signals[HEADER_SELECTED_SIGNAL],
1344 g_object_unref (G_OBJECT (header));
1346 /* free all items in 'selected' */
1347 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1348 g_list_free (selected);
1352 /* PROTECTED method. It's useful when we want to force a given
1353 selection to reload a msg. For example if we have selected a header
1354 in offline mode, when Modest become online, we want to reload the
1355 message automatically without an user click over the header */
1357 _modest_header_view_change_selection (GtkTreeSelection *selection,
1360 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1361 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1363 on_selection_changed (selection, user_data);
1367 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1374 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1378 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1382 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1390 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1397 /* static int counter = 0; */
1399 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1400 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1401 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1405 case TNY_HEADER_FLAG_ATTACHMENTS:
1407 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1408 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1409 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1410 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1412 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1413 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1415 return cmp ? cmp : t1 - t2;
1417 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1418 TnyHeader *header1 = NULL, *header2 = NULL;
1420 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1421 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1422 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1423 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1425 /* This is for making priority values respect the intuitive sort relationship
1426 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1428 if (header1 && header2) {
1429 cmp = compare_priorities (tny_header_get_priority (header1),
1430 tny_header_get_priority (header2));
1431 g_object_unref (header1);
1432 g_object_unref (header2);
1434 return cmp ? cmp : t1 - t2;
1440 return &iter1 - &iter2; /* oughhhh */
1445 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1451 /* static int counter = 0; */
1453 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1455 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1456 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1457 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1458 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1460 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1461 val2 + modest_text_utils_get_subject_prefix_len(val2),
1468 /* Drag and drop stuff */
1470 drag_data_get_cb (GtkWidget *widget,
1471 GdkDragContext *context,
1472 GtkSelectionData *selection_data,
1477 ModestHeaderView *self = NULL;
1478 ModestHeaderViewPrivate *priv = NULL;
1480 self = MODEST_HEADER_VIEW (widget);
1481 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1483 /* Set the data. Do not use the current selection because it
1484 could be different than the selection at the beginning of
1486 modest_dnd_selection_data_set_paths (selection_data,
1487 priv->drag_begin_cached_selected_rows);
1491 * We're caching the selected rows at the beginning because the
1492 * selection could change between drag-begin and drag-data-get, for
1493 * example if we have a set of rows already selected, and then we
1494 * click in one of them (without SHIFT key pressed) and begin a drag,
1495 * the selection at that moment contains all the selected lines, but
1496 * after dropping the selection, the release event provokes that only
1497 * the row used to begin the drag is selected, so at the end the
1498 * drag&drop affects only one rows instead of all the selected ones.
1502 drag_begin_cb (GtkWidget *widget,
1503 GdkDragContext *context,
1506 ModestHeaderView *self = NULL;
1507 ModestHeaderViewPrivate *priv = NULL;
1508 GtkTreeSelection *selection;
1510 self = MODEST_HEADER_VIEW (widget);
1511 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1513 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1514 priv->drag_begin_cached_selected_rows =
1515 gtk_tree_selection_get_selected_rows (selection, NULL);
1519 * We use the drag-end signal to clear the cached selection, we use
1520 * this because this allways happens, whether or not the d&d was a
1524 drag_end_cb (GtkWidget *widget,
1528 ModestHeaderView *self = NULL;
1529 ModestHeaderViewPrivate *priv = NULL;
1531 self = MODEST_HEADER_VIEW (widget);
1532 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1534 /* Free cached data */
1535 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1536 g_list_free (priv->drag_begin_cached_selected_rows);
1537 priv->drag_begin_cached_selected_rows = NULL;
1540 /* Header view drag types */
1541 const GtkTargetEntry header_view_drag_types[] = {
1542 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1546 enable_drag_and_drop (GtkWidget *self)
1548 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1549 header_view_drag_types,
1550 G_N_ELEMENTS (header_view_drag_types),
1551 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1555 disable_drag_and_drop (GtkWidget *self)
1557 gtk_drag_source_unset (self);
1561 setup_drag_and_drop (GtkWidget *self)
1563 enable_drag_and_drop(self);
1564 g_signal_connect(G_OBJECT (self), "drag_data_get",
1565 G_CALLBACK(drag_data_get_cb), NULL);
1567 g_signal_connect(G_OBJECT (self), "drag_begin",
1568 G_CALLBACK(drag_begin_cb), NULL);
1570 g_signal_connect(G_OBJECT (self), "drag_end",
1571 G_CALLBACK(drag_end_cb), NULL);
1574 static GtkTreePath *
1575 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1577 GtkTreePath *path = NULL;
1578 GtkTreeSelection *sel = NULL;
1581 sel = gtk_tree_view_get_selection(self);
1582 rows = gtk_tree_selection_get_selected_rows (sel, model);
1584 if ((rows == NULL) || (g_list_length(rows) != 1))
1587 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1592 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1599 * This function moves the tree view scroll to the current selected
1600 * row when the widget grabs the focus
1603 on_focus_in (GtkWidget *self,
1604 GdkEventFocus *event,
1607 GtkTreeSelection *selection;
1608 GtkTreeModel *model;
1609 GList *selected = NULL;
1610 GtkTreePath *selected_path = NULL;
1612 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1616 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1617 /* If none selected yet, pick the first one */
1618 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1622 /* Return if the model is empty */
1623 if (!gtk_tree_model_get_iter_first (model, &iter))
1626 path = gtk_tree_model_get_path (model, &iter);
1627 gtk_tree_selection_select_path (selection, path);
1628 gtk_tree_path_free (path);
1631 /* Need to get the all the rows because is selection multiple */
1632 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1633 if (selected == NULL) return FALSE;
1634 selected_path = (GtkTreePath *) selected->data;
1636 /* Check if we need to scroll */
1637 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1638 GtkTreePath *start_path = NULL;
1639 GtkTreePath *end_path = NULL;
1640 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1644 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1645 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1647 /* Scroll to first path */
1648 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1657 gtk_tree_path_free (start_path);
1659 gtk_tree_path_free (end_path);
1661 #endif /* GTK_CHECK_VERSION */
1664 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1665 g_list_free (selected);
1671 on_focus_out (GtkWidget *self,
1672 GdkEventFocus *event,
1676 if (!gtk_widget_is_focus (self)) {
1677 GtkTreeSelection *selection = NULL;
1678 GList *selected_rows = NULL;
1679 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1680 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1681 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1682 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1683 gtk_tree_selection_unselect_all (selection);
1684 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1685 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1686 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1687 g_list_free (selected_rows);
1694 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1696 enable_drag_and_drop(self);
1701 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1703 GtkTreeSelection *selection = NULL;
1704 GtkTreePath *path = NULL;
1705 gboolean already_selected = FALSE;
1707 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1708 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1709 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1712 /* Enable drag and drop onlly if the user clicks on a row that
1713 it's already selected. If not, let him select items using
1715 if (!already_selected) {
1716 disable_drag_and_drop(self);
1720 gtk_tree_path_free(path);
1727 folder_monitor_update (TnyFolderObserver *self,
1728 TnyFolderChange *change)
1730 ModestHeaderViewPrivate *priv = NULL;
1731 TnyFolderChangeChanged changed;
1732 TnyFolder *folder = NULL;
1734 changed = tny_folder_change_get_changed (change);
1736 /* Do not notify the observers if the folder of the header
1737 view has changed before this call to the observer
1739 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1740 folder = tny_folder_change_get_folder (change);
1741 if (folder != priv->folder)
1744 /* Check folder count */
1745 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1746 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1748 g_mutex_lock (priv->observers_lock);
1750 /* Emit signal to evaluate how headers changes affects
1751 to the window view */
1752 g_signal_emit (G_OBJECT(self),
1753 signals[MSG_COUNT_CHANGED_SIGNAL],
1756 /* Added or removed headers, so data stored on cliboard are invalid */
1757 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1758 modest_email_clipboard_clear (priv->clipboard);
1760 g_mutex_unlock (priv->observers_lock);
1766 g_object_unref (folder);
1770 modest_header_view_is_empty (ModestHeaderView *self)
1772 ModestHeaderViewPrivate *priv;
1774 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1776 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1778 return priv->status == HEADER_VIEW_EMPTY;
1782 modest_header_view_clear (ModestHeaderView *self)
1784 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1786 modest_header_view_set_folder (self, NULL, NULL, NULL);
1790 modest_header_view_copy_selection (ModestHeaderView *header_view)
1792 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1794 /* Copy selection */
1795 _clipboard_set_selected_data (header_view, FALSE);
1799 modest_header_view_cut_selection (ModestHeaderView *header_view)
1801 ModestHeaderViewPrivate *priv = NULL;
1802 const gchar **hidding = NULL;
1803 guint i, n_selected;
1805 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1807 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1809 /* Copy selection */
1810 _clipboard_set_selected_data (header_view, TRUE);
1812 /* Get hidding ids */
1813 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1815 /* Clear hidding array created by previous cut operation */
1816 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1818 /* Copy hidding array */
1819 priv->n_selected = n_selected;
1820 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1821 for (i=0; i < n_selected; i++)
1822 priv->hidding_ids[i] = g_strdup(hidding[i]);
1824 /* Hide cut headers */
1825 modest_header_view_refilter (header_view);
1832 _clipboard_set_selected_data (ModestHeaderView *header_view,
1835 ModestHeaderViewPrivate *priv = NULL;
1836 TnyList *headers = NULL;
1838 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1839 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1841 /* Set selected data on clipboard */
1842 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1843 headers = modest_header_view_get_selected_headers (header_view);
1844 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1847 g_object_unref (headers);
1853 filter_row (GtkTreeModel *model,
1857 ModestHeaderViewPrivate *priv = NULL;
1858 TnyHeaderFlags flags;
1859 TnyHeader *header = NULL;
1862 gboolean visible = TRUE;
1863 gboolean found = FALSE;
1864 GValue value = {0,};
1866 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1867 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1869 /* Get header from model */
1870 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
1871 flags = (TnyHeaderFlags) g_value_get_int (&value);
1872 g_value_unset (&value);
1873 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
1874 header = (TnyHeader *) g_value_get_object (&value);
1875 g_value_unset (&value);
1877 /* Hide mark as deleted heders */
1878 if (flags & TNY_HEADER_FLAG_DELETED) {
1883 /* If no data on clipboard, return always TRUE */
1884 if (modest_email_clipboard_cleared(priv->clipboard)) {
1889 /* Get message id from header (ensure is a valid id) */
1890 if (!header) return FALSE;
1893 if (priv->hidding_ids != NULL) {
1894 id = g_strdup(tny_header_get_message_id (header));
1895 for (i=0; i < priv->n_selected && !found; i++)
1896 if (priv->hidding_ids[i] != NULL && id != NULL)
1897 found = (!strcmp (priv->hidding_ids[i], id));
1904 priv->status = ((gboolean) priv->status) && !visible;
1910 _clear_hidding_filter (ModestHeaderView *header_view)
1912 ModestHeaderViewPrivate *priv = NULL;
1915 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1916 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1918 if (priv->hidding_ids != NULL) {
1919 for (i=0; i < priv->n_selected; i++)
1920 g_free (priv->hidding_ids[i]);
1921 g_free(priv->hidding_ids);
1926 modest_header_view_refilter (ModestHeaderView *header_view)
1928 GtkTreeModel *model = NULL;
1929 ModestHeaderViewPrivate *priv = NULL;
1931 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1932 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1934 /* Hide cut headers */
1935 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1936 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1937 priv->status = HEADER_VIEW_INIT;
1938 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1943 * Called when an account is removed. If I'm showing a folder of the
1944 * account that has been removed then clear the view
1947 on_account_removed (TnyAccountStore *self,
1948 TnyAccount *account,
1951 ModestHeaderViewPrivate *priv = NULL;
1953 /* Ignore changes in transport accounts */
1954 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1957 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1960 TnyAccount *my_account;
1962 my_account = tny_folder_get_account (priv->folder);
1963 if (my_account == account)
1964 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1965 g_object_unref (account);
1970 modest_header_view_add_observer(ModestHeaderView *header_view,
1971 ModestHeaderViewObserver *observer)
1973 ModestHeaderViewPrivate *priv;
1975 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1976 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1978 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1980 g_mutex_lock(priv->observer_list_lock);
1981 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1982 g_mutex_unlock(priv->observer_list_lock);
1986 modest_header_view_remove_observer(ModestHeaderView *header_view,
1987 ModestHeaderViewObserver *observer)
1989 ModestHeaderViewPrivate *priv;
1991 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1992 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1994 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1996 g_mutex_lock(priv->observer_list_lock);
1997 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1998 g_mutex_unlock(priv->observer_list_lock);
2002 modest_header_view_notify_observers(ModestHeaderView *header_view,
2003 GtkTreeModel *model,
2004 const gchar *tny_folder_id)
2006 ModestHeaderViewPrivate *priv = NULL;
2008 ModestHeaderViewObserver *observer;
2011 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2013 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2015 g_mutex_lock(priv->observer_list_lock);
2016 iter = priv->observer_list;
2017 while(iter != NULL){
2018 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2019 modest_header_view_observer_update(observer, model,
2021 iter = g_slist_next(iter);
2023 g_mutex_unlock(priv->observer_list_lock);