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 sort_colid = modest_header_view_get_sort_column_id (self, type);
1018 sort_type = modest_header_view_get_sort_type (self, type);
1019 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1022 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1023 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1024 (GtkTreeIterCompareFunc) cmp_rows,
1026 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1027 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1028 (GtkTreeIterCompareFunc) cmp_subject_rows,
1033 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1034 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1035 tny_folder_get_id(folder));
1036 g_object_unref (G_OBJECT (filter_model));
1037 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1038 /* g_object_unref (G_OBJECT (sortable)); */
1045 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1047 GtkSortType sort_type)
1049 ModestHeaderViewPrivate *priv = NULL;
1050 GtkTreeModel *tree_filter, *sortable = NULL;
1053 /* Get model and private data */
1054 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1055 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1056 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1057 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1059 /* Sort tree model */
1060 type = modest_tny_folder_guess_folder_type (priv->folder);
1061 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1064 /* Store new sort parameters */
1065 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1067 /* Save GConf parameters */
1068 /* modest_widget_memory_save (modest_runtime_get_conf(), */
1069 /* G_OBJECT(self), "header-view"); */
1074 modest_header_view_set_sort_params (ModestHeaderView *self,
1076 GtkSortType sort_type,
1079 ModestHeaderViewPrivate *priv;
1080 ModestHeaderViewStyle style;
1082 style = modest_header_view_get_style (self);
1083 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1085 priv->sort_colid[style][type] = sort_colid;
1086 priv->sort_type[style][type] = sort_type;
1090 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1093 ModestHeaderViewPrivate *priv;
1094 ModestHeaderViewStyle style;
1096 style = modest_header_view_get_style (self);
1097 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1099 return priv->sort_colid[style][type];
1103 modest_header_view_get_sort_type (ModestHeaderView *self,
1106 ModestHeaderViewPrivate *priv;
1107 ModestHeaderViewStyle style;
1109 style = modest_header_view_get_style (self);
1110 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1112 return priv->sort_type[style][type];
1116 ModestHeaderView *header_view;
1117 RefreshAsyncUserCallback cb;
1122 folder_refreshed_cb (ModestMailOperation *mail_op,
1126 ModestHeaderViewPrivate *priv;
1127 SetFolderHelper *info;
1129 info = (SetFolderHelper*) user_data;
1131 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1135 info->cb (mail_op, folder, info->user_data);
1137 /* Start the folder count changes observer. We do not need it
1138 before the refresh. Note that the monitor could still be
1139 called for this refresh but now we know that the callback
1140 was previously called */
1141 g_mutex_lock (priv->observers_lock);
1142 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1143 g_mutex_unlock (priv->observers_lock);
1145 /* Notify the observers that the update is over */
1146 g_signal_emit (G_OBJECT (info->header_view),
1147 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1154 modest_header_view_set_folder (ModestHeaderView *self,
1156 RefreshAsyncUserCallback callback,
1159 ModestHeaderViewPrivate *priv;
1160 ModestWindowMgr *mgr = NULL;
1161 GObject *source = NULL;
1162 SetFolderHelper *info;
1164 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1167 g_mutex_lock (priv->observers_lock);
1168 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1169 g_object_unref (priv->folder);
1170 priv->folder = NULL;
1171 g_mutex_unlock (priv->observers_lock);
1175 ModestMailOperation *mail_op = NULL;
1176 GtkTreeSelection *selection;
1178 /* Get main window to use it as source of mail operation */
1179 mgr = modest_runtime_get_window_mgr ();
1180 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1182 /* Set folder in the model */
1183 modest_header_view_set_folder_intern (self, folder);
1185 /* Pick my reference. Nothing to do with the mail operation */
1186 priv->folder = g_object_ref (folder);
1188 /* Clear the selection if exists */
1189 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1190 gtk_tree_selection_unselect_all(selection);
1191 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1193 /* Notify the observers that the update begins */
1194 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1197 /* create the helper */
1198 info = g_malloc0 (sizeof(SetFolderHelper));
1199 info->header_view = self;
1200 info->cb = callback;
1201 info->user_data = user_data;
1203 /* Create the mail operation (source will be the parent widget) */
1204 mail_op = modest_mail_operation_new (source);
1205 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1208 /* Refresh the folder asynchronously */
1209 modest_mail_operation_refresh_folder (mail_op,
1211 folder_refreshed_cb,
1215 g_object_unref (mail_op);
1217 g_mutex_lock (priv->observers_lock);
1219 if (priv->monitor) {
1220 tny_folder_monitor_stop (priv->monitor);
1221 g_object_unref (G_OBJECT (priv->monitor));
1222 priv->monitor = NULL;
1224 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1226 modest_header_view_notify_observers(self, NULL, NULL);
1228 g_mutex_unlock (priv->observers_lock);
1230 /* Notify the observers that the update is over */
1231 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1237 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1238 GtkTreeViewColumn *column, gpointer userdata)
1240 ModestHeaderView *self = NULL;
1241 ModestHeaderViewPrivate *priv = NULL;
1243 GtkTreeModel *model = NULL;
1244 TnyHeader *header = NULL;
1245 TnyHeaderFlags flags;
1247 self = MODEST_HEADER_VIEW (treeview);
1248 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1250 model = gtk_tree_view_get_model (treeview);
1251 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1254 /* get the first selected item */
1255 gtk_tree_model_get (model, &iter,
1256 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1257 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1260 /* Dont open DELETED messages */
1261 if (flags & TNY_HEADER_FLAG_DELETED) {
1262 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1267 g_signal_emit (G_OBJECT(self),
1268 signals[HEADER_ACTIVATED_SIGNAL],
1274 g_object_unref (G_OBJECT (header));
1279 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1281 GtkTreeModel *model;
1282 TnyHeader *header = NULL;
1283 GtkTreePath *path = NULL;
1285 ModestHeaderView *self;
1286 ModestHeaderViewPrivate *priv;
1287 GList *selected = NULL;
1289 g_return_if_fail (sel);
1290 g_return_if_fail (user_data);
1292 self = MODEST_HEADER_VIEW (user_data);
1293 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1295 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1296 if (selected != NULL)
1297 path = (GtkTreePath *) selected->data;
1298 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1299 return; /* msg was _un_selected */
1301 gtk_tree_model_get (model, &iter,
1302 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1306 g_signal_emit (G_OBJECT(self),
1307 signals[HEADER_SELECTED_SIGNAL],
1310 g_object_unref (G_OBJECT (header));
1312 /* free all items in 'selected' */
1313 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1314 g_list_free (selected);
1318 /* PROTECTED method. It's useful when we want to force a given
1319 selection to reload a msg. For example if we have selected a header
1320 in offline mode, when Modest become online, we want to reload the
1321 message automatically without an user click over the header */
1323 _modest_header_view_change_selection (GtkTreeSelection *selection,
1326 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1327 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1329 on_selection_changed (selection, user_data);
1332 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1334 p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1335 p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1337 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1339 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1344 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1351 /* static int counter = 0; */
1353 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1354 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1355 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1359 case TNY_HEADER_FLAG_ATTACHMENTS:
1361 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1362 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1363 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1364 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1366 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1367 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1369 return cmp ? cmp : t1 - t2;
1371 case TNY_HEADER_FLAG_PRIORITY:
1372 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1373 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1374 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1375 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1377 /* This is for making priority values respect the intuitive sort relationship
1378 * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1379 cmp = compare_priorities (val1, val2);
1381 return cmp ? cmp : t1 - t2;
1384 return &iter1 - &iter2; /* oughhhh */
1389 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1395 /* static int counter = 0; */
1397 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1399 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1400 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1401 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1402 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1404 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1405 val2 + modest_text_utils_get_subject_prefix_len(val2),
1412 /* Drag and drop stuff */
1414 drag_data_get_cb (GtkWidget *widget,
1415 GdkDragContext *context,
1416 GtkSelectionData *selection_data,
1421 ModestHeaderView *self = NULL;
1422 ModestHeaderViewPrivate *priv = NULL;
1424 self = MODEST_HEADER_VIEW (widget);
1425 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1427 /* Set the data. Do not use the current selection because it
1428 could be different than the selection at the beginning of
1430 modest_dnd_selection_data_set_paths (selection_data,
1431 priv->drag_begin_cached_selected_rows);
1435 * We're caching the selected rows at the beginning because the
1436 * selection could change between drag-begin and drag-data-get, for
1437 * example if we have a set of rows already selected, and then we
1438 * click in one of them (without SHIFT key pressed) and begin a drag,
1439 * the selection at that moment contains all the selected lines, but
1440 * after dropping the selection, the release event provokes that only
1441 * the row used to begin the drag is selected, so at the end the
1442 * drag&drop affects only one rows instead of all the selected ones.
1446 drag_begin_cb (GtkWidget *widget,
1447 GdkDragContext *context,
1450 ModestHeaderView *self = NULL;
1451 ModestHeaderViewPrivate *priv = NULL;
1452 GtkTreeSelection *selection;
1454 self = MODEST_HEADER_VIEW (widget);
1455 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1457 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1458 priv->drag_begin_cached_selected_rows =
1459 gtk_tree_selection_get_selected_rows (selection, NULL);
1463 * We use the drag-end signal to clear the cached selection, we use
1464 * this because this allways happens, whether or not the d&d was a
1468 drag_end_cb (GtkWidget *widget,
1472 ModestHeaderView *self = NULL;
1473 ModestHeaderViewPrivate *priv = NULL;
1475 self = MODEST_HEADER_VIEW (widget);
1476 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1478 /* Free cached data */
1479 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1480 g_list_free (priv->drag_begin_cached_selected_rows);
1481 priv->drag_begin_cached_selected_rows = NULL;
1484 /* Header view drag types */
1485 const GtkTargetEntry header_view_drag_types[] = {
1486 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1490 enable_drag_and_drop (GtkWidget *self)
1492 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1493 header_view_drag_types,
1494 G_N_ELEMENTS (header_view_drag_types),
1495 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1499 disable_drag_and_drop (GtkWidget *self)
1501 gtk_drag_source_unset (self);
1505 setup_drag_and_drop (GtkWidget *self)
1507 enable_drag_and_drop(self);
1508 g_signal_connect(G_OBJECT (self), "drag_data_get",
1509 G_CALLBACK(drag_data_get_cb), NULL);
1511 g_signal_connect(G_OBJECT (self), "drag_begin",
1512 G_CALLBACK(drag_begin_cb), NULL);
1514 g_signal_connect(G_OBJECT (self), "drag_end",
1515 G_CALLBACK(drag_end_cb), NULL);
1518 static GtkTreePath *
1519 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1521 GtkTreePath *path = NULL;
1522 GtkTreeSelection *sel = NULL;
1525 sel = gtk_tree_view_get_selection(self);
1526 rows = gtk_tree_selection_get_selected_rows (sel, model);
1528 if ((rows == NULL) || (g_list_length(rows) != 1))
1531 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1536 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1543 * This function moves the tree view scroll to the current selected
1544 * row when the widget grabs the focus
1547 on_focus_in (GtkWidget *self,
1548 GdkEventFocus *event,
1551 GtkTreeSelection *selection;
1552 GtkTreeModel *model;
1553 GList *selected = NULL;
1554 GtkTreePath *selected_path = NULL;
1556 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1560 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1561 /* If none selected yet, pick the first one */
1562 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1566 /* Return if the model is empty */
1567 if (!gtk_tree_model_get_iter_first (model, &iter))
1570 path = gtk_tree_model_get_path (model, &iter);
1571 gtk_tree_selection_select_path (selection, path);
1572 gtk_tree_path_free (path);
1575 /* Need to get the all the rows because is selection multiple */
1576 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1577 if (selected == NULL) return FALSE;
1578 selected_path = (GtkTreePath *) selected->data;
1580 /* Check if we need to scroll */
1581 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1582 GtkTreePath *start_path = NULL;
1583 GtkTreePath *end_path = NULL;
1584 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1588 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1589 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1591 /* Scroll to first path */
1592 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1601 gtk_tree_path_free (start_path);
1603 gtk_tree_path_free (end_path);
1605 #endif /* GTK_CHECK_VERSION */
1608 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1609 g_list_free (selected);
1615 on_focus_out (GtkWidget *self,
1616 GdkEventFocus *event,
1620 if (!gtk_widget_is_focus (self)) {
1621 GtkTreeSelection *selection = NULL;
1622 GList *selected_rows = NULL;
1623 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1624 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1625 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1626 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1627 gtk_tree_selection_unselect_all (selection);
1628 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1629 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1630 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1631 g_list_free (selected_rows);
1638 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1640 enable_drag_and_drop(self);
1645 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1647 GtkTreeSelection *selection = NULL;
1648 GtkTreePath *path = NULL;
1649 gboolean already_selected = FALSE;
1651 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1652 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1653 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1656 /* Enable drag and drop onlly if the user clicks on a row that
1657 it's already selected. If not, let him select items using
1659 if (!already_selected) {
1660 disable_drag_and_drop(self);
1664 gtk_tree_path_free(path);
1671 folder_monitor_update (TnyFolderObserver *self,
1672 TnyFolderChange *change)
1674 ModestHeaderViewPrivate *priv = NULL;
1675 TnyFolderChangeChanged changed;
1676 TnyFolder *folder = NULL;
1678 changed = tny_folder_change_get_changed (change);
1680 /* Do not notify the observers if the folder of the header
1681 view has changed before this call to the observer
1683 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1684 folder = tny_folder_change_get_folder (change);
1685 if (folder != priv->folder)
1688 /* Check folder count */
1689 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1690 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1692 g_mutex_lock (priv->observers_lock);
1694 /* Emit signal to evaluate how headers changes affects
1695 to the window view */
1696 g_signal_emit (G_OBJECT(self),
1697 signals[MSG_COUNT_CHANGED_SIGNAL],
1700 /* Added or removed headers, so data stored on cliboard are invalid */
1701 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1702 modest_email_clipboard_clear (priv->clipboard);
1704 g_mutex_unlock (priv->observers_lock);
1710 g_object_unref (folder);
1714 modest_header_view_is_empty (ModestHeaderView *self)
1716 ModestHeaderViewPrivate *priv = NULL;
1718 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1720 return priv->status == HEADER_VIEW_EMPTY;
1724 modest_header_view_clear (ModestHeaderView *self)
1726 modest_header_view_set_folder (self, NULL, NULL, NULL);
1730 modest_header_view_copy_selection (ModestHeaderView *header_view)
1732 /* Copy selection */
1733 _clipboard_set_selected_data (header_view, FALSE);
1737 modest_header_view_cut_selection (ModestHeaderView *header_view)
1739 ModestHeaderViewPrivate *priv = NULL;
1740 const gchar **hidding = NULL;
1741 guint i, n_selected;
1743 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1744 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1746 /* Copy selection */
1747 _clipboard_set_selected_data (header_view, TRUE);
1749 /* Get hidding ids */
1750 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1752 /* Clear hidding array created by previous cut operation */
1753 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1755 /* Copy hidding array */
1756 priv->n_selected = n_selected;
1757 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1758 for (i=0; i < n_selected; i++)
1759 priv->hidding_ids[i] = g_strdup(hidding[i]);
1761 /* Hide cut headers */
1762 modest_header_view_refilter (header_view);
1769 _clipboard_set_selected_data (ModestHeaderView *header_view,
1772 ModestHeaderViewPrivate *priv = NULL;
1773 TnyList *headers = NULL;
1775 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1776 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1778 /* Set selected data on clipboard */
1779 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1780 headers = modest_header_view_get_selected_headers (header_view);
1781 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1784 g_object_unref (headers);
1790 filter_row (GtkTreeModel *model,
1794 ModestHeaderViewPrivate *priv = NULL;
1795 TnyHeaderFlags flags;
1796 TnyHeader *header = NULL;
1799 gboolean visible = TRUE;
1800 gboolean found = FALSE;
1802 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1803 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1805 /* Get header from model */
1806 gtk_tree_model_get (model, iter,
1807 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1808 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1811 /* Hide mark as deleted heders */
1812 if (flags & TNY_HEADER_FLAG_DELETED) {
1817 /* If no data on clipboard, return always TRUE */
1818 if (modest_email_clipboard_cleared(priv->clipboard)) {
1823 /* Get message id from header (ensure is a valid id) */
1824 if (!header) return FALSE;
1825 id = g_strdup(tny_header_get_message_id (header));
1828 if (priv->hidding_ids != NULL) {
1829 for (i=0; i < priv->n_selected && !found; i++)
1830 if (priv->hidding_ids[i] != NULL && id != NULL)
1831 found = (!strcmp (priv->hidding_ids[i], id));
1837 priv->status = ((gboolean) priv->status) && !visible;
1841 g_object_unref (header);
1848 _clear_hidding_filter (ModestHeaderView *header_view)
1850 ModestHeaderViewPrivate *priv = NULL;
1853 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1854 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1856 if (priv->hidding_ids != NULL) {
1857 for (i=0; i < priv->n_selected; i++)
1858 g_free (priv->hidding_ids[i]);
1859 g_free(priv->hidding_ids);
1864 modest_header_view_refilter (ModestHeaderView *header_view)
1866 GtkTreeModel *model = NULL;
1867 ModestHeaderViewPrivate *priv = NULL;
1869 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1870 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1872 /* Hide cut headers */
1873 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1874 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1876 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1881 * Called when an account is removed. If I'm showing a folder of the
1882 * account that has been removed then clear the view
1885 on_account_removed (TnyAccountStore *self,
1886 TnyAccount *account,
1889 ModestHeaderViewPrivate *priv = NULL;
1891 /* Ignore changes in transport accounts */
1892 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1895 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1898 TnyAccount *my_account;
1900 my_account = tny_folder_get_account (priv->folder);
1901 if (my_account == account)
1902 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1903 g_object_unref (account);
1907 void modest_header_view_add_observer(
1908 ModestHeaderView *header_view,
1909 ModestHeaderViewObserver *observer)
1911 ModestHeaderViewPrivate *priv = NULL;
1913 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1914 g_assert(observer != NULL);
1915 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1917 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1919 g_mutex_lock(priv->observer_list_lock);
1920 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1921 g_mutex_unlock(priv->observer_list_lock);
1925 modest_header_view_remove_observer(ModestHeaderView *header_view,
1926 ModestHeaderViewObserver *observer)
1928 ModestHeaderViewPrivate *priv = NULL;
1930 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1931 g_assert(observer != NULL);
1932 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1934 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1936 g_mutex_lock(priv->observer_list_lock);
1937 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1938 g_mutex_unlock(priv->observer_list_lock);
1942 modest_header_view_notify_observers(ModestHeaderView *header_view,
1943 GtkTreeModel *model,
1944 const gchar *tny_folder_id)
1946 ModestHeaderViewPrivate *priv = NULL;
1948 ModestHeaderViewObserver *observer;
1950 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1952 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1954 g_mutex_lock(priv->observer_list_lock);
1955 iter = priv->observer_list;
1956 while(iter != NULL){
1957 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1958 modest_header_view_observer_update(observer, model,
1960 iter = g_slist_next(iter);
1962 g_mutex_unlock(priv->observer_list_lock);