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 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
348 /* FIXME: check whether these renderers need to be freed */
349 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
350 renderer_attach = gtk_cell_renderer_pixbuf_new ();
351 renderer_priority = gtk_cell_renderer_pixbuf_new ();
352 renderer_header = gtk_cell_renderer_text_new ();
354 renderer_compact_header = modest_vbox_cell_renderer_new ();
355 renderer_recpt_box = modest_hbox_cell_renderer_new ();
356 renderer_subject_box = modest_hbox_cell_renderer_new ();
357 renderer_recpt = gtk_cell_renderer_text_new ();
358 renderer_subject = gtk_cell_renderer_text_new ();
359 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
361 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
362 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
363 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
364 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
365 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
366 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
367 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
368 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
369 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
370 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
371 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
372 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
373 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
374 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
376 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
377 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
378 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
379 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
380 g_object_set(G_OBJECT(renderer_header),
381 "ellipsize", PANGO_ELLIPSIZE_END,
383 g_object_set (G_OBJECT (renderer_subject),
384 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
386 g_object_set (G_OBJECT (renderer_recpt),
387 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
389 g_object_set(G_OBJECT(renderer_compact_date_or_status),
390 "xalign", 1.0, "yalign", 0.0,
392 g_object_set (G_OBJECT (renderer_priority),
393 "yalign", 1.0, NULL);
394 g_object_set (G_OBJECT (renderer_attach),
395 "yalign", 0.0, NULL);
397 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
398 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
399 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
401 remove_all_columns (self);
403 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
404 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
405 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
406 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
408 /* Add new columns */
409 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
410 ModestHeaderViewColumn col =
411 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
413 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
414 g_printerr ("modest: invalid column %d in column list\n", col);
420 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
421 column = get_new_column (_("M"), renderer_msgtype, FALSE,
422 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
424 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
426 gtk_tree_view_column_set_fixed_width (column, 45);
429 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
430 column = get_new_column (_("A"), renderer_attach, FALSE,
431 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
433 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
435 gtk_tree_view_column_set_fixed_width (column, 45);
439 case MODEST_HEADER_VIEW_COLUMN_FROM:
440 column = get_new_column (_("From"), renderer_header, TRUE,
441 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
443 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
444 GINT_TO_POINTER(TRUE));
447 case MODEST_HEADER_VIEW_COLUMN_TO:
448 column = get_new_column (_("To"), renderer_header, TRUE,
449 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
451 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
452 GINT_TO_POINTER(FALSE));
455 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
456 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
457 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
459 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
460 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
461 compact_column = column;
464 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
465 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
466 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
468 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
469 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
470 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
471 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
472 compact_column = column;
476 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
477 column = get_new_column (_("Subject"), renderer_header, TRUE,
478 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
480 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
484 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
485 column = get_new_column (_("Received"), renderer_header, TRUE,
486 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
488 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
489 GINT_TO_POINTER(TRUE));
492 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
493 column = get_new_column (_("Sent"), renderer_header, TRUE,
494 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
496 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
497 GINT_TO_POINTER(FALSE));
500 case MODEST_HEADER_VIEW_COLUMN_SIZE:
501 column = get_new_column (_("Size"), renderer_header, TRUE,
502 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
504 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
507 case MODEST_HEADER_VIEW_COLUMN_STATUS:
508 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
509 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
511 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
516 g_return_val_if_reached(FALSE);
519 /* we keep the column id around */
520 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
521 GINT_TO_POINTER(col));
523 /* we need this ptr when sorting the rows */
524 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
526 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
530 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
531 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
532 (GtkTreeIterCompareFunc) cmp_rows,
533 compact_column, NULL);
534 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
535 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
536 (GtkTreeIterCompareFunc) cmp_subject_rows,
537 compact_column, NULL);
545 modest_header_view_init (ModestHeaderView *obj)
547 ModestHeaderViewPrivate *priv;
550 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
554 priv->monitor = NULL;
555 priv->observers_lock = g_mutex_new ();
557 priv->status = HEADER_VIEW_INIT;
559 priv->observer_list_lock = g_mutex_new();
560 priv->observer_list = NULL;
562 priv->clipboard = modest_runtime_get_email_clipboard ();
563 priv->hidding_ids = NULL;
564 priv->n_selected = 0;
565 priv->selection_changed_handler = 0;
566 priv->acc_removed_handler = 0;
568 /* Sort parameters */
569 for (j=0; j < 2; j++) {
570 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
571 priv->sort_colid[j][i] = -1;
572 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
576 setup_drag_and_drop (GTK_WIDGET(obj));
580 modest_header_view_dispose (GObject *obj)
582 ModestHeaderView *self;
583 ModestHeaderViewPrivate *priv;
584 GtkTreeSelection *sel;
586 self = MODEST_HEADER_VIEW(obj);
587 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
589 /* Free in the dispose to avoid unref cycles */
591 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
592 g_object_unref (G_OBJECT (priv->folder));
596 /* We need to do this here in the dispose because the
597 selection won't exist when finalizing */
598 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
599 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
600 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
601 priv->selection_changed_handler = 0;
604 G_OBJECT_CLASS(parent_class)->dispose (obj);
608 modest_header_view_finalize (GObject *obj)
610 ModestHeaderView *self;
611 ModestHeaderViewPrivate *priv;
613 self = MODEST_HEADER_VIEW(obj);
614 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
616 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
617 priv->acc_removed_handler)) {
618 g_signal_handler_disconnect (modest_runtime_get_account_store (),
619 priv->acc_removed_handler);
622 /* There is no need to lock because there should not be any
623 * reference to self now. */
624 g_mutex_free(priv->observer_list_lock);
625 g_slist_free(priv->observer_list);
627 g_mutex_lock (priv->observers_lock);
629 tny_folder_monitor_stop (priv->monitor);
630 g_object_unref (G_OBJECT (priv->monitor));
632 g_mutex_unlock (priv->observers_lock);
633 g_mutex_free (priv->observers_lock);
635 /* Clear hidding array created by cut operation */
636 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
638 G_OBJECT_CLASS(parent_class)->finalize (obj);
643 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
646 GtkTreeSelection *sel;
647 ModestHeaderView *self;
648 ModestHeaderViewPrivate *priv;
650 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
653 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
654 self = MODEST_HEADER_VIEW(obj);
655 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
657 modest_header_view_set_style (self, style);
659 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
660 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
661 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
663 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
664 TRUE); /* alternating row colors */
666 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
667 priv->selection_changed_handler =
668 g_signal_connect_after (sel, "changed",
669 G_CALLBACK(on_selection_changed), self);
671 g_signal_connect (self, "row-activated",
672 G_CALLBACK (on_header_row_activated), NULL);
674 g_signal_connect (self, "focus-in-event",
675 G_CALLBACK(on_focus_in), NULL);
676 g_signal_connect (self, "focus-out-event",
677 G_CALLBACK(on_focus_out), NULL);
679 g_signal_connect (self, "button-press-event",
680 G_CALLBACK(on_button_press_event), NULL);
681 g_signal_connect (self, "button-release-event",
682 G_CALLBACK(on_button_release_event), NULL);
684 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
686 G_CALLBACK (on_account_removed),
689 g_signal_connect (self, "expose-event",
690 G_CALLBACK(modest_header_view_on_expose_event),
693 return GTK_WIDGET(self);
698 modest_header_view_count_selected_headers (ModestHeaderView *self)
700 GtkTreeSelection *sel;
703 g_return_val_if_fail (self, 0);
705 /* Get selection object and check selected rows count */
706 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
707 selected_rows = gtk_tree_selection_count_selected_rows (sel);
709 return selected_rows;
713 modest_header_view_has_selected_headers (ModestHeaderView *self)
715 GtkTreeSelection *sel;
718 g_return_val_if_fail (self, FALSE);
720 /* Get selection object and check selected rows count */
721 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
722 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
729 modest_header_view_get_selected_headers (ModestHeaderView *self)
731 GtkTreeSelection *sel;
732 ModestHeaderViewPrivate *priv;
733 TnyList *header_list = NULL;
735 GList *list, *tmp = NULL;
736 GtkTreeModel *tree_model = NULL;
739 g_return_val_if_fail (self, NULL);
741 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
743 /* Get selected rows */
744 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
745 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
748 header_list = tny_simple_list_new();
750 list = g_list_reverse (list);
753 /* get header from selection */
754 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
755 gtk_tree_model_get (tree_model, &iter,
756 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
758 /* Prepend to list */
759 tny_list_prepend (header_list, G_OBJECT (header));
760 g_object_unref (G_OBJECT (header));
762 tmp = g_list_next (tmp);
765 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
772 /* scroll our list view so the selected item is visible */
774 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
776 #ifdef MODEST_PLATFORM_GNOME
778 GtkTreePath *selected_path;
779 GtkTreePath *start, *end;
783 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
784 selected_path = gtk_tree_model_get_path (model, iter);
786 start = gtk_tree_path_new ();
787 end = gtk_tree_path_new ();
789 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
791 if (gtk_tree_path_compare (selected_path, start) < 0 ||
792 gtk_tree_path_compare (end, selected_path) < 0)
793 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
794 selected_path, NULL, TRUE,
797 gtk_tree_path_free (selected_path);
798 gtk_tree_path_free (start);
799 gtk_tree_path_free (end);
801 #endif /* MODEST_PLATFORM_GNOME */
806 modest_header_view_select_next (ModestHeaderView *self)
808 GtkTreeSelection *sel;
813 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
814 path = get_selected_row (GTK_TREE_VIEW(self), &model);
815 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
816 /* Unselect previous path */
817 gtk_tree_selection_unselect_path (sel, path);
819 /* Move path down and selects new one */
820 if (gtk_tree_model_iter_next (model, &iter)) {
821 gtk_tree_selection_select_iter (sel, &iter);
822 scroll_to_selected (self, &iter, FALSE);
824 gtk_tree_path_free(path);
830 modest_header_view_select_prev (ModestHeaderView *self)
832 GtkTreeSelection *sel;
837 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
838 path = get_selected_row (GTK_TREE_VIEW(self), &model);
839 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
840 /* Unselect previous path */
841 gtk_tree_selection_unselect_path (sel, path);
844 if (gtk_tree_path_prev (path)) {
845 gtk_tree_model_get_iter (model, &iter, path);
847 /* Select the new one */
848 gtk_tree_selection_select_iter (sel, &iter);
849 scroll_to_selected (self, &iter, TRUE);
852 gtk_tree_path_free (path);
857 modest_header_view_get_columns (ModestHeaderView *self)
859 g_return_val_if_fail (self, FALSE);
860 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
866 modest_header_view_set_style (ModestHeaderView *self,
867 ModestHeaderViewStyle style)
869 ModestHeaderViewPrivate *priv;
870 gboolean show_col_headers = FALSE;
871 ModestHeaderViewStyle old_style;
873 g_return_val_if_fail (self, FALSE);
874 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
877 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
878 if (priv->style == style)
879 return TRUE; /* nothing to do */
882 case MODEST_HEADER_VIEW_STYLE_DETAILS:
883 show_col_headers = TRUE;
885 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
888 g_return_val_if_reached (FALSE);
890 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
891 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
893 old_style = priv->style;
900 ModestHeaderViewStyle
901 modest_header_view_get_style (ModestHeaderView *self)
903 g_return_val_if_fail (self, FALSE);
904 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
907 /* This is used to automatically select the first header if the user
908 * has not selected any header yet.
911 modest_header_view_on_expose_event(GtkTreeView *header_view,
912 GdkEventExpose *event,
915 GtkTreeSelection *sel;
917 GtkTreeIter tree_iter;
919 model = gtk_tree_view_get_model(header_view);
921 sel = gtk_tree_view_get_selection(header_view);
922 if(!gtk_tree_selection_count_selected_rows(sel))
923 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
924 /* Prevent the widget from getting the focus
925 when selecting the first item */
926 g_object_set(header_view, "can-focus", FALSE, NULL);
927 gtk_tree_selection_select_iter(sel, &tree_iter);
928 g_object_set(header_view, "can-focus", TRUE, NULL);
935 * This function sets a sortable model in the header view. It's just
936 * used for developing purposes, because it only does a
937 * gtk_tree_view_set_model
940 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
942 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
943 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
944 /* GtkTreeModel *old_model; */
945 /* ModestHeaderViewPrivate *priv; */
946 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
947 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
949 /* /\* Set new model *\/ */
950 /* gtk_tree_view_set_model (header_view, model); */
952 gtk_tree_view_set_model (header_view, model);
956 modest_header_view_get_folder (ModestHeaderView *self)
958 ModestHeaderViewPrivate *priv;
959 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
962 g_object_ref (priv->folder);
968 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
972 ModestHeaderViewPrivate *priv;
973 GList *cols, *cursor;
974 GtkTreeModel *filter_model, *sortable;
976 GtkSortType sort_type;
978 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
980 headers = TNY_LIST (tny_gtk_header_list_model_new ());
982 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
983 folder, FALSE, NULL, NULL, NULL);
985 /* Add IDLE observer (monitor) and another folder observer for
986 new messages (self) */
987 g_mutex_lock (priv->observers_lock);
989 tny_folder_monitor_stop (priv->monitor);
990 g_object_unref (G_OBJECT (priv->monitor));
992 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
993 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
994 tny_folder_monitor_start (priv->monitor);
995 g_mutex_unlock (priv->observers_lock);
997 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
998 g_object_unref (G_OBJECT (headers));
1000 /* Init filter_row function to examine empty status */
1001 priv->status = HEADER_VIEW_INIT;
1003 /* Create a tree model filter to hide and show rows for cut operations */
1004 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1005 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1009 g_object_unref (G_OBJECT (sortable));
1011 /* install our special sorting functions */
1012 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1014 /* Restore sort column id */
1016 type = modest_tny_folder_guess_folder_type (folder);
1017 if (type == TNY_FOLDER_TYPE_INVALID)
1018 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1020 sort_colid = modest_header_view_get_sort_column_id (self, type);
1021 sort_type = modest_header_view_get_sort_type (self, type);
1022 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1025 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1026 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1027 (GtkTreeIterCompareFunc) cmp_rows,
1029 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1030 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1031 (GtkTreeIterCompareFunc) cmp_subject_rows,
1036 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1037 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1038 tny_folder_get_id(folder));
1039 g_object_unref (G_OBJECT (filter_model));
1040 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1041 /* g_object_unref (G_OBJECT (sortable)); */
1048 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1050 GtkSortType sort_type)
1052 ModestHeaderViewPrivate *priv = NULL;
1053 GtkTreeModel *tree_filter, *sortable = NULL;
1056 /* Get model and private data */
1057 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1058 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1059 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1060 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1062 /* Sort tree model */
1063 type = modest_tny_folder_guess_folder_type (priv->folder);
1064 if (type == TNY_FOLDER_TYPE_INVALID)
1065 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1067 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1070 /* Store new sort parameters */
1071 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1076 modest_header_view_set_sort_params (ModestHeaderView *self,
1078 GtkSortType sort_type,
1081 ModestHeaderViewPrivate *priv;
1082 ModestHeaderViewStyle style;
1084 style = modest_header_view_get_style (self);
1085 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1087 priv->sort_colid[style][type] = sort_colid;
1088 priv->sort_type[style][type] = sort_type;
1092 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1095 ModestHeaderViewPrivate *priv;
1096 ModestHeaderViewStyle style;
1098 style = modest_header_view_get_style (self);
1099 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1101 return priv->sort_colid[style][type];
1105 modest_header_view_get_sort_type (ModestHeaderView *self,
1108 ModestHeaderViewPrivate *priv;
1109 ModestHeaderViewStyle style;
1111 style = modest_header_view_get_style (self);
1112 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1114 return priv->sort_type[style][type];
1118 ModestHeaderView *header_view;
1119 RefreshAsyncUserCallback cb;
1124 folder_refreshed_cb (ModestMailOperation *mail_op,
1128 ModestHeaderViewPrivate *priv;
1129 SetFolderHelper *info;
1131 info = (SetFolderHelper*) user_data;
1133 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1137 info->cb (mail_op, folder, info->user_data);
1139 /* Start the folder count changes observer. We do not need it
1140 before the refresh. Note that the monitor could still be
1141 called for this refresh but now we know that the callback
1142 was previously called */
1143 g_mutex_lock (priv->observers_lock);
1144 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1145 g_mutex_unlock (priv->observers_lock);
1147 /* Notify the observers that the update is over */
1148 g_signal_emit (G_OBJECT (info->header_view),
1149 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1156 modest_header_view_set_folder (ModestHeaderView *self,
1158 RefreshAsyncUserCallback callback,
1161 ModestHeaderViewPrivate *priv;
1162 ModestWindowMgr *mgr = NULL;
1163 GObject *source = NULL;
1164 SetFolderHelper *info;
1166 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1169 g_mutex_lock (priv->observers_lock);
1170 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1171 g_object_unref (priv->folder);
1172 priv->folder = NULL;
1173 g_mutex_unlock (priv->observers_lock);
1177 ModestMailOperation *mail_op = NULL;
1178 GtkTreeSelection *selection;
1180 /* Get main window to use it as source of mail operation */
1181 mgr = modest_runtime_get_window_mgr ();
1182 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1184 /* Set folder in the model */
1185 modest_header_view_set_folder_intern (self, folder);
1187 /* Pick my reference. Nothing to do with the mail operation */
1188 priv->folder = g_object_ref (folder);
1190 /* Clear the selection if exists */
1191 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1192 gtk_tree_selection_unselect_all(selection);
1193 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1195 /* Notify the observers that the update begins */
1196 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1199 /* create the helper */
1200 info = g_malloc0 (sizeof(SetFolderHelper));
1201 info->header_view = self;
1202 info->cb = callback;
1203 info->user_data = user_data;
1205 /* Create the mail operation (source will be the parent widget) */
1206 mail_op = modest_mail_operation_new (source);
1207 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1210 /* Refresh the folder asynchronously */
1211 modest_mail_operation_refresh_folder (mail_op,
1213 folder_refreshed_cb,
1217 g_object_unref (mail_op);
1219 g_mutex_lock (priv->observers_lock);
1221 if (priv->monitor) {
1222 tny_folder_monitor_stop (priv->monitor);
1223 g_object_unref (G_OBJECT (priv->monitor));
1224 priv->monitor = NULL;
1226 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1228 modest_header_view_notify_observers(self, NULL, NULL);
1230 g_mutex_unlock (priv->observers_lock);
1232 /* Notify the observers that the update is over */
1233 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1239 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1240 GtkTreeViewColumn *column, gpointer userdata)
1242 ModestHeaderView *self = NULL;
1243 ModestHeaderViewPrivate *priv = NULL;
1245 GtkTreeModel *model = NULL;
1246 TnyHeader *header = NULL;
1247 TnyHeaderFlags flags;
1249 self = MODEST_HEADER_VIEW (treeview);
1250 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1252 model = gtk_tree_view_get_model (treeview);
1253 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1256 /* get the first selected item */
1257 gtk_tree_model_get (model, &iter,
1258 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1259 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1262 /* Dont open DELETED messages */
1263 if (flags & TNY_HEADER_FLAG_DELETED) {
1264 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1269 g_signal_emit (G_OBJECT(self),
1270 signals[HEADER_ACTIVATED_SIGNAL],
1276 g_object_unref (G_OBJECT (header));
1281 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1283 GtkTreeModel *model;
1284 TnyHeader *header = NULL;
1285 GtkTreePath *path = NULL;
1287 ModestHeaderView *self;
1288 ModestHeaderViewPrivate *priv;
1289 GList *selected = NULL;
1291 g_return_if_fail (sel);
1292 g_return_if_fail (user_data);
1294 self = MODEST_HEADER_VIEW (user_data);
1295 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1297 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1298 if (selected != NULL)
1299 path = (GtkTreePath *) selected->data;
1300 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1301 return; /* msg was _un_selected */
1303 gtk_tree_model_get (model, &iter,
1304 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1308 g_signal_emit (G_OBJECT(self),
1309 signals[HEADER_SELECTED_SIGNAL],
1312 g_object_unref (G_OBJECT (header));
1314 /* free all items in 'selected' */
1315 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1316 g_list_free (selected);
1320 /* PROTECTED method. It's useful when we want to force a given
1321 selection to reload a msg. For example if we have selected a header
1322 in offline mode, when Modest become online, we want to reload the
1323 message automatically without an user click over the header */
1325 _modest_header_view_change_selection (GtkTreeSelection *selection,
1328 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1329 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1331 on_selection_changed (selection, user_data);
1334 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1342 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1346 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1350 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1359 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1366 /* static int counter = 0; */
1368 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1369 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1370 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1374 case TNY_HEADER_FLAG_ATTACHMENTS:
1376 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1377 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1378 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1379 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1381 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1382 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1384 return cmp ? cmp : t1 - t2;
1386 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1387 TnyHeader *header1 = NULL, *header2 = NULL;
1389 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1390 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1391 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1392 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1394 /* This is for making priority values respect the intuitive sort relationship
1395 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1397 if (header1 && header2) {
1398 cmp = compare_priorities (tny_header_get_priority (header1),
1399 tny_header_get_priority (header2));
1400 g_object_unref (header1);
1401 g_object_unref (header2);
1403 return cmp ? cmp : t1 - t2;
1409 return &iter1 - &iter2; /* oughhhh */
1414 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1420 /* static int counter = 0; */
1422 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1424 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1425 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1426 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1427 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1429 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1430 val2 + modest_text_utils_get_subject_prefix_len(val2),
1437 /* Drag and drop stuff */
1439 drag_data_get_cb (GtkWidget *widget,
1440 GdkDragContext *context,
1441 GtkSelectionData *selection_data,
1446 ModestHeaderView *self = NULL;
1447 ModestHeaderViewPrivate *priv = NULL;
1449 self = MODEST_HEADER_VIEW (widget);
1450 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1452 /* Set the data. Do not use the current selection because it
1453 could be different than the selection at the beginning of
1455 modest_dnd_selection_data_set_paths (selection_data,
1456 priv->drag_begin_cached_selected_rows);
1460 * We're caching the selected rows at the beginning because the
1461 * selection could change between drag-begin and drag-data-get, for
1462 * example if we have a set of rows already selected, and then we
1463 * click in one of them (without SHIFT key pressed) and begin a drag,
1464 * the selection at that moment contains all the selected lines, but
1465 * after dropping the selection, the release event provokes that only
1466 * the row used to begin the drag is selected, so at the end the
1467 * drag&drop affects only one rows instead of all the selected ones.
1471 drag_begin_cb (GtkWidget *widget,
1472 GdkDragContext *context,
1475 ModestHeaderView *self = NULL;
1476 ModestHeaderViewPrivate *priv = NULL;
1477 GtkTreeSelection *selection;
1479 self = MODEST_HEADER_VIEW (widget);
1480 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1482 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1483 priv->drag_begin_cached_selected_rows =
1484 gtk_tree_selection_get_selected_rows (selection, NULL);
1488 * We use the drag-end signal to clear the cached selection, we use
1489 * this because this allways happens, whether or not the d&d was a
1493 drag_end_cb (GtkWidget *widget,
1497 ModestHeaderView *self = NULL;
1498 ModestHeaderViewPrivate *priv = NULL;
1500 self = MODEST_HEADER_VIEW (widget);
1501 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1503 /* Free cached data */
1504 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1505 g_list_free (priv->drag_begin_cached_selected_rows);
1506 priv->drag_begin_cached_selected_rows = NULL;
1509 /* Header view drag types */
1510 const GtkTargetEntry header_view_drag_types[] = {
1511 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1515 enable_drag_and_drop (GtkWidget *self)
1517 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1518 header_view_drag_types,
1519 G_N_ELEMENTS (header_view_drag_types),
1520 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1524 disable_drag_and_drop (GtkWidget *self)
1526 gtk_drag_source_unset (self);
1530 setup_drag_and_drop (GtkWidget *self)
1532 enable_drag_and_drop(self);
1533 g_signal_connect(G_OBJECT (self), "drag_data_get",
1534 G_CALLBACK(drag_data_get_cb), NULL);
1536 g_signal_connect(G_OBJECT (self), "drag_begin",
1537 G_CALLBACK(drag_begin_cb), NULL);
1539 g_signal_connect(G_OBJECT (self), "drag_end",
1540 G_CALLBACK(drag_end_cb), NULL);
1543 static GtkTreePath *
1544 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1546 GtkTreePath *path = NULL;
1547 GtkTreeSelection *sel = NULL;
1550 sel = gtk_tree_view_get_selection(self);
1551 rows = gtk_tree_selection_get_selected_rows (sel, model);
1553 if ((rows == NULL) || (g_list_length(rows) != 1))
1556 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1561 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1568 * This function moves the tree view scroll to the current selected
1569 * row when the widget grabs the focus
1572 on_focus_in (GtkWidget *self,
1573 GdkEventFocus *event,
1576 GtkTreeSelection *selection;
1577 GtkTreeModel *model;
1578 GList *selected = NULL;
1579 GtkTreePath *selected_path = NULL;
1581 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1585 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1586 /* If none selected yet, pick the first one */
1587 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1591 /* Return if the model is empty */
1592 if (!gtk_tree_model_get_iter_first (model, &iter))
1595 path = gtk_tree_model_get_path (model, &iter);
1596 gtk_tree_selection_select_path (selection, path);
1597 gtk_tree_path_free (path);
1600 /* Need to get the all the rows because is selection multiple */
1601 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1602 if (selected == NULL) return FALSE;
1603 selected_path = (GtkTreePath *) selected->data;
1605 /* Check if we need to scroll */
1606 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1607 GtkTreePath *start_path = NULL;
1608 GtkTreePath *end_path = NULL;
1609 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1613 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1614 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1616 /* Scroll to first path */
1617 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1626 gtk_tree_path_free (start_path);
1628 gtk_tree_path_free (end_path);
1630 #endif /* GTK_CHECK_VERSION */
1633 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1634 g_list_free (selected);
1640 on_focus_out (GtkWidget *self,
1641 GdkEventFocus *event,
1645 if (!gtk_widget_is_focus (self)) {
1646 GtkTreeSelection *selection = NULL;
1647 GList *selected_rows = NULL;
1648 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1649 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1650 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1651 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1652 gtk_tree_selection_unselect_all (selection);
1653 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1654 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1655 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1656 g_list_free (selected_rows);
1663 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1665 enable_drag_and_drop(self);
1670 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1672 GtkTreeSelection *selection = NULL;
1673 GtkTreePath *path = NULL;
1674 gboolean already_selected = FALSE;
1676 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1677 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1678 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1681 /* Enable drag and drop onlly if the user clicks on a row that
1682 it's already selected. If not, let him select items using
1684 if (!already_selected) {
1685 disable_drag_and_drop(self);
1689 gtk_tree_path_free(path);
1696 folder_monitor_update (TnyFolderObserver *self,
1697 TnyFolderChange *change)
1699 ModestHeaderViewPrivate *priv = NULL;
1700 TnyFolderChangeChanged changed;
1701 TnyFolder *folder = NULL;
1703 changed = tny_folder_change_get_changed (change);
1705 /* Do not notify the observers if the folder of the header
1706 view has changed before this call to the observer
1708 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1709 folder = tny_folder_change_get_folder (change);
1710 if (folder != priv->folder)
1713 /* Check folder count */
1714 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1715 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1717 g_mutex_lock (priv->observers_lock);
1719 /* Emit signal to evaluate how headers changes affects
1720 to the window view */
1721 g_signal_emit (G_OBJECT(self),
1722 signals[MSG_COUNT_CHANGED_SIGNAL],
1725 /* Added or removed headers, so data stored on cliboard are invalid */
1726 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1727 modest_email_clipboard_clear (priv->clipboard);
1729 g_mutex_unlock (priv->observers_lock);
1735 g_object_unref (folder);
1739 modest_header_view_is_empty (ModestHeaderView *self)
1741 ModestHeaderViewPrivate *priv = NULL;
1743 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1745 return priv->status == HEADER_VIEW_EMPTY;
1749 modest_header_view_clear (ModestHeaderView *self)
1751 modest_header_view_set_folder (self, NULL, NULL, NULL);
1755 modest_header_view_copy_selection (ModestHeaderView *header_view)
1757 /* Copy selection */
1758 _clipboard_set_selected_data (header_view, FALSE);
1762 modest_header_view_cut_selection (ModestHeaderView *header_view)
1764 ModestHeaderViewPrivate *priv = NULL;
1765 const gchar **hidding = NULL;
1766 guint i, n_selected;
1768 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1769 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1771 /* Copy selection */
1772 _clipboard_set_selected_data (header_view, TRUE);
1774 /* Get hidding ids */
1775 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1777 /* Clear hidding array created by previous cut operation */
1778 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1780 /* Copy hidding array */
1781 priv->n_selected = n_selected;
1782 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1783 for (i=0; i < n_selected; i++)
1784 priv->hidding_ids[i] = g_strdup(hidding[i]);
1786 /* Hide cut headers */
1787 modest_header_view_refilter (header_view);
1794 _clipboard_set_selected_data (ModestHeaderView *header_view,
1797 ModestHeaderViewPrivate *priv = NULL;
1798 TnyList *headers = NULL;
1800 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1801 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1803 /* Set selected data on clipboard */
1804 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1805 headers = modest_header_view_get_selected_headers (header_view);
1806 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1809 g_object_unref (headers);
1815 filter_row (GtkTreeModel *model,
1819 ModestHeaderViewPrivate *priv = NULL;
1820 TnyHeaderFlags flags;
1821 TnyHeader *header = NULL;
1824 gboolean visible = TRUE;
1825 gboolean found = FALSE;
1827 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1828 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1830 /* Get header from model */
1831 gtk_tree_model_get (model, iter,
1832 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1833 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1836 /* Hide mark as deleted heders */
1837 if (flags & TNY_HEADER_FLAG_DELETED) {
1842 /* If no data on clipboard, return always TRUE */
1843 if (modest_email_clipboard_cleared(priv->clipboard)) {
1848 /* Get message id from header (ensure is a valid id) */
1849 if (!header) return FALSE;
1850 id = g_strdup(tny_header_get_message_id (header));
1853 if (priv->hidding_ids != NULL) {
1854 for (i=0; i < priv->n_selected && !found; i++)
1855 if (priv->hidding_ids[i] != NULL && id != NULL)
1856 found = (!strcmp (priv->hidding_ids[i], id));
1862 priv->status = ((gboolean) priv->status) && !visible;
1866 g_object_unref (header);
1873 _clear_hidding_filter (ModestHeaderView *header_view)
1875 ModestHeaderViewPrivate *priv = NULL;
1878 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1879 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1881 if (priv->hidding_ids != NULL) {
1882 for (i=0; i < priv->n_selected; i++)
1883 g_free (priv->hidding_ids[i]);
1884 g_free(priv->hidding_ids);
1889 modest_header_view_refilter (ModestHeaderView *header_view)
1891 GtkTreeModel *model = NULL;
1892 ModestHeaderViewPrivate *priv = NULL;
1894 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1895 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1897 /* Hide cut headers */
1898 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1899 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1901 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1906 * Called when an account is removed. If I'm showing a folder of the
1907 * account that has been removed then clear the view
1910 on_account_removed (TnyAccountStore *self,
1911 TnyAccount *account,
1914 ModestHeaderViewPrivate *priv = NULL;
1916 /* Ignore changes in transport accounts */
1917 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1920 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1923 TnyAccount *my_account;
1925 my_account = tny_folder_get_account (priv->folder);
1926 if (my_account == account)
1927 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1928 g_object_unref (account);
1932 void modest_header_view_add_observer(
1933 ModestHeaderView *header_view,
1934 ModestHeaderViewObserver *observer)
1936 ModestHeaderViewPrivate *priv = NULL;
1938 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1939 g_assert(observer != NULL);
1940 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1942 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1944 g_mutex_lock(priv->observer_list_lock);
1945 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1946 g_mutex_unlock(priv->observer_list_lock);
1950 modest_header_view_remove_observer(ModestHeaderView *header_view,
1951 ModestHeaderViewObserver *observer)
1953 ModestHeaderViewPrivate *priv = NULL;
1955 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1956 g_assert(observer != NULL);
1957 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1959 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1961 g_mutex_lock(priv->observer_list_lock);
1962 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1963 g_mutex_unlock(priv->observer_list_lock);
1967 modest_header_view_notify_observers(ModestHeaderView *header_view,
1968 GtkTreeModel *model,
1969 const gchar *tny_folder_id)
1971 ModestHeaderViewPrivate *priv = NULL;
1973 ModestHeaderViewObserver *observer;
1975 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1977 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1979 g_mutex_lock(priv->observer_list_lock);
1980 iter = priv->observer_list;
1981 while(iter != NULL){
1982 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1983 modest_header_view_observer_update(observer, model,
1985 iter = g_slist_next(iter);
1987 g_mutex_unlock(priv->observer_list_lock);