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 /* I'm invalidating this method because it causes an annoying
920 efect, the focus changes to the header view when selecting
921 a folder in the folder view because of this code and it
922 shouldn't. We need to find another way to set the passive
923 focus on it. Sergio. */
926 model = gtk_tree_view_get_model(header_view);
928 sel = gtk_tree_view_get_selection(header_view);
929 if(!gtk_tree_selection_count_selected_rows(sel))
930 if (gtk_tree_model_get_iter_first(model, &tree_iter))
931 gtk_tree_selection_select_iter(sel, &tree_iter);
937 * This function sets a sortable model in the header view. It's just
938 * used for developing purposes, because it only does a
939 * gtk_tree_view_set_model
942 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
944 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
945 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
946 /* GtkTreeModel *old_model; */
947 /* ModestHeaderViewPrivate *priv; */
948 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
949 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
951 /* /\* Set new model *\/ */
952 /* gtk_tree_view_set_model (header_view, model); */
954 gtk_tree_view_set_model (header_view, model);
958 modest_header_view_get_folder (ModestHeaderView *self)
960 ModestHeaderViewPrivate *priv;
961 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
964 g_object_ref (priv->folder);
970 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
974 ModestHeaderViewPrivate *priv;
975 GList *cols, *cursor;
976 GtkTreeModel *filter_model, *sortable;
978 GtkSortType sort_type;
980 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
982 headers = TNY_LIST (tny_gtk_header_list_model_new ());
984 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
985 folder, FALSE, NULL, NULL, NULL);
987 /* Add IDLE observer (monitor) and another folder observer for
988 new messages (self) */
989 g_mutex_lock (priv->observers_lock);
991 tny_folder_monitor_stop (priv->monitor);
992 g_object_unref (G_OBJECT (priv->monitor));
994 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
995 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
996 tny_folder_monitor_start (priv->monitor);
997 g_mutex_unlock (priv->observers_lock);
999 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
1000 g_object_unref (G_OBJECT (headers));
1002 /* Init filter_row function to examine empty status */
1003 priv->status = HEADER_VIEW_INIT;
1005 /* Create a tree model filter to hide and show rows for cut operations */
1006 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1007 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1011 g_object_unref (G_OBJECT (sortable));
1013 /* install our special sorting functions */
1014 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1016 /* Restore sort column id */
1018 type = modest_tny_folder_guess_folder_type (folder);
1019 sort_colid = modest_header_view_get_sort_column_id (self, type);
1020 sort_type = modest_header_view_get_sort_type (self, type);
1021 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1024 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1025 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1026 (GtkTreeIterCompareFunc) cmp_rows,
1028 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1029 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1030 (GtkTreeIterCompareFunc) cmp_subject_rows,
1035 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1036 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1037 tny_folder_get_id(folder));
1038 g_object_unref (G_OBJECT (filter_model));
1039 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1040 /* g_object_unref (G_OBJECT (sortable)); */
1047 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1049 GtkSortType sort_type)
1051 ModestHeaderViewPrivate *priv = NULL;
1052 GtkTreeModel *tree_filter, *sortable = NULL;
1055 /* Get model and private data */
1056 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1057 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1058 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1059 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1061 /* Sort tree model */
1062 type = modest_tny_folder_guess_folder_type (priv->folder);
1063 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1066 /* Store new sort parameters */
1067 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1069 /* Save GConf parameters */
1070 /* modest_widget_memory_save (modest_runtime_get_conf(), */
1071 /* G_OBJECT(self), "header-view"); */
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)
1336 p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1337 p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1339 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1341 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1346 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1353 /* static int counter = 0; */
1355 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1356 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1357 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1361 case TNY_HEADER_FLAG_ATTACHMENTS:
1363 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1364 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1365 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1366 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1368 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1369 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1371 return cmp ? cmp : t1 - t2;
1373 case TNY_HEADER_FLAG_PRIORITY:
1374 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1375 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1376 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1377 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1379 /* This is for making priority values respect the intuitive sort relationship
1380 * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1381 cmp = compare_priorities (val1, val2);
1383 return cmp ? cmp : t1 - t2;
1386 return &iter1 - &iter2; /* oughhhh */
1391 cmp_subject_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);
1401 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1402 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1403 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1404 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1406 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1407 val2 + modest_text_utils_get_subject_prefix_len(val2),
1414 /* Drag and drop stuff */
1416 drag_data_get_cb (GtkWidget *widget,
1417 GdkDragContext *context,
1418 GtkSelectionData *selection_data,
1423 ModestHeaderView *self = NULL;
1424 ModestHeaderViewPrivate *priv = NULL;
1426 self = MODEST_HEADER_VIEW (widget);
1427 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1429 /* Set the data. Do not use the current selection because it
1430 could be different than the selection at the beginning of
1432 modest_dnd_selection_data_set_paths (selection_data,
1433 priv->drag_begin_cached_selected_rows);
1437 * We're caching the selected rows at the beginning because the
1438 * selection could change between drag-begin and drag-data-get, for
1439 * example if we have a set of rows already selected, and then we
1440 * click in one of them (without SHIFT key pressed) and begin a drag,
1441 * the selection at that moment contains all the selected lines, but
1442 * after dropping the selection, the release event provokes that only
1443 * the row used to begin the drag is selected, so at the end the
1444 * drag&drop affects only one rows instead of all the selected ones.
1448 drag_begin_cb (GtkWidget *widget,
1449 GdkDragContext *context,
1452 ModestHeaderView *self = NULL;
1453 ModestHeaderViewPrivate *priv = NULL;
1454 GtkTreeSelection *selection;
1456 self = MODEST_HEADER_VIEW (widget);
1457 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1459 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1460 priv->drag_begin_cached_selected_rows =
1461 gtk_tree_selection_get_selected_rows (selection, NULL);
1465 * We use the drag-end signal to clear the cached selection, we use
1466 * this because this allways happens, whether or not the d&d was a
1470 drag_end_cb (GtkWidget *widget,
1474 ModestHeaderView *self = NULL;
1475 ModestHeaderViewPrivate *priv = NULL;
1477 self = MODEST_HEADER_VIEW (widget);
1478 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1480 /* Free cached data */
1481 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1482 g_list_free (priv->drag_begin_cached_selected_rows);
1483 priv->drag_begin_cached_selected_rows = NULL;
1486 /* Header view drag types */
1487 const GtkTargetEntry header_view_drag_types[] = {
1488 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1492 enable_drag_and_drop (GtkWidget *self)
1494 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1495 header_view_drag_types,
1496 G_N_ELEMENTS (header_view_drag_types),
1497 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1501 disable_drag_and_drop (GtkWidget *self)
1503 gtk_drag_source_unset (self);
1507 setup_drag_and_drop (GtkWidget *self)
1509 enable_drag_and_drop(self);
1510 g_signal_connect(G_OBJECT (self), "drag_data_get",
1511 G_CALLBACK(drag_data_get_cb), NULL);
1513 g_signal_connect(G_OBJECT (self), "drag_begin",
1514 G_CALLBACK(drag_begin_cb), NULL);
1516 g_signal_connect(G_OBJECT (self), "drag_end",
1517 G_CALLBACK(drag_end_cb), NULL);
1520 static GtkTreePath *
1521 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1523 GtkTreePath *path = NULL;
1524 GtkTreeSelection *sel = NULL;
1527 sel = gtk_tree_view_get_selection(self);
1528 rows = gtk_tree_selection_get_selected_rows (sel, model);
1530 if ((rows == NULL) || (g_list_length(rows) != 1))
1533 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1538 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1545 * This function moves the tree view scroll to the current selected
1546 * row when the widget grabs the focus
1549 on_focus_in (GtkWidget *self,
1550 GdkEventFocus *event,
1553 GtkTreeSelection *selection;
1554 GtkTreeModel *model;
1555 GList *selected = NULL;
1556 GtkTreePath *selected_path = NULL;
1558 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1562 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1563 /* If none selected yet, pick the first one */
1564 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1568 /* Return if the model is empty */
1569 if (!gtk_tree_model_get_iter_first (model, &iter))
1572 path = gtk_tree_model_get_path (model, &iter);
1573 gtk_tree_selection_select_path (selection, path);
1574 gtk_tree_path_free (path);
1577 /* Need to get the all the rows because is selection multiple */
1578 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1579 if (selected == NULL) return FALSE;
1580 selected_path = (GtkTreePath *) selected->data;
1582 /* Check if we need to scroll */
1583 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1584 GtkTreePath *start_path = NULL;
1585 GtkTreePath *end_path = NULL;
1586 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1590 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1591 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1593 /* Scroll to first path */
1594 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1603 gtk_tree_path_free (start_path);
1605 gtk_tree_path_free (end_path);
1607 #endif /* GTK_CHECK_VERSION */
1610 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1611 g_list_free (selected);
1617 on_focus_out (GtkWidget *self,
1618 GdkEventFocus *event,
1622 if (!gtk_widget_is_focus (self)) {
1623 GtkTreeSelection *selection = NULL;
1624 GList *selected_rows = NULL;
1625 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1626 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1627 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1628 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1629 gtk_tree_selection_unselect_all (selection);
1630 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1631 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1632 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1633 g_list_free (selected_rows);
1640 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1642 enable_drag_and_drop(self);
1647 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1649 GtkTreeSelection *selection = NULL;
1650 GtkTreePath *path = NULL;
1651 gboolean already_selected = FALSE;
1653 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1654 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1655 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1658 /* Enable drag and drop onlly if the user clicks on a row that
1659 it's already selected. If not, let him select items using
1661 if (!already_selected) {
1662 disable_drag_and_drop(self);
1666 gtk_tree_path_free(path);
1673 folder_monitor_update (TnyFolderObserver *self,
1674 TnyFolderChange *change)
1676 ModestHeaderViewPrivate *priv = NULL;
1677 TnyFolderChangeChanged changed;
1678 TnyFolder *folder = NULL;
1680 changed = tny_folder_change_get_changed (change);
1682 /* Do not notify the observers if the folder of the header
1683 view has changed before this call to the observer
1685 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1686 folder = tny_folder_change_get_folder (change);
1687 if (folder != priv->folder)
1690 /* Check folder count */
1691 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1692 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1694 g_mutex_lock (priv->observers_lock);
1696 /* Emit signal to evaluate how headers changes affects
1697 to the window view */
1698 g_signal_emit (G_OBJECT(self),
1699 signals[MSG_COUNT_CHANGED_SIGNAL],
1702 /* Added or removed headers, so data stored on cliboard are invalid */
1703 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1704 modest_email_clipboard_clear (priv->clipboard);
1706 g_mutex_unlock (priv->observers_lock);
1712 g_object_unref (folder);
1716 modest_header_view_is_empty (ModestHeaderView *self)
1718 ModestHeaderViewPrivate *priv = NULL;
1720 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1722 return priv->status == HEADER_VIEW_EMPTY;
1726 modest_header_view_clear (ModestHeaderView *self)
1728 modest_header_view_set_folder (self, NULL, NULL, NULL);
1732 modest_header_view_copy_selection (ModestHeaderView *header_view)
1734 /* Copy selection */
1735 _clipboard_set_selected_data (header_view, FALSE);
1739 modest_header_view_cut_selection (ModestHeaderView *header_view)
1741 ModestHeaderViewPrivate *priv = NULL;
1742 const gchar **hidding = NULL;
1743 guint i, n_selected;
1745 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1746 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1748 /* Copy selection */
1749 _clipboard_set_selected_data (header_view, TRUE);
1751 /* Get hidding ids */
1752 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1754 /* Clear hidding array created by previous cut operation */
1755 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1757 /* Copy hidding array */
1758 priv->n_selected = n_selected;
1759 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1760 for (i=0; i < n_selected; i++)
1761 priv->hidding_ids[i] = g_strdup(hidding[i]);
1763 /* Hide cut headers */
1764 modest_header_view_refilter (header_view);
1771 _clipboard_set_selected_data (ModestHeaderView *header_view,
1774 ModestHeaderViewPrivate *priv = NULL;
1775 TnyList *headers = NULL;
1777 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1778 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1780 /* Set selected data on clipboard */
1781 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1782 headers = modest_header_view_get_selected_headers (header_view);
1783 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1786 g_object_unref (headers);
1792 filter_row (GtkTreeModel *model,
1796 ModestHeaderViewPrivate *priv = NULL;
1797 TnyHeaderFlags flags;
1798 TnyHeader *header = NULL;
1801 gboolean visible = TRUE;
1802 gboolean found = FALSE;
1804 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1805 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1807 /* Get header from model */
1808 gtk_tree_model_get (model, iter,
1809 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1810 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1813 /* Hide mark as deleted heders */
1814 if (flags & TNY_HEADER_FLAG_DELETED) {
1819 /* If no data on clipboard, return always TRUE */
1820 if (modest_email_clipboard_cleared(priv->clipboard)) {
1825 /* Get message id from header (ensure is a valid id) */
1826 if (!header) return FALSE;
1827 id = g_strdup(tny_header_get_message_id (header));
1830 if (priv->hidding_ids != NULL) {
1831 for (i=0; i < priv->n_selected && !found; i++)
1832 if (priv->hidding_ids[i] != NULL && id != NULL)
1833 found = (!strcmp (priv->hidding_ids[i], id));
1839 priv->status = ((gboolean) priv->status) && !visible;
1843 g_object_unref (header);
1850 _clear_hidding_filter (ModestHeaderView *header_view)
1852 ModestHeaderViewPrivate *priv = NULL;
1855 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1856 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1858 if (priv->hidding_ids != NULL) {
1859 for (i=0; i < priv->n_selected; i++)
1860 g_free (priv->hidding_ids[i]);
1861 g_free(priv->hidding_ids);
1866 modest_header_view_refilter (ModestHeaderView *header_view)
1868 GtkTreeModel *model = NULL;
1869 ModestHeaderViewPrivate *priv = NULL;
1871 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1872 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1874 /* Hide cut headers */
1875 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1876 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1878 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1883 * Called when an account is removed. If I'm showing a folder of the
1884 * account that has been removed then clear the view
1887 on_account_removed (TnyAccountStore *self,
1888 TnyAccount *account,
1891 ModestHeaderViewPrivate *priv = NULL;
1893 /* Ignore changes in transport accounts */
1894 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1897 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1900 TnyAccount *my_account;
1902 my_account = tny_folder_get_account (priv->folder);
1903 if (my_account == account)
1904 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1905 g_object_unref (account);
1909 void modest_header_view_add_observer(
1910 ModestHeaderView *header_view,
1911 ModestHeaderViewObserver *observer)
1913 ModestHeaderViewPrivate *priv = NULL;
1915 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1916 g_assert(observer != NULL);
1917 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1919 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1921 g_mutex_lock(priv->observer_list_lock);
1922 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1923 g_mutex_unlock(priv->observer_list_lock);
1927 modest_header_view_remove_observer(ModestHeaderView *header_view,
1928 ModestHeaderViewObserver *observer)
1930 ModestHeaderViewPrivate *priv = NULL;
1932 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1933 g_assert(observer != NULL);
1934 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1936 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1938 g_mutex_lock(priv->observer_list_lock);
1939 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1940 g_mutex_unlock(priv->observer_list_lock);
1944 modest_header_view_notify_observers(ModestHeaderView *header_view,
1945 GtkTreeModel *model,
1946 const gchar *tny_folder_id)
1948 ModestHeaderViewPrivate *priv = NULL;
1950 ModestHeaderViewObserver *observer;
1952 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1954 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1956 g_mutex_lock(priv->observer_list_lock);
1957 iter = priv->observer_list;
1958 while(iter != NULL){
1959 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1960 modest_header_view_observer_update(observer, model,
1962 iter = g_slist_next(iter);
1964 g_mutex_unlock(priv->observer_list_lock);