1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
37 #include <modest-header-view.h>
38 #include <modest-header-view-priv.h>
39 #include <modest-dnd.h>
40 #include <modest-tny-folder.h>
42 #include <modest-main-window.h>
44 #include <modest-marshal.h>
45 #include <modest-text-utils.h>
46 #include <modest-icon-names.h>
47 #include <modest-runtime.h>
48 #include "modest-platform.h"
49 #include <modest-hbox-cell-renderer.h>
50 #include <modest-vbox-cell-renderer.h>
52 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
53 static void modest_header_view_init (ModestHeaderView *obj);
54 static void modest_header_view_finalize (GObject *obj);
55 static void modest_header_view_dispose (GObject *obj);
57 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
58 GtkTreeViewColumn *column, gpointer userdata);
60 static gint cmp_rows (GtkTreeModel *tree_model,
65 static gint cmp_subject_rows (GtkTreeModel *tree_model,
70 static gboolean filter_row (GtkTreeModel *model,
74 static void on_account_removed (TnyAccountStore *self,
78 static void on_selection_changed (GtkTreeSelection *sel,
81 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
84 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
87 static void setup_drag_and_drop (GtkWidget *self);
89 static void enable_drag_and_drop (GtkWidget *self);
91 static void disable_drag_and_drop (GtkWidget *self);
93 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
95 static gboolean on_focus_in (GtkWidget *sef,
99 static gboolean on_focus_out (GtkWidget *self,
100 GdkEventFocus *event,
103 static void folder_monitor_update (TnyFolderObserver *self,
104 TnyFolderChange *change);
106 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
108 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
110 static void _clear_hidding_filter (ModestHeaderView *header_view);
112 static void modest_header_view_notify_observers(
113 ModestHeaderView *header_view,
115 const gchar *tny_folder_id);
117 static gboolean modest_header_view_on_expose_event(
118 GtkTreeView *header_view,
119 GdkEventExpose *event,
123 HEADER_VIEW_NON_EMPTY,
128 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
129 struct _ModestHeaderViewPrivate {
131 ModestHeaderViewStyle style;
133 TnyFolderMonitor *monitor;
134 GMutex *observers_lock;
136 /*header-view-observer observer*/
137 GMutex *observer_list_lock;
138 GSList *observer_list;
140 /* not unref this object, its a singlenton */
141 ModestEmailClipboard *clipboard;
143 /* Filter tree model */
147 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
148 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
150 gulong selection_changed_handler;
151 gulong acc_removed_handler;
153 HeaderViewStatus status;
154 GList *drag_begin_cached_selected_rows;
157 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
158 struct _HeadersCountChangedHelper {
159 ModestHeaderView *self;
160 TnyFolderChange *change;
164 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
165 MODEST_TYPE_HEADER_VIEW, \
166 ModestHeaderViewPrivate))
170 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
173 HEADER_SELECTED_SIGNAL,
174 HEADER_ACTIVATED_SIGNAL,
175 ITEM_NOT_FOUND_SIGNAL,
176 MSG_COUNT_CHANGED_SIGNAL,
177 UPDATING_MSG_LIST_SIGNAL,
182 static GObjectClass *parent_class = NULL;
184 /* uncomment the following if you have defined any signals */
185 static guint signals[LAST_SIGNAL] = {0};
188 modest_header_view_get_type (void)
190 static GType my_type = 0;
192 static const GTypeInfo my_info = {
193 sizeof(ModestHeaderViewClass),
194 NULL, /* base init */
195 NULL, /* base finalize */
196 (GClassInitFunc) modest_header_view_class_init,
197 NULL, /* class finalize */
198 NULL, /* class data */
199 sizeof(ModestHeaderView),
201 (GInstanceInitFunc) modest_header_view_init,
205 static const GInterfaceInfo tny_folder_observer_info =
207 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
208 NULL, /* interface_finalize */
209 NULL /* interface_data */
211 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
215 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
216 &tny_folder_observer_info);
224 modest_header_view_class_init (ModestHeaderViewClass *klass)
226 GObjectClass *gobject_class;
227 gobject_class = (GObjectClass*) klass;
229 parent_class = g_type_class_peek_parent (klass);
230 gobject_class->finalize = modest_header_view_finalize;
231 gobject_class->dispose = modest_header_view_dispose;
233 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
235 signals[HEADER_SELECTED_SIGNAL] =
236 g_signal_new ("header_selected",
237 G_TYPE_FROM_CLASS (gobject_class),
239 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
241 g_cclosure_marshal_VOID__POINTER,
242 G_TYPE_NONE, 1, G_TYPE_POINTER);
244 signals[HEADER_ACTIVATED_SIGNAL] =
245 g_signal_new ("header_activated",
246 G_TYPE_FROM_CLASS (gobject_class),
248 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
250 g_cclosure_marshal_VOID__POINTER,
251 G_TYPE_NONE, 1, G_TYPE_POINTER);
254 signals[ITEM_NOT_FOUND_SIGNAL] =
255 g_signal_new ("item_not_found",
256 G_TYPE_FROM_CLASS (gobject_class),
258 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
260 g_cclosure_marshal_VOID__INT,
261 G_TYPE_NONE, 1, G_TYPE_INT);
263 signals[MSG_COUNT_CHANGED_SIGNAL] =
264 g_signal_new ("msg_count_changed",
265 G_TYPE_FROM_CLASS (gobject_class),
267 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
269 modest_marshal_VOID__POINTER_POINTER,
270 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
272 signals[UPDATING_MSG_LIST_SIGNAL] =
273 g_signal_new ("updating-msg-list",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
278 g_cclosure_marshal_VOID__BOOLEAN,
279 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
283 tny_folder_observer_init (TnyFolderObserverIface *klass)
285 klass->update_func = folder_monitor_update;
288 static GtkTreeViewColumn*
289 get_new_column (const gchar *name, GtkCellRenderer *renderer,
290 gboolean resizable, gint sort_col_id, gboolean show_as_text,
291 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
293 GtkTreeViewColumn *column;
295 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
296 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
298 gtk_tree_view_column_set_resizable (column, resizable);
300 gtk_tree_view_column_set_expand (column, TRUE);
303 gtk_tree_view_column_add_attribute (column, renderer, "text",
305 if (sort_col_id >= 0)
306 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
308 gtk_tree_view_column_set_sort_indicator (column, FALSE);
309 gtk_tree_view_column_set_reorderable (column, TRUE);
312 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
319 remove_all_columns (ModestHeaderView *obj)
321 GList *columns, *cursor;
323 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
325 for (cursor = columns; cursor; cursor = cursor->next)
326 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
327 GTK_TREE_VIEW_COLUMN(cursor->data));
328 g_list_free (columns);
332 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
334 GtkTreeModel *tree_filter, *sortable;
335 GtkTreeViewColumn *column=NULL;
336 GtkTreeSelection *selection = NULL;
337 GtkCellRenderer *renderer_msgtype,*renderer_header,
338 *renderer_attach, *renderer_compact_date_or_status;
339 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
340 *renderer_subject, *renderer_subject_box, *renderer_recpt,
342 ModestHeaderViewPrivate *priv;
343 GtkTreeViewColumn *compact_column = NULL;
346 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
348 /* FIXME: check whether these renderers need to be freed */
349 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
350 renderer_attach = gtk_cell_renderer_pixbuf_new ();
351 renderer_priority = gtk_cell_renderer_pixbuf_new ();
352 renderer_header = gtk_cell_renderer_text_new ();
354 renderer_compact_header = modest_vbox_cell_renderer_new ();
355 renderer_recpt_box = modest_hbox_cell_renderer_new ();
356 renderer_subject_box = modest_hbox_cell_renderer_new ();
357 renderer_recpt = gtk_cell_renderer_text_new ();
358 renderer_subject = gtk_cell_renderer_text_new ();
359 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
361 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
362 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
363 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
364 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
365 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
366 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
367 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
368 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
369 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
370 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
371 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
372 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
373 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
374 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
376 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
377 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
378 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
379 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
380 g_object_set(G_OBJECT(renderer_header),
381 "ellipsize", PANGO_ELLIPSIZE_END,
383 g_object_set (G_OBJECT (renderer_subject),
384 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
386 g_object_set (G_OBJECT (renderer_recpt),
387 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
389 g_object_set(G_OBJECT(renderer_compact_date_or_status),
390 "xalign", 1.0, "yalign", 0.0,
392 g_object_set (G_OBJECT (renderer_priority),
393 "yalign", 1.0, NULL);
394 g_object_set (G_OBJECT (renderer_attach),
395 "yalign", 0.0, NULL);
397 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
398 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
399 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
401 remove_all_columns (self);
403 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
404 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
405 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
406 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
408 /* Add new columns */
409 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
410 ModestHeaderViewColumn col =
411 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
413 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
414 g_printerr ("modest: invalid column %d in column list\n", col);
420 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
421 column = get_new_column (_("M"), renderer_msgtype, FALSE,
422 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
424 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
426 gtk_tree_view_column_set_fixed_width (column, 45);
429 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
430 column = get_new_column (_("A"), renderer_attach, FALSE,
431 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
433 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
435 gtk_tree_view_column_set_fixed_width (column, 45);
439 case MODEST_HEADER_VIEW_COLUMN_FROM:
440 column = get_new_column (_("From"), renderer_header, TRUE,
441 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
443 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
444 GINT_TO_POINTER(TRUE));
447 case MODEST_HEADER_VIEW_COLUMN_TO:
448 column = get_new_column (_("To"), renderer_header, TRUE,
449 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
451 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
452 GINT_TO_POINTER(FALSE));
455 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
456 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
457 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
459 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
460 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
461 compact_column = column;
464 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
465 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
466 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
468 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
469 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
470 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
471 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
472 compact_column = column;
476 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
477 column = get_new_column (_("Subject"), renderer_header, TRUE,
478 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
480 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
484 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
485 column = get_new_column (_("Received"), renderer_header, TRUE,
486 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
488 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
489 GINT_TO_POINTER(TRUE));
492 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
493 column = get_new_column (_("Sent"), renderer_header, TRUE,
494 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
496 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
497 GINT_TO_POINTER(FALSE));
500 case MODEST_HEADER_VIEW_COLUMN_SIZE:
501 column = get_new_column (_("Size"), renderer_header, TRUE,
502 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
504 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
507 case MODEST_HEADER_VIEW_COLUMN_STATUS:
508 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
509 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
511 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
516 g_return_val_if_reached(FALSE);
519 /* we keep the column id around */
520 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
521 GINT_TO_POINTER(col));
523 /* we need this ptr when sorting the rows */
524 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
526 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
530 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
531 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
532 (GtkTreeIterCompareFunc) cmp_rows,
533 compact_column, NULL);
534 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
535 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
536 (GtkTreeIterCompareFunc) cmp_subject_rows,
537 compact_column, NULL);
545 modest_header_view_init (ModestHeaderView *obj)
547 ModestHeaderViewPrivate *priv;
550 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
554 priv->monitor = NULL;
555 priv->observers_lock = g_mutex_new ();
557 priv->status = HEADER_VIEW_INIT;
559 priv->observer_list_lock = g_mutex_new();
560 priv->observer_list = NULL;
562 priv->clipboard = modest_runtime_get_email_clipboard ();
563 priv->hidding_ids = NULL;
564 priv->n_selected = 0;
565 priv->selection_changed_handler = 0;
566 priv->acc_removed_handler = 0;
568 /* Sort parameters */
569 for (j=0; j < 2; j++) {
570 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
571 priv->sort_colid[j][i] = -1;
572 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
576 setup_drag_and_drop (GTK_WIDGET(obj));
580 modest_header_view_dispose (GObject *obj)
582 ModestHeaderView *self;
583 ModestHeaderViewPrivate *priv;
584 GtkTreeSelection *sel;
586 self = MODEST_HEADER_VIEW(obj);
587 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
589 /* Free in the dispose to avoid unref cycles */
591 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
592 g_object_unref (G_OBJECT (priv->folder));
596 /* We need to do this here in the dispose because the
597 selection won't exist when finalizing */
598 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
599 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
600 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
601 priv->selection_changed_handler = 0;
604 G_OBJECT_CLASS(parent_class)->dispose (obj);
608 modest_header_view_finalize (GObject *obj)
610 ModestHeaderView *self;
611 ModestHeaderViewPrivate *priv;
613 self = MODEST_HEADER_VIEW(obj);
614 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
616 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
617 priv->acc_removed_handler)) {
618 g_signal_handler_disconnect (modest_runtime_get_account_store (),
619 priv->acc_removed_handler);
622 /* There is no need to lock because there should not be any
623 * reference to self now. */
624 g_mutex_free(priv->observer_list_lock);
625 g_slist_free(priv->observer_list);
627 g_mutex_lock (priv->observers_lock);
629 tny_folder_monitor_stop (priv->monitor);
630 g_object_unref (G_OBJECT (priv->monitor));
632 g_mutex_unlock (priv->observers_lock);
633 g_mutex_free (priv->observers_lock);
635 /* Clear hidding array created by cut operation */
636 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
638 G_OBJECT_CLASS(parent_class)->finalize (obj);
643 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
646 GtkTreeSelection *sel;
647 ModestHeaderView *self;
648 ModestHeaderViewPrivate *priv;
650 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
653 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
654 self = MODEST_HEADER_VIEW(obj);
655 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
657 modest_header_view_set_style (self, style);
659 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
660 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
661 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
663 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
664 TRUE); /* alternating row colors */
666 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
667 priv->selection_changed_handler =
668 g_signal_connect_after (sel, "changed",
669 G_CALLBACK(on_selection_changed), self);
671 g_signal_connect (self, "row-activated",
672 G_CALLBACK (on_header_row_activated), NULL);
674 g_signal_connect (self, "focus-in-event",
675 G_CALLBACK(on_focus_in), NULL);
676 g_signal_connect (self, "focus-out-event",
677 G_CALLBACK(on_focus_out), NULL);
679 g_signal_connect (self, "button-press-event",
680 G_CALLBACK(on_button_press_event), NULL);
681 g_signal_connect (self, "button-release-event",
682 G_CALLBACK(on_button_release_event), NULL);
684 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
686 G_CALLBACK (on_account_removed),
689 g_signal_connect (self, "expose-event",
690 G_CALLBACK(modest_header_view_on_expose_event),
693 return GTK_WIDGET(self);
698 modest_header_view_count_selected_headers (ModestHeaderView *self)
700 GtkTreeSelection *sel;
703 g_return_val_if_fail (self, 0);
705 /* Get selection object and check selected rows count */
706 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
707 selected_rows = gtk_tree_selection_count_selected_rows (sel);
709 return selected_rows;
713 modest_header_view_has_selected_headers (ModestHeaderView *self)
715 GtkTreeSelection *sel;
718 g_return_val_if_fail (self, FALSE);
720 /* Get selection object and check selected rows count */
721 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
722 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
729 modest_header_view_get_selected_headers (ModestHeaderView *self)
731 GtkTreeSelection *sel;
732 ModestHeaderViewPrivate *priv;
733 TnyList *header_list = NULL;
735 GList *list, *tmp = NULL;
736 GtkTreeModel *tree_model = NULL;
739 g_return_val_if_fail (self, NULL);
741 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
743 /* Get selected rows */
744 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
745 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
748 header_list = tny_simple_list_new();
750 list = g_list_reverse (list);
753 /* get header from selection */
754 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
755 gtk_tree_model_get (tree_model, &iter,
756 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
758 /* Prepend to list */
759 tny_list_prepend (header_list, G_OBJECT (header));
760 g_object_unref (G_OBJECT (header));
762 tmp = g_list_next (tmp);
765 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
772 /* scroll our list view so the selected item is visible */
774 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
776 #ifdef MODEST_PLATFORM_GNOME
778 GtkTreePath *selected_path;
779 GtkTreePath *start, *end;
783 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
784 selected_path = gtk_tree_model_get_path (model, iter);
786 start = gtk_tree_path_new ();
787 end = gtk_tree_path_new ();
789 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
791 if (gtk_tree_path_compare (selected_path, start) < 0 ||
792 gtk_tree_path_compare (end, selected_path) < 0)
793 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
794 selected_path, NULL, TRUE,
797 gtk_tree_path_free (selected_path);
798 gtk_tree_path_free (start);
799 gtk_tree_path_free (end);
801 #endif /* MODEST_PLATFORM_GNOME */
806 modest_header_view_select_next (ModestHeaderView *self)
808 GtkTreeSelection *sel;
813 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
814 path = get_selected_row (GTK_TREE_VIEW(self), &model);
815 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
816 /* Unselect previous path */
817 gtk_tree_selection_unselect_path (sel, path);
819 /* Move path down and selects new one */
820 if (gtk_tree_model_iter_next (model, &iter)) {
821 gtk_tree_selection_select_iter (sel, &iter);
822 scroll_to_selected (self, &iter, FALSE);
824 gtk_tree_path_free(path);
830 modest_header_view_select_prev (ModestHeaderView *self)
832 GtkTreeSelection *sel;
837 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
838 path = get_selected_row (GTK_TREE_VIEW(self), &model);
839 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
840 /* Unselect previous path */
841 gtk_tree_selection_unselect_path (sel, path);
844 if (gtk_tree_path_prev (path)) {
845 gtk_tree_model_get_iter (model, &iter, path);
847 /* Select the new one */
848 gtk_tree_selection_select_iter (sel, &iter);
849 scroll_to_selected (self, &iter, TRUE);
852 gtk_tree_path_free (path);
857 modest_header_view_get_columns (ModestHeaderView *self)
859 g_return_val_if_fail (self, FALSE);
860 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
866 modest_header_view_set_style (ModestHeaderView *self,
867 ModestHeaderViewStyle style)
869 ModestHeaderViewPrivate *priv;
870 gboolean show_col_headers = FALSE;
871 ModestHeaderViewStyle old_style;
873 g_return_val_if_fail (self, FALSE);
874 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
877 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
878 if (priv->style == style)
879 return TRUE; /* nothing to do */
882 case MODEST_HEADER_VIEW_STYLE_DETAILS:
883 show_col_headers = TRUE;
885 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
888 g_return_val_if_reached (FALSE);
890 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
891 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
893 old_style = priv->style;
900 ModestHeaderViewStyle
901 modest_header_view_get_style (ModestHeaderView *self)
903 g_return_val_if_fail (self, FALSE);
904 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
907 /* This is used to automatically select the first header if the user
908 * has not selected any header yet.
911 modest_header_view_on_expose_event(GtkTreeView *header_view,
912 GdkEventExpose *event,
915 GtkTreeSelection *sel;
917 GtkTreeIter tree_iter;
919 model = gtk_tree_view_get_model(header_view);
921 sel = gtk_tree_view_get_selection(header_view);
922 if(!gtk_tree_selection_count_selected_rows(sel))
923 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
924 /* Prevent the widget from getting the focus
925 when selecting the first item */
926 g_object_set(header_view, "can-focus", FALSE, NULL);
927 gtk_tree_selection_select_iter(sel, &tree_iter);
928 g_object_set(header_view, "can-focus", TRUE, NULL);
935 * This function sets a sortable model in the header view. It's just
936 * used for developing purposes, because it only does a
937 * gtk_tree_view_set_model
940 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
942 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
943 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
944 /* GtkTreeModel *old_model; */
945 /* ModestHeaderViewPrivate *priv; */
946 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
947 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
949 /* /\* Set new model *\/ */
950 /* gtk_tree_view_set_model (header_view, model); */
952 gtk_tree_view_set_model (header_view, model);
956 modest_header_view_get_folder (ModestHeaderView *self)
958 ModestHeaderViewPrivate *priv;
959 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
962 g_object_ref (priv->folder);
968 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
972 ModestHeaderViewPrivate *priv;
973 GList *cols, *cursor;
974 GtkTreeModel *filter_model, *sortable;
976 GtkSortType sort_type;
978 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
980 headers = TNY_LIST (tny_gtk_header_list_model_new ());
982 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
983 folder, FALSE, NULL, NULL, NULL);
985 /* Add IDLE observer (monitor) and another folder observer for
986 new messages (self) */
987 g_mutex_lock (priv->observers_lock);
989 tny_folder_monitor_stop (priv->monitor);
990 g_object_unref (G_OBJECT (priv->monitor));
992 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
993 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
994 tny_folder_monitor_start (priv->monitor);
995 g_mutex_unlock (priv->observers_lock);
997 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
998 g_object_unref (G_OBJECT (headers));
1000 /* Init filter_row function to examine empty status */
1001 priv->status = HEADER_VIEW_INIT;
1003 /* Create a tree model filter to hide and show rows for cut operations */
1004 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1005 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1009 g_object_unref (G_OBJECT (sortable));
1011 /* install our special sorting functions */
1012 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1014 /* Restore sort column id */
1016 type = modest_tny_folder_guess_folder_type (folder);
1017 if (type == TNY_FOLDER_TYPE_INVALID)
1018 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1020 sort_colid = modest_header_view_get_sort_column_id (self, type);
1021 sort_type = modest_header_view_get_sort_type (self, type);
1022 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1025 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1026 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1027 (GtkTreeIterCompareFunc) cmp_rows,
1029 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1030 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1031 (GtkTreeIterCompareFunc) cmp_subject_rows,
1036 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1037 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1038 tny_folder_get_id(folder));
1039 g_object_unref (G_OBJECT (filter_model));
1040 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1041 /* g_object_unref (G_OBJECT (sortable)); */
1048 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1050 GtkSortType sort_type)
1052 ModestHeaderViewPrivate *priv = NULL;
1053 GtkTreeModel *tree_filter, *sortable = NULL;
1056 /* Get model and private data */
1057 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1058 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1059 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1060 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1062 /* Sort tree model */
1063 type = modest_tny_folder_guess_folder_type (priv->folder);
1064 if (type == TNY_FOLDER_TYPE_INVALID)
1065 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1067 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1070 /* Store new sort parameters */
1071 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1076 modest_header_view_set_sort_params (ModestHeaderView *self,
1078 GtkSortType sort_type,
1081 ModestHeaderViewPrivate *priv;
1082 ModestHeaderViewStyle style;
1084 style = modest_header_view_get_style (self);
1085 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1087 priv->sort_colid[style][type] = sort_colid;
1088 priv->sort_type[style][type] = sort_type;
1092 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1095 ModestHeaderViewPrivate *priv;
1096 ModestHeaderViewStyle style;
1098 style = modest_header_view_get_style (self);
1099 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1101 return priv->sort_colid[style][type];
1105 modest_header_view_get_sort_type (ModestHeaderView *self,
1108 ModestHeaderViewPrivate *priv;
1109 ModestHeaderViewStyle style;
1111 style = modest_header_view_get_style (self);
1112 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1114 return priv->sort_type[style][type];
1118 ModestHeaderView *header_view;
1119 RefreshAsyncUserCallback cb;
1124 folder_refreshed_cb (ModestMailOperation *mail_op,
1128 ModestHeaderViewPrivate *priv;
1129 SetFolderHelper *info;
1131 info = (SetFolderHelper*) user_data;
1133 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1137 info->cb (mail_op, folder, info->user_data);
1139 /* Start the folder count changes observer. We do not need it
1140 before the refresh. Note that the monitor could still be
1141 called for this refresh but now we know that the callback
1142 was previously called */
1143 g_mutex_lock (priv->observers_lock);
1144 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1145 g_mutex_unlock (priv->observers_lock);
1147 /* Notify the observers that the update is over */
1148 g_signal_emit (G_OBJECT (info->header_view),
1149 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1156 modest_header_view_set_folder (ModestHeaderView *self,
1158 RefreshAsyncUserCallback callback,
1161 ModestHeaderViewPrivate *priv;
1162 SetFolderHelper *info;
1163 ModestWindow *main_win;
1165 g_return_if_fail (self);
1167 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1169 main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1170 FALSE); /* don't create */
1172 g_warning ("%s: BUG: no main window", __FUNCTION__);
1177 g_mutex_lock (priv->observers_lock);
1178 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1179 g_object_unref (priv->folder);
1180 priv->folder = NULL;
1181 g_mutex_unlock (priv->observers_lock);
1185 ModestMailOperation *mail_op = NULL;
1186 GtkTreeSelection *selection;
1188 /* Set folder in the model */
1189 modest_header_view_set_folder_intern (self, folder);
1191 /* Pick my reference. Nothing to do with the mail operation */
1192 priv->folder = g_object_ref (folder);
1194 /* Clear the selection if exists */
1195 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1196 gtk_tree_selection_unselect_all(selection);
1197 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1199 /* Notify the observers that the update begins */
1200 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1203 /* create the helper */
1204 info = g_malloc0 (sizeof(SetFolderHelper));
1205 info->header_view = self;
1206 info->cb = callback;
1207 info->user_data = user_data;
1209 /* Create the mail operation (source will be the parent widget) */
1210 mail_op = modest_mail_operation_new (G_OBJECT(main_win));
1211 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1214 /* Refresh the folder asynchronously */
1215 modest_mail_operation_refresh_folder (mail_op,
1217 folder_refreshed_cb,
1221 g_object_unref (mail_op);
1223 g_mutex_lock (priv->observers_lock);
1225 if (priv->monitor) {
1226 tny_folder_monitor_stop (priv->monitor);
1227 g_object_unref (G_OBJECT (priv->monitor));
1228 priv->monitor = NULL;
1230 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1232 modest_header_view_notify_observers(self, NULL, NULL);
1234 g_mutex_unlock (priv->observers_lock);
1236 /* Notify the observers that the update is over */
1237 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1243 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1244 GtkTreeViewColumn *column, gpointer userdata)
1246 ModestHeaderView *self = NULL;
1247 ModestHeaderViewPrivate *priv = NULL;
1249 GtkTreeModel *model = NULL;
1250 TnyHeader *header = NULL;
1251 TnyHeaderFlags flags;
1253 self = MODEST_HEADER_VIEW (treeview);
1254 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1256 model = gtk_tree_view_get_model (treeview);
1257 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1260 /* get the first selected item */
1261 gtk_tree_model_get (model, &iter,
1262 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1263 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1266 /* Dont open DELETED messages */
1267 if (flags & TNY_HEADER_FLAG_DELETED) {
1268 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1273 g_signal_emit (G_OBJECT(self),
1274 signals[HEADER_ACTIVATED_SIGNAL],
1280 g_object_unref (G_OBJECT (header));
1285 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1287 GtkTreeModel *model;
1288 TnyHeader *header = NULL;
1289 GtkTreePath *path = NULL;
1291 ModestHeaderView *self;
1292 ModestHeaderViewPrivate *priv;
1293 GList *selected = NULL;
1295 g_return_if_fail (sel);
1296 g_return_if_fail (user_data);
1298 self = MODEST_HEADER_VIEW (user_data);
1299 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1301 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1302 if (selected != NULL)
1303 path = (GtkTreePath *) selected->data;
1304 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1305 return; /* msg was _un_selected */
1307 gtk_tree_model_get (model, &iter,
1308 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1312 g_signal_emit (G_OBJECT(self),
1313 signals[HEADER_SELECTED_SIGNAL],
1316 g_object_unref (G_OBJECT (header));
1318 /* free all items in 'selected' */
1319 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1320 g_list_free (selected);
1324 /* PROTECTED method. It's useful when we want to force a given
1325 selection to reload a msg. For example if we have selected a header
1326 in offline mode, when Modest become online, we want to reload the
1327 message automatically without an user click over the header */
1329 _modest_header_view_change_selection (GtkTreeSelection *selection,
1332 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1333 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1335 on_selection_changed (selection, user_data);
1338 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1346 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1350 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1354 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1363 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1370 /* static int counter = 0; */
1372 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1373 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1374 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1378 case TNY_HEADER_FLAG_ATTACHMENTS:
1380 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1381 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1382 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1383 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1385 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1386 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1388 return cmp ? cmp : t1 - t2;
1390 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1391 TnyHeader *header1 = NULL, *header2 = NULL;
1393 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1394 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1395 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1396 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1398 /* This is for making priority values respect the intuitive sort relationship
1399 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1401 if (header1 && header2) {
1402 cmp = compare_priorities (tny_header_get_priority (header1),
1403 tny_header_get_priority (header2));
1404 g_object_unref (header1);
1405 g_object_unref (header2);
1407 return cmp ? cmp : t1 - t2;
1413 return &iter1 - &iter2; /* oughhhh */
1418 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1424 /* static int counter = 0; */
1426 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1428 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1429 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1430 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1431 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1433 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1434 val2 + modest_text_utils_get_subject_prefix_len(val2),
1441 /* Drag and drop stuff */
1443 drag_data_get_cb (GtkWidget *widget,
1444 GdkDragContext *context,
1445 GtkSelectionData *selection_data,
1450 ModestHeaderView *self = NULL;
1451 ModestHeaderViewPrivate *priv = NULL;
1453 self = MODEST_HEADER_VIEW (widget);
1454 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1456 /* Set the data. Do not use the current selection because it
1457 could be different than the selection at the beginning of
1459 modest_dnd_selection_data_set_paths (selection_data,
1460 priv->drag_begin_cached_selected_rows);
1464 * We're caching the selected rows at the beginning because the
1465 * selection could change between drag-begin and drag-data-get, for
1466 * example if we have a set of rows already selected, and then we
1467 * click in one of them (without SHIFT key pressed) and begin a drag,
1468 * the selection at that moment contains all the selected lines, but
1469 * after dropping the selection, the release event provokes that only
1470 * the row used to begin the drag is selected, so at the end the
1471 * drag&drop affects only one rows instead of all the selected ones.
1475 drag_begin_cb (GtkWidget *widget,
1476 GdkDragContext *context,
1479 ModestHeaderView *self = NULL;
1480 ModestHeaderViewPrivate *priv = NULL;
1481 GtkTreeSelection *selection;
1483 self = MODEST_HEADER_VIEW (widget);
1484 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1486 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1487 priv->drag_begin_cached_selected_rows =
1488 gtk_tree_selection_get_selected_rows (selection, NULL);
1492 * We use the drag-end signal to clear the cached selection, we use
1493 * this because this allways happens, whether or not the d&d was a
1497 drag_end_cb (GtkWidget *widget,
1501 ModestHeaderView *self = NULL;
1502 ModestHeaderViewPrivate *priv = NULL;
1504 self = MODEST_HEADER_VIEW (widget);
1505 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1507 /* Free cached data */
1508 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1509 g_list_free (priv->drag_begin_cached_selected_rows);
1510 priv->drag_begin_cached_selected_rows = NULL;
1513 /* Header view drag types */
1514 const GtkTargetEntry header_view_drag_types[] = {
1515 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1519 enable_drag_and_drop (GtkWidget *self)
1521 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1522 header_view_drag_types,
1523 G_N_ELEMENTS (header_view_drag_types),
1524 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1528 disable_drag_and_drop (GtkWidget *self)
1530 gtk_drag_source_unset (self);
1534 setup_drag_and_drop (GtkWidget *self)
1536 enable_drag_and_drop(self);
1537 g_signal_connect(G_OBJECT (self), "drag_data_get",
1538 G_CALLBACK(drag_data_get_cb), NULL);
1540 g_signal_connect(G_OBJECT (self), "drag_begin",
1541 G_CALLBACK(drag_begin_cb), NULL);
1543 g_signal_connect(G_OBJECT (self), "drag_end",
1544 G_CALLBACK(drag_end_cb), NULL);
1547 static GtkTreePath *
1548 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1550 GtkTreePath *path = NULL;
1551 GtkTreeSelection *sel = NULL;
1554 sel = gtk_tree_view_get_selection(self);
1555 rows = gtk_tree_selection_get_selected_rows (sel, model);
1557 if ((rows == NULL) || (g_list_length(rows) != 1))
1560 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1565 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1572 * This function moves the tree view scroll to the current selected
1573 * row when the widget grabs the focus
1576 on_focus_in (GtkWidget *self,
1577 GdkEventFocus *event,
1580 GtkTreeSelection *selection;
1581 GtkTreeModel *model;
1582 GList *selected = NULL;
1583 GtkTreePath *selected_path = NULL;
1585 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1589 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1590 /* If none selected yet, pick the first one */
1591 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1595 /* Return if the model is empty */
1596 if (!gtk_tree_model_get_iter_first (model, &iter))
1599 path = gtk_tree_model_get_path (model, &iter);
1600 gtk_tree_selection_select_path (selection, path);
1601 gtk_tree_path_free (path);
1604 /* Need to get the all the rows because is selection multiple */
1605 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1606 if (selected == NULL) return FALSE;
1607 selected_path = (GtkTreePath *) selected->data;
1609 /* Check if we need to scroll */
1610 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1611 GtkTreePath *start_path = NULL;
1612 GtkTreePath *end_path = NULL;
1613 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1617 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1618 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1620 /* Scroll to first path */
1621 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1630 gtk_tree_path_free (start_path);
1632 gtk_tree_path_free (end_path);
1634 #endif /* GTK_CHECK_VERSION */
1637 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1638 g_list_free (selected);
1644 on_focus_out (GtkWidget *self,
1645 GdkEventFocus *event,
1649 if (!gtk_widget_is_focus (self)) {
1650 GtkTreeSelection *selection = NULL;
1651 GList *selected_rows = NULL;
1652 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1653 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1654 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1655 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1656 gtk_tree_selection_unselect_all (selection);
1657 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1658 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1659 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1660 g_list_free (selected_rows);
1667 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1669 enable_drag_and_drop(self);
1674 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1676 GtkTreeSelection *selection = NULL;
1677 GtkTreePath *path = NULL;
1678 gboolean already_selected = FALSE;
1680 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1681 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1682 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1685 /* Enable drag and drop onlly if the user clicks on a row that
1686 it's already selected. If not, let him select items using
1688 if (!already_selected) {
1689 disable_drag_and_drop(self);
1693 gtk_tree_path_free(path);
1700 folder_monitor_update (TnyFolderObserver *self,
1701 TnyFolderChange *change)
1703 ModestHeaderViewPrivate *priv = NULL;
1704 TnyFolderChangeChanged changed;
1705 TnyFolder *folder = NULL;
1707 changed = tny_folder_change_get_changed (change);
1709 /* Do not notify the observers if the folder of the header
1710 view has changed before this call to the observer
1712 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1713 folder = tny_folder_change_get_folder (change);
1714 if (folder != priv->folder)
1717 /* Check folder count */
1718 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1719 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1721 g_mutex_lock (priv->observers_lock);
1723 /* Emit signal to evaluate how headers changes affects
1724 to the window view */
1725 g_signal_emit (G_OBJECT(self),
1726 signals[MSG_COUNT_CHANGED_SIGNAL],
1729 /* Added or removed headers, so data stored on cliboard are invalid */
1730 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1731 modest_email_clipboard_clear (priv->clipboard);
1733 g_mutex_unlock (priv->observers_lock);
1739 g_object_unref (folder);
1743 modest_header_view_is_empty (ModestHeaderView *self)
1745 ModestHeaderViewPrivate *priv = NULL;
1747 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1749 return priv->status == HEADER_VIEW_EMPTY;
1753 modest_header_view_clear (ModestHeaderView *self)
1755 modest_header_view_set_folder (self, NULL, NULL, NULL);
1759 modest_header_view_copy_selection (ModestHeaderView *header_view)
1761 /* Copy selection */
1762 _clipboard_set_selected_data (header_view, FALSE);
1766 modest_header_view_cut_selection (ModestHeaderView *header_view)
1768 ModestHeaderViewPrivate *priv = NULL;
1769 const gchar **hidding = NULL;
1770 guint i, n_selected;
1772 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1773 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1775 /* Copy selection */
1776 _clipboard_set_selected_data (header_view, TRUE);
1778 /* Get hidding ids */
1779 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1781 /* Clear hidding array created by previous cut operation */
1782 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1784 /* Copy hidding array */
1785 priv->n_selected = n_selected;
1786 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1787 for (i=0; i < n_selected; i++)
1788 priv->hidding_ids[i] = g_strdup(hidding[i]);
1790 /* Hide cut headers */
1791 modest_header_view_refilter (header_view);
1798 _clipboard_set_selected_data (ModestHeaderView *header_view,
1801 ModestHeaderViewPrivate *priv = NULL;
1802 TnyList *headers = NULL;
1804 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1805 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1807 /* Set selected data on clipboard */
1808 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1809 headers = modest_header_view_get_selected_headers (header_view);
1810 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1813 g_object_unref (headers);
1819 filter_row (GtkTreeModel *model,
1823 ModestHeaderViewPrivate *priv = NULL;
1824 TnyHeaderFlags flags;
1825 TnyHeader *header = NULL;
1828 gboolean visible = TRUE;
1829 gboolean found = FALSE;
1831 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1832 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1834 /* Get header from model */
1835 gtk_tree_model_get (model, iter,
1836 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1837 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1840 /* Hide mark as deleted heders */
1841 if (flags & TNY_HEADER_FLAG_DELETED) {
1846 /* If no data on clipboard, return always TRUE */
1847 if (modest_email_clipboard_cleared(priv->clipboard)) {
1852 /* Get message id from header (ensure is a valid id) */
1853 if (!header) return FALSE;
1854 id = g_strdup(tny_header_get_message_id (header));
1857 if (priv->hidding_ids != NULL) {
1858 for (i=0; i < priv->n_selected && !found; i++)
1859 if (priv->hidding_ids[i] != NULL && id != NULL)
1860 found = (!strcmp (priv->hidding_ids[i], id));
1866 priv->status = ((gboolean) priv->status) && !visible;
1870 g_object_unref (header);
1877 _clear_hidding_filter (ModestHeaderView *header_view)
1879 ModestHeaderViewPrivate *priv = NULL;
1882 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1883 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1885 if (priv->hidding_ids != NULL) {
1886 for (i=0; i < priv->n_selected; i++)
1887 g_free (priv->hidding_ids[i]);
1888 g_free(priv->hidding_ids);
1893 modest_header_view_refilter (ModestHeaderView *header_view)
1895 GtkTreeModel *model = NULL;
1896 ModestHeaderViewPrivate *priv = NULL;
1898 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1899 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1901 /* Hide cut headers */
1902 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1903 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1905 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1910 * Called when an account is removed. If I'm showing a folder of the
1911 * account that has been removed then clear the view
1914 on_account_removed (TnyAccountStore *self,
1915 TnyAccount *account,
1918 ModestHeaderViewPrivate *priv = NULL;
1920 /* Ignore changes in transport accounts */
1921 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1924 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1927 TnyAccount *my_account;
1929 my_account = tny_folder_get_account (priv->folder);
1930 if (my_account == account)
1931 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1932 g_object_unref (account);
1936 void modest_header_view_add_observer(
1937 ModestHeaderView *header_view,
1938 ModestHeaderViewObserver *observer)
1940 ModestHeaderViewPrivate *priv = NULL;
1942 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1943 g_assert(observer != NULL);
1944 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1946 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1948 g_mutex_lock(priv->observer_list_lock);
1949 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1950 g_mutex_unlock(priv->observer_list_lock);
1954 modest_header_view_remove_observer(ModestHeaderView *header_view,
1955 ModestHeaderViewObserver *observer)
1957 ModestHeaderViewPrivate *priv = NULL;
1959 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1960 g_assert(observer != NULL);
1961 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1963 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1965 g_mutex_lock(priv->observer_list_lock);
1966 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1967 g_mutex_unlock(priv->observer_list_lock);
1971 modest_header_view_notify_observers(ModestHeaderView *header_view,
1972 GtkTreeModel *model,
1973 const gchar *tny_folder_id)
1975 ModestHeaderViewPrivate *priv = NULL;
1977 ModestHeaderViewObserver *observer;
1979 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1981 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1983 g_mutex_lock(priv->observer_list_lock);
1984 iter = priv->observer_list;
1985 while(iter != NULL){
1986 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1987 modest_header_view_observer_update(observer, model,
1989 iter = g_slist_next(iter);
1991 g_mutex_unlock(priv->observer_list_lock);