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)
1340 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1344 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1348 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1357 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1364 /* static int counter = 0; */
1366 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1367 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1368 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1372 case TNY_HEADER_FLAG_ATTACHMENTS:
1374 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1375 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1376 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1377 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1379 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1380 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1382 return cmp ? cmp : t1 - t2;
1384 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1385 TnyHeader *header1 = NULL, *header2 = NULL;
1387 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1388 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1389 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1390 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1392 /* This is for making priority values respect the intuitive sort relationship
1393 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1395 if (header1 && header2) {
1396 cmp = compare_priorities (tny_header_get_priority (header1),
1397 tny_header_get_priority (header2));
1398 g_object_unref (header1);
1399 g_object_unref (header2);
1401 return cmp ? cmp : t1 - t2;
1407 return &iter1 - &iter2; /* oughhhh */
1412 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1418 /* static int counter = 0; */
1420 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1422 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1423 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1424 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1425 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1427 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1428 val2 + modest_text_utils_get_subject_prefix_len(val2),
1435 /* Drag and drop stuff */
1437 drag_data_get_cb (GtkWidget *widget,
1438 GdkDragContext *context,
1439 GtkSelectionData *selection_data,
1444 ModestHeaderView *self = NULL;
1445 ModestHeaderViewPrivate *priv = NULL;
1447 self = MODEST_HEADER_VIEW (widget);
1448 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1450 /* Set the data. Do not use the current selection because it
1451 could be different than the selection at the beginning of
1453 modest_dnd_selection_data_set_paths (selection_data,
1454 priv->drag_begin_cached_selected_rows);
1458 * We're caching the selected rows at the beginning because the
1459 * selection could change between drag-begin and drag-data-get, for
1460 * example if we have a set of rows already selected, and then we
1461 * click in one of them (without SHIFT key pressed) and begin a drag,
1462 * the selection at that moment contains all the selected lines, but
1463 * after dropping the selection, the release event provokes that only
1464 * the row used to begin the drag is selected, so at the end the
1465 * drag&drop affects only one rows instead of all the selected ones.
1469 drag_begin_cb (GtkWidget *widget,
1470 GdkDragContext *context,
1473 ModestHeaderView *self = NULL;
1474 ModestHeaderViewPrivate *priv = NULL;
1475 GtkTreeSelection *selection;
1477 self = MODEST_HEADER_VIEW (widget);
1478 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1480 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1481 priv->drag_begin_cached_selected_rows =
1482 gtk_tree_selection_get_selected_rows (selection, NULL);
1486 * We use the drag-end signal to clear the cached selection, we use
1487 * this because this allways happens, whether or not the d&d was a
1491 drag_end_cb (GtkWidget *widget,
1495 ModestHeaderView *self = NULL;
1496 ModestHeaderViewPrivate *priv = NULL;
1498 self = MODEST_HEADER_VIEW (widget);
1499 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1501 /* Free cached data */
1502 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1503 g_list_free (priv->drag_begin_cached_selected_rows);
1504 priv->drag_begin_cached_selected_rows = NULL;
1507 /* Header view drag types */
1508 const GtkTargetEntry header_view_drag_types[] = {
1509 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1513 enable_drag_and_drop (GtkWidget *self)
1515 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1516 header_view_drag_types,
1517 G_N_ELEMENTS (header_view_drag_types),
1518 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1522 disable_drag_and_drop (GtkWidget *self)
1524 gtk_drag_source_unset (self);
1528 setup_drag_and_drop (GtkWidget *self)
1530 enable_drag_and_drop(self);
1531 g_signal_connect(G_OBJECT (self), "drag_data_get",
1532 G_CALLBACK(drag_data_get_cb), NULL);
1534 g_signal_connect(G_OBJECT (self), "drag_begin",
1535 G_CALLBACK(drag_begin_cb), NULL);
1537 g_signal_connect(G_OBJECT (self), "drag_end",
1538 G_CALLBACK(drag_end_cb), NULL);
1541 static GtkTreePath *
1542 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1544 GtkTreePath *path = NULL;
1545 GtkTreeSelection *sel = NULL;
1548 sel = gtk_tree_view_get_selection(self);
1549 rows = gtk_tree_selection_get_selected_rows (sel, model);
1551 if ((rows == NULL) || (g_list_length(rows) != 1))
1554 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1559 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1566 * This function moves the tree view scroll to the current selected
1567 * row when the widget grabs the focus
1570 on_focus_in (GtkWidget *self,
1571 GdkEventFocus *event,
1574 GtkTreeSelection *selection;
1575 GtkTreeModel *model;
1576 GList *selected = NULL;
1577 GtkTreePath *selected_path = NULL;
1579 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1583 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1584 /* If none selected yet, pick the first one */
1585 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1589 /* Return if the model is empty */
1590 if (!gtk_tree_model_get_iter_first (model, &iter))
1593 path = gtk_tree_model_get_path (model, &iter);
1594 gtk_tree_selection_select_path (selection, path);
1595 gtk_tree_path_free (path);
1598 /* Need to get the all the rows because is selection multiple */
1599 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1600 if (selected == NULL) return FALSE;
1601 selected_path = (GtkTreePath *) selected->data;
1603 /* Check if we need to scroll */
1604 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1605 GtkTreePath *start_path = NULL;
1606 GtkTreePath *end_path = NULL;
1607 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1611 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1612 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1614 /* Scroll to first path */
1615 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1624 gtk_tree_path_free (start_path);
1626 gtk_tree_path_free (end_path);
1628 #endif /* GTK_CHECK_VERSION */
1631 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1632 g_list_free (selected);
1638 on_focus_out (GtkWidget *self,
1639 GdkEventFocus *event,
1643 if (!gtk_widget_is_focus (self)) {
1644 GtkTreeSelection *selection = NULL;
1645 GList *selected_rows = NULL;
1646 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1647 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1648 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1649 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1650 gtk_tree_selection_unselect_all (selection);
1651 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1652 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1653 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1654 g_list_free (selected_rows);
1661 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1663 enable_drag_and_drop(self);
1668 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1670 GtkTreeSelection *selection = NULL;
1671 GtkTreePath *path = NULL;
1672 gboolean already_selected = FALSE;
1674 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1675 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1676 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1679 /* Enable drag and drop onlly if the user clicks on a row that
1680 it's already selected. If not, let him select items using
1682 if (!already_selected) {
1683 disable_drag_and_drop(self);
1687 gtk_tree_path_free(path);
1694 folder_monitor_update (TnyFolderObserver *self,
1695 TnyFolderChange *change)
1697 ModestHeaderViewPrivate *priv = NULL;
1698 TnyFolderChangeChanged changed;
1699 TnyFolder *folder = NULL;
1701 changed = tny_folder_change_get_changed (change);
1703 /* Do not notify the observers if the folder of the header
1704 view has changed before this call to the observer
1706 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1707 folder = tny_folder_change_get_folder (change);
1708 if (folder != priv->folder)
1711 /* Check folder count */
1712 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1713 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1715 g_mutex_lock (priv->observers_lock);
1717 /* Emit signal to evaluate how headers changes affects
1718 to the window view */
1719 g_signal_emit (G_OBJECT(self),
1720 signals[MSG_COUNT_CHANGED_SIGNAL],
1723 /* Added or removed headers, so data stored on cliboard are invalid */
1724 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1725 modest_email_clipboard_clear (priv->clipboard);
1727 g_mutex_unlock (priv->observers_lock);
1733 g_object_unref (folder);
1737 modest_header_view_is_empty (ModestHeaderView *self)
1739 ModestHeaderViewPrivate *priv = NULL;
1741 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1743 return priv->status == HEADER_VIEW_EMPTY;
1747 modest_header_view_clear (ModestHeaderView *self)
1749 modest_header_view_set_folder (self, NULL, NULL, NULL);
1753 modest_header_view_copy_selection (ModestHeaderView *header_view)
1755 /* Copy selection */
1756 _clipboard_set_selected_data (header_view, FALSE);
1760 modest_header_view_cut_selection (ModestHeaderView *header_view)
1762 ModestHeaderViewPrivate *priv = NULL;
1763 const gchar **hidding = NULL;
1764 guint i, n_selected;
1766 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1767 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1769 /* Copy selection */
1770 _clipboard_set_selected_data (header_view, TRUE);
1772 /* Get hidding ids */
1773 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1775 /* Clear hidding array created by previous cut operation */
1776 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1778 /* Copy hidding array */
1779 priv->n_selected = n_selected;
1780 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1781 for (i=0; i < n_selected; i++)
1782 priv->hidding_ids[i] = g_strdup(hidding[i]);
1784 /* Hide cut headers */
1785 modest_header_view_refilter (header_view);
1792 _clipboard_set_selected_data (ModestHeaderView *header_view,
1795 ModestHeaderViewPrivate *priv = NULL;
1796 TnyList *headers = NULL;
1798 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1799 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1801 /* Set selected data on clipboard */
1802 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1803 headers = modest_header_view_get_selected_headers (header_view);
1804 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1807 g_object_unref (headers);
1813 filter_row (GtkTreeModel *model,
1817 ModestHeaderViewPrivate *priv = NULL;
1818 TnyHeaderFlags flags;
1819 TnyHeader *header = NULL;
1822 gboolean visible = TRUE;
1823 gboolean found = FALSE;
1825 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1826 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1828 /* Get header from model */
1829 gtk_tree_model_get (model, iter,
1830 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1831 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1834 /* Hide mark as deleted heders */
1835 if (flags & TNY_HEADER_FLAG_DELETED) {
1840 /* If no data on clipboard, return always TRUE */
1841 if (modest_email_clipboard_cleared(priv->clipboard)) {
1846 /* Get message id from header (ensure is a valid id) */
1847 if (!header) return FALSE;
1848 id = g_strdup(tny_header_get_message_id (header));
1851 if (priv->hidding_ids != NULL) {
1852 for (i=0; i < priv->n_selected && !found; i++)
1853 if (priv->hidding_ids[i] != NULL && id != NULL)
1854 found = (!strcmp (priv->hidding_ids[i], id));
1860 priv->status = ((gboolean) priv->status) && !visible;
1864 g_object_unref (header);
1871 _clear_hidding_filter (ModestHeaderView *header_view)
1873 ModestHeaderViewPrivate *priv = NULL;
1876 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1877 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1879 if (priv->hidding_ids != NULL) {
1880 for (i=0; i < priv->n_selected; i++)
1881 g_free (priv->hidding_ids[i]);
1882 g_free(priv->hidding_ids);
1887 modest_header_view_refilter (ModestHeaderView *header_view)
1889 GtkTreeModel *model = NULL;
1890 ModestHeaderViewPrivate *priv = NULL;
1892 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1893 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1895 /* Hide cut headers */
1896 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1897 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1899 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1904 * Called when an account is removed. If I'm showing a folder of the
1905 * account that has been removed then clear the view
1908 on_account_removed (TnyAccountStore *self,
1909 TnyAccount *account,
1912 ModestHeaderViewPrivate *priv = NULL;
1914 /* Ignore changes in transport accounts */
1915 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1918 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1921 TnyAccount *my_account;
1923 my_account = tny_folder_get_account (priv->folder);
1924 if (my_account == account)
1925 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1926 g_object_unref (account);
1930 void modest_header_view_add_observer(
1931 ModestHeaderView *header_view,
1932 ModestHeaderViewObserver *observer)
1934 ModestHeaderViewPrivate *priv = NULL;
1936 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1937 g_assert(observer != NULL);
1938 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1940 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1942 g_mutex_lock(priv->observer_list_lock);
1943 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1944 g_mutex_unlock(priv->observer_list_lock);
1948 modest_header_view_remove_observer(ModestHeaderView *header_view,
1949 ModestHeaderViewObserver *observer)
1951 ModestHeaderViewPrivate *priv = NULL;
1953 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1954 g_assert(observer != NULL);
1955 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1957 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1959 g_mutex_lock(priv->observer_list_lock);
1960 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1961 g_mutex_unlock(priv->observer_list_lock);
1965 modest_header_view_notify_observers(ModestHeaderView *header_view,
1966 GtkTreeModel *model,
1967 const gchar *tny_folder_id)
1969 ModestHeaderViewPrivate *priv = NULL;
1971 ModestHeaderViewObserver *observer;
1973 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1975 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1977 g_mutex_lock(priv->observer_list_lock);
1978 iter = priv->observer_list;
1979 while(iter != NULL){
1980 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1981 modest_header_view_observer_update(observer, model,
1983 iter = g_slist_next(iter);
1985 g_mutex_unlock(priv->observer_list_lock);