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>
41 #include <modest-debug.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 GList *drag_begin_cached_selected_rows;
155 HeaderViewStatus status;
156 guint status_timeout;
159 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
160 struct _HeadersCountChangedHelper {
161 ModestHeaderView *self;
162 TnyFolderChange *change;
166 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
167 MODEST_TYPE_HEADER_VIEW, \
168 ModestHeaderViewPrivate))
172 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
175 HEADER_SELECTED_SIGNAL,
176 HEADER_ACTIVATED_SIGNAL,
177 ITEM_NOT_FOUND_SIGNAL,
178 MSG_COUNT_CHANGED_SIGNAL,
179 UPDATING_MSG_LIST_SIGNAL,
184 static GObjectClass *parent_class = NULL;
186 /* uncomment the following if you have defined any signals */
187 static guint signals[LAST_SIGNAL] = {0};
190 modest_header_view_get_type (void)
192 static GType my_type = 0;
194 static const GTypeInfo my_info = {
195 sizeof(ModestHeaderViewClass),
196 NULL, /* base init */
197 NULL, /* base finalize */
198 (GClassInitFunc) modest_header_view_class_init,
199 NULL, /* class finalize */
200 NULL, /* class data */
201 sizeof(ModestHeaderView),
203 (GInstanceInitFunc) modest_header_view_init,
207 static const GInterfaceInfo tny_folder_observer_info =
209 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
210 NULL, /* interface_finalize */
211 NULL /* interface_data */
213 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
217 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
218 &tny_folder_observer_info);
226 modest_header_view_class_init (ModestHeaderViewClass *klass)
228 GObjectClass *gobject_class;
229 gobject_class = (GObjectClass*) klass;
231 parent_class = g_type_class_peek_parent (klass);
232 gobject_class->finalize = modest_header_view_finalize;
233 gobject_class->dispose = modest_header_view_dispose;
235 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
237 signals[HEADER_SELECTED_SIGNAL] =
238 g_signal_new ("header_selected",
239 G_TYPE_FROM_CLASS (gobject_class),
241 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
243 g_cclosure_marshal_VOID__POINTER,
244 G_TYPE_NONE, 1, G_TYPE_POINTER);
246 signals[HEADER_ACTIVATED_SIGNAL] =
247 g_signal_new ("header_activated",
248 G_TYPE_FROM_CLASS (gobject_class),
250 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
252 g_cclosure_marshal_VOID__POINTER,
253 G_TYPE_NONE, 1, G_TYPE_POINTER);
256 signals[ITEM_NOT_FOUND_SIGNAL] =
257 g_signal_new ("item_not_found",
258 G_TYPE_FROM_CLASS (gobject_class),
260 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
262 g_cclosure_marshal_VOID__INT,
263 G_TYPE_NONE, 1, G_TYPE_INT);
265 signals[MSG_COUNT_CHANGED_SIGNAL] =
266 g_signal_new ("msg_count_changed",
267 G_TYPE_FROM_CLASS (gobject_class),
269 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
271 modest_marshal_VOID__POINTER_POINTER,
272 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
274 signals[UPDATING_MSG_LIST_SIGNAL] =
275 g_signal_new ("updating-msg-list",
276 G_TYPE_FROM_CLASS (gobject_class),
278 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
280 g_cclosure_marshal_VOID__BOOLEAN,
281 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
285 tny_folder_observer_init (TnyFolderObserverIface *klass)
287 klass->update_func = folder_monitor_update;
290 static GtkTreeViewColumn*
291 get_new_column (const gchar *name, GtkCellRenderer *renderer,
292 gboolean resizable, gint sort_col_id, gboolean show_as_text,
293 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
295 GtkTreeViewColumn *column;
297 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
298 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
300 gtk_tree_view_column_set_resizable (column, resizable);
302 gtk_tree_view_column_set_expand (column, TRUE);
305 gtk_tree_view_column_add_attribute (column, renderer, "text",
307 if (sort_col_id >= 0)
308 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
310 gtk_tree_view_column_set_sort_indicator (column, FALSE);
311 gtk_tree_view_column_set_reorderable (column, TRUE);
314 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
321 remove_all_columns (ModestHeaderView *obj)
323 GList *columns, *cursor;
325 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
327 for (cursor = columns; cursor; cursor = cursor->next)
328 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
329 GTK_TREE_VIEW_COLUMN(cursor->data));
330 g_list_free (columns);
334 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
336 GtkTreeModel *tree_filter, *sortable;
337 GtkTreeViewColumn *column=NULL;
338 GtkTreeSelection *selection = NULL;
339 GtkCellRenderer *renderer_msgtype,*renderer_header,
340 *renderer_attach, *renderer_compact_date_or_status;
341 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
342 *renderer_subject, *renderer_subject_box, *renderer_recpt,
344 ModestHeaderViewPrivate *priv;
345 GtkTreeViewColumn *compact_column = NULL;
348 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
349 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
351 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
353 /* FIXME: check whether these renderers need to be freed */
354 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
355 renderer_attach = gtk_cell_renderer_pixbuf_new ();
356 renderer_priority = gtk_cell_renderer_pixbuf_new ();
357 renderer_header = gtk_cell_renderer_text_new ();
359 renderer_compact_header = modest_vbox_cell_renderer_new ();
360 renderer_recpt_box = modest_hbox_cell_renderer_new ();
361 renderer_subject_box = modest_hbox_cell_renderer_new ();
362 renderer_recpt = gtk_cell_renderer_text_new ();
363 renderer_subject = gtk_cell_renderer_text_new ();
364 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
366 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
367 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
368 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
369 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
370 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
371 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
372 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
373 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
374 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
375 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
376 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
377 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
378 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
379 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
381 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
382 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
383 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
384 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
385 g_object_set(G_OBJECT(renderer_header),
386 "ellipsize", PANGO_ELLIPSIZE_END,
388 g_object_set (G_OBJECT (renderer_subject),
389 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
391 g_object_set (G_OBJECT (renderer_recpt),
392 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
394 g_object_set(G_OBJECT(renderer_compact_date_or_status),
395 "xalign", 1.0, "yalign", 0.0,
397 g_object_set (G_OBJECT (renderer_priority),
398 "yalign", 1.0, NULL);
399 g_object_set (G_OBJECT (renderer_attach),
400 "yalign", 0.0, NULL);
402 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
403 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
404 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
406 remove_all_columns (self);
408 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
409 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
410 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
411 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
413 /* Add new columns */
414 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
415 ModestHeaderViewColumn col =
416 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
418 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
419 g_printerr ("modest: invalid column %d in column list\n", col);
425 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
426 column = get_new_column (_("M"), renderer_msgtype, FALSE,
427 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
429 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
431 gtk_tree_view_column_set_fixed_width (column, 45);
434 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
435 column = get_new_column (_("A"), renderer_attach, FALSE,
436 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
438 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
440 gtk_tree_view_column_set_fixed_width (column, 45);
444 case MODEST_HEADER_VIEW_COLUMN_FROM:
445 column = get_new_column (_("From"), renderer_header, TRUE,
446 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
448 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
449 GINT_TO_POINTER(TRUE));
452 case MODEST_HEADER_VIEW_COLUMN_TO:
453 column = get_new_column (_("To"), renderer_header, TRUE,
454 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
456 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
457 GINT_TO_POINTER(FALSE));
460 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
461 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
462 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
464 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
465 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
466 compact_column = column;
469 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
470 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
471 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
473 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
474 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
475 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
476 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
477 compact_column = column;
481 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
482 column = get_new_column (_("Subject"), renderer_header, TRUE,
483 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
485 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
489 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
490 column = get_new_column (_("Received"), renderer_header, TRUE,
491 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
493 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
494 GINT_TO_POINTER(TRUE));
497 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
498 column = get_new_column (_("Sent"), renderer_header, TRUE,
499 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
501 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
502 GINT_TO_POINTER(FALSE));
505 case MODEST_HEADER_VIEW_COLUMN_SIZE:
506 column = get_new_column (_("Size"), renderer_header, TRUE,
507 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
509 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
512 case MODEST_HEADER_VIEW_COLUMN_STATUS:
513 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
514 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
516 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
521 g_return_val_if_reached(FALSE);
524 /* we keep the column id around */
525 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
526 GINT_TO_POINTER(col));
528 /* we need this ptr when sorting the rows */
529 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
531 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
535 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
536 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
537 (GtkTreeIterCompareFunc) cmp_rows,
538 compact_column, NULL);
539 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
540 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
541 (GtkTreeIterCompareFunc) cmp_subject_rows,
542 compact_column, NULL);
550 modest_header_view_init (ModestHeaderView *obj)
552 ModestHeaderViewPrivate *priv;
555 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
559 priv->monitor = NULL;
560 priv->observers_lock = g_mutex_new ();
562 priv->status = HEADER_VIEW_INIT;
563 priv->status_timeout = 0;
565 priv->observer_list_lock = g_mutex_new();
566 priv->observer_list = NULL;
568 priv->clipboard = modest_runtime_get_email_clipboard ();
569 priv->hidding_ids = NULL;
570 priv->n_selected = 0;
571 priv->selection_changed_handler = 0;
572 priv->acc_removed_handler = 0;
574 /* Sort parameters */
575 for (j=0; j < 2; j++) {
576 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
577 priv->sort_colid[j][i] = -1;
578 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
582 setup_drag_and_drop (GTK_WIDGET(obj));
586 modest_header_view_dispose (GObject *obj)
588 ModestHeaderView *self;
589 ModestHeaderViewPrivate *priv;
590 GtkTreeSelection *sel;
592 self = MODEST_HEADER_VIEW(obj);
593 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
595 /* Free in the dispose to avoid unref cycles */
597 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
598 g_object_unref (G_OBJECT (priv->folder));
602 /* We need to do this here in the dispose because the
603 selection won't exist when finalizing */
604 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
605 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
606 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
607 priv->selection_changed_handler = 0;
610 G_OBJECT_CLASS(parent_class)->dispose (obj);
614 modest_header_view_finalize (GObject *obj)
616 ModestHeaderView *self;
617 ModestHeaderViewPrivate *priv;
619 self = MODEST_HEADER_VIEW(obj);
620 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
622 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
623 priv->acc_removed_handler)) {
624 g_signal_handler_disconnect (modest_runtime_get_account_store (),
625 priv->acc_removed_handler);
628 /* There is no need to lock because there should not be any
629 * reference to self now. */
630 g_mutex_free(priv->observer_list_lock);
631 g_slist_free(priv->observer_list);
633 g_mutex_lock (priv->observers_lock);
635 tny_folder_monitor_stop (priv->monitor);
636 g_object_unref (G_OBJECT (priv->monitor));
638 g_mutex_unlock (priv->observers_lock);
639 g_mutex_free (priv->observers_lock);
641 /* Clear hidding array created by cut operation */
642 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
644 G_OBJECT_CLASS(parent_class)->finalize (obj);
649 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
652 GtkTreeSelection *sel;
653 ModestHeaderView *self;
654 ModestHeaderViewPrivate *priv;
656 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
659 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
660 self = MODEST_HEADER_VIEW(obj);
661 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
663 modest_header_view_set_style (self, style);
665 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
666 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
667 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
669 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
670 TRUE); /* alternating row colors */
672 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
673 priv->selection_changed_handler =
674 g_signal_connect_after (sel, "changed",
675 G_CALLBACK(on_selection_changed), self);
677 g_signal_connect (self, "row-activated",
678 G_CALLBACK (on_header_row_activated), NULL);
680 g_signal_connect (self, "focus-in-event",
681 G_CALLBACK(on_focus_in), NULL);
682 g_signal_connect (self, "focus-out-event",
683 G_CALLBACK(on_focus_out), NULL);
685 g_signal_connect (self, "button-press-event",
686 G_CALLBACK(on_button_press_event), NULL);
687 g_signal_connect (self, "button-release-event",
688 G_CALLBACK(on_button_release_event), NULL);
690 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
692 G_CALLBACK (on_account_removed),
695 g_signal_connect (self, "expose-event",
696 G_CALLBACK(modest_header_view_on_expose_event),
699 return GTK_WIDGET(self);
704 modest_header_view_count_selected_headers (ModestHeaderView *self)
706 GtkTreeSelection *sel;
709 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
711 /* Get selection object and check selected rows count */
712 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
713 selected_rows = gtk_tree_selection_count_selected_rows (sel);
715 return selected_rows;
719 modest_header_view_has_selected_headers (ModestHeaderView *self)
721 GtkTreeSelection *sel;
724 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
726 /* Get selection object and check selected rows count */
727 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
728 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
735 modest_header_view_get_selected_headers (ModestHeaderView *self)
737 GtkTreeSelection *sel;
738 ModestHeaderViewPrivate *priv;
739 TnyList *header_list = NULL;
741 GList *list, *tmp = NULL;
742 GtkTreeModel *tree_model = NULL;
745 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
747 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
749 /* Get selected rows */
750 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
751 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
754 header_list = tny_simple_list_new();
756 list = g_list_reverse (list);
759 /* get header from selection */
760 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
761 gtk_tree_model_get (tree_model, &iter,
762 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
764 /* Prepend to list */
765 tny_list_prepend (header_list, G_OBJECT (header));
766 g_object_unref (G_OBJECT (header));
768 tmp = g_list_next (tmp);
771 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
778 /* scroll our list view so the selected item is visible */
780 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
782 #ifdef MODEST_PLATFORM_GNOME
784 GtkTreePath *selected_path;
785 GtkTreePath *start, *end;
789 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
790 selected_path = gtk_tree_model_get_path (model, iter);
792 start = gtk_tree_path_new ();
793 end = gtk_tree_path_new ();
795 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
797 if (gtk_tree_path_compare (selected_path, start) < 0 ||
798 gtk_tree_path_compare (end, selected_path) < 0)
799 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
800 selected_path, NULL, TRUE,
803 gtk_tree_path_free (selected_path);
804 gtk_tree_path_free (start);
805 gtk_tree_path_free (end);
807 #endif /* MODEST_PLATFORM_GNOME */
812 modest_header_view_select_next (ModestHeaderView *self)
814 GtkTreeSelection *sel;
819 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
821 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
822 path = get_selected_row (GTK_TREE_VIEW(self), &model);
823 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
824 /* Unselect previous path */
825 gtk_tree_selection_unselect_path (sel, path);
827 /* Move path down and selects new one */
828 if (gtk_tree_model_iter_next (model, &iter)) {
829 gtk_tree_selection_select_iter (sel, &iter);
830 scroll_to_selected (self, &iter, FALSE);
832 gtk_tree_path_free(path);
838 modest_header_view_select_prev (ModestHeaderView *self)
840 GtkTreeSelection *sel;
845 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
847 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
848 path = get_selected_row (GTK_TREE_VIEW(self), &model);
849 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
850 /* Unselect previous path */
851 gtk_tree_selection_unselect_path (sel, path);
854 if (gtk_tree_path_prev (path)) {
855 gtk_tree_model_get_iter (model, &iter, path);
857 /* Select the new one */
858 gtk_tree_selection_select_iter (sel, &iter);
859 scroll_to_selected (self, &iter, TRUE);
862 gtk_tree_path_free (path);
867 modest_header_view_get_columns (ModestHeaderView *self)
869 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
871 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
877 modest_header_view_set_style (ModestHeaderView *self,
878 ModestHeaderViewStyle style)
880 ModestHeaderViewPrivate *priv;
881 gboolean show_col_headers = FALSE;
882 ModestHeaderViewStyle old_style;
884 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
885 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
888 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
889 if (priv->style == style)
890 return TRUE; /* nothing to do */
893 case MODEST_HEADER_VIEW_STYLE_DETAILS:
894 show_col_headers = TRUE;
896 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
899 g_return_val_if_reached (FALSE);
901 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
902 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
904 old_style = priv->style;
911 ModestHeaderViewStyle
912 modest_header_view_get_style (ModestHeaderView *self)
914 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
916 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
919 /* This is used to automatically select the first header if the user
920 * has not selected any header yet.
923 modest_header_view_on_expose_event(GtkTreeView *header_view,
924 GdkEventExpose *event,
927 GtkTreeSelection *sel;
929 GtkTreeIter tree_iter;
931 model = gtk_tree_view_get_model(header_view);
936 sel = gtk_tree_view_get_selection(header_view);
937 if(!gtk_tree_selection_count_selected_rows(sel))
938 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
939 /* Prevent the widget from getting the focus
940 when selecting the first item */
941 g_object_set(header_view, "can-focus", FALSE, NULL);
942 gtk_tree_selection_select_iter(sel, &tree_iter);
943 g_object_set(header_view, "can-focus", TRUE, NULL);
950 * This function sets a sortable model in the header view. It's just
951 * used for developing purposes, because it only does a
952 * gtk_tree_view_set_model
955 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
957 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
958 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
959 /* GtkTreeModel *old_model; */
960 /* ModestHeaderViewPrivate *priv; */
961 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
962 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
964 /* /\* Set new model *\/ */
965 /* gtk_tree_view_set_model (header_view, model); */
967 gtk_tree_view_set_model (header_view, model);
971 modest_header_view_get_folder (ModestHeaderView *self)
973 ModestHeaderViewPrivate *priv;
975 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
977 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
980 g_object_ref (priv->folder);
986 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
990 ModestHeaderViewPrivate *priv;
991 GList *cols, *cursor;
992 GtkTreeModel *filter_model, *sortable;
994 GtkSortType sort_type;
996 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
998 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1000 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1001 folder, FALSE, NULL, NULL, NULL);
1003 /* Add IDLE observer (monitor) and another folder observer for
1004 new messages (self) */
1005 g_mutex_lock (priv->observers_lock);
1006 if (priv->monitor) {
1007 tny_folder_monitor_stop (priv->monitor);
1008 g_object_unref (G_OBJECT (priv->monitor));
1010 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1011 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1012 tny_folder_monitor_start (priv->monitor);
1013 g_mutex_unlock (priv->observers_lock);
1015 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
1016 g_object_unref (G_OBJECT (headers));
1018 /* Init filter_row function to examine empty status */
1019 priv->status = HEADER_VIEW_INIT;
1021 /* Create a tree model filter to hide and show rows for cut operations */
1022 filter_model = gtk_tree_model_filter_new (sortable, NULL);
1023 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1027 g_object_unref (G_OBJECT (sortable));
1029 /* install our special sorting functions */
1030 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1032 /* Restore sort column id */
1034 type = modest_tny_folder_guess_folder_type (folder);
1035 if (type == TNY_FOLDER_TYPE_INVALID)
1036 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1038 sort_colid = modest_header_view_get_sort_column_id (self, type);
1039 sort_type = modest_header_view_get_sort_type (self, type);
1040 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1043 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1044 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1045 (GtkTreeIterCompareFunc) cmp_rows,
1047 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1048 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1049 (GtkTreeIterCompareFunc) cmp_subject_rows,
1054 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1055 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1056 tny_folder_get_id(folder));
1057 g_object_unref (G_OBJECT (filter_model));
1058 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1059 /* g_object_unref (G_OBJECT (sortable)); */
1066 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1068 GtkSortType sort_type)
1070 ModestHeaderViewPrivate *priv = NULL;
1071 GtkTreeModel *tree_filter, *sortable = NULL;
1074 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1075 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1077 /* Get model and private data */
1078 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1079 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1080 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1081 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1083 /* Sort tree model */
1084 type = modest_tny_folder_guess_folder_type (priv->folder);
1085 if (type == TNY_FOLDER_TYPE_INVALID)
1086 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1088 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1091 /* Store new sort parameters */
1092 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1097 modest_header_view_set_sort_params (ModestHeaderView *self,
1099 GtkSortType sort_type,
1102 ModestHeaderViewPrivate *priv;
1103 ModestHeaderViewStyle style;
1105 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1106 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1107 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1109 style = modest_header_view_get_style (self);
1110 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1112 priv->sort_colid[style][type] = sort_colid;
1113 priv->sort_type[style][type] = sort_type;
1117 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1120 ModestHeaderViewPrivate *priv;
1121 ModestHeaderViewStyle style;
1123 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1124 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1126 style = modest_header_view_get_style (self);
1127 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1129 return priv->sort_colid[style][type];
1133 modest_header_view_get_sort_type (ModestHeaderView *self,
1136 ModestHeaderViewPrivate *priv;
1137 ModestHeaderViewStyle style;
1139 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1140 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1142 style = modest_header_view_get_style (self);
1143 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1145 return priv->sort_type[style][type];
1149 ModestHeaderView *header_view;
1150 RefreshAsyncUserCallback cb;
1155 folder_refreshed_cb (ModestMailOperation *mail_op,
1159 ModestHeaderViewPrivate *priv;
1160 SetFolderHelper *info;
1162 info = (SetFolderHelper*) user_data;
1164 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1168 info->cb (mail_op, folder, info->user_data);
1170 /* Start the folder count changes observer. We do not need it
1171 before the refresh. Note that the monitor could still be
1172 called for this refresh but now we know that the callback
1173 was previously called */
1174 g_mutex_lock (priv->observers_lock);
1175 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1176 g_mutex_unlock (priv->observers_lock);
1178 /* Notify the observers that the update is over */
1179 g_signal_emit (G_OBJECT (info->header_view),
1180 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1183 g_object_unref (info->header_view);
1188 modest_header_view_set_folder (ModestHeaderView *self,
1190 RefreshAsyncUserCallback callback,
1193 ModestHeaderViewPrivate *priv;
1194 SetFolderHelper *info;
1195 ModestWindow *main_win;
1197 g_return_if_fail (self);
1199 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1201 main_win = modest_window_mgr_get_main_window (modest_runtime_get_window_mgr (),
1202 FALSE); /* don't create */
1204 g_warning ("%s: BUG: no main window", __FUNCTION__);
1209 if (priv->status_timeout)
1210 g_source_remove (priv->status_timeout);
1212 g_mutex_lock (priv->observers_lock);
1213 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1214 g_object_unref (priv->folder);
1215 priv->folder = NULL;
1216 g_mutex_unlock (priv->observers_lock);
1220 ModestMailOperation *mail_op = NULL;
1221 GtkTreeSelection *selection;
1223 /* Set folder in the model */
1224 modest_header_view_set_folder_intern (self, folder);
1226 /* Pick my reference. Nothing to do with the mail operation */
1227 priv->folder = g_object_ref (folder);
1229 /* Clear the selection if exists */
1230 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1231 gtk_tree_selection_unselect_all(selection);
1232 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1234 /* Notify the observers that the update begins */
1235 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1238 /* create the helper */
1239 info = g_malloc0 (sizeof(SetFolderHelper));
1240 info->header_view = g_object_ref (self);
1241 info->cb = callback;
1242 info->user_data = user_data;
1244 /* Create the mail operation (source will be the parent widget) */
1245 mail_op = modest_mail_operation_new (G_OBJECT(main_win));
1246 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1249 /* Refresh the folder asynchronously */
1250 modest_mail_operation_refresh_folder (mail_op,
1252 folder_refreshed_cb,
1256 g_object_unref (mail_op);
1258 g_mutex_lock (priv->observers_lock);
1260 if (priv->monitor) {
1261 tny_folder_monitor_stop (priv->monitor);
1262 g_object_unref (G_OBJECT (priv->monitor));
1263 priv->monitor = NULL;
1265 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1267 modest_header_view_notify_observers(self, NULL, NULL);
1269 g_mutex_unlock (priv->observers_lock);
1271 /* Notify the observers that the update is over */
1272 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1278 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1279 GtkTreeViewColumn *column, gpointer userdata)
1281 ModestHeaderView *self = NULL;
1282 ModestHeaderViewPrivate *priv = NULL;
1284 GtkTreeModel *model = NULL;
1285 TnyHeader *header = NULL;
1286 TnyHeaderFlags flags;
1288 self = MODEST_HEADER_VIEW (treeview);
1289 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1291 model = gtk_tree_view_get_model (treeview);
1292 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1295 /* get the first selected item */
1296 gtk_tree_model_get (model, &iter,
1297 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1298 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1301 /* Dont open DELETED messages */
1302 if (flags & TNY_HEADER_FLAG_DELETED) {
1303 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1308 g_signal_emit (G_OBJECT(self),
1309 signals[HEADER_ACTIVATED_SIGNAL],
1315 g_object_unref (G_OBJECT (header));
1320 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1322 GtkTreeModel *model;
1323 TnyHeader *header = NULL;
1324 GtkTreePath *path = NULL;
1326 ModestHeaderView *self;
1327 ModestHeaderViewPrivate *priv;
1328 GList *selected = NULL;
1330 g_return_if_fail (sel);
1331 g_return_if_fail (user_data);
1333 self = MODEST_HEADER_VIEW (user_data);
1334 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1336 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1337 if (selected != NULL)
1338 path = (GtkTreePath *) selected->data;
1339 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1340 return; /* msg was _un_selected */
1342 gtk_tree_model_get (model, &iter,
1343 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1347 g_signal_emit (G_OBJECT(self),
1348 signals[HEADER_SELECTED_SIGNAL],
1351 g_object_unref (G_OBJECT (header));
1353 /* free all items in 'selected' */
1354 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1355 g_list_free (selected);
1359 /* PROTECTED method. It's useful when we want to force a given
1360 selection to reload a msg. For example if we have selected a header
1361 in offline mode, when Modest become online, we want to reload the
1362 message automatically without an user click over the header */
1364 _modest_header_view_change_selection (GtkTreeSelection *selection,
1367 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1368 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1370 on_selection_changed (selection, user_data);
1374 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1381 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1385 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1389 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1397 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1404 /* static int counter = 0; */
1406 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1407 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1408 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1412 case TNY_HEADER_FLAG_ATTACHMENTS:
1414 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1415 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1416 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1417 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1419 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1420 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1422 return cmp ? cmp : t1 - t2;
1424 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1425 TnyHeader *header1 = NULL, *header2 = NULL;
1427 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1428 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1429 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1430 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1432 /* This is for making priority values respect the intuitive sort relationship
1433 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1435 if (header1 && header2) {
1436 cmp = compare_priorities (tny_header_get_priority (header1),
1437 tny_header_get_priority (header2));
1438 g_object_unref (header1);
1439 g_object_unref (header2);
1441 return cmp ? cmp : t1 - t2;
1447 return &iter1 - &iter2; /* oughhhh */
1452 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1458 /* static int counter = 0; */
1460 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1462 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1463 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1464 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1465 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1467 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1468 val2 + modest_text_utils_get_subject_prefix_len(val2),
1475 /* Drag and drop stuff */
1477 drag_data_get_cb (GtkWidget *widget,
1478 GdkDragContext *context,
1479 GtkSelectionData *selection_data,
1484 ModestHeaderView *self = NULL;
1485 ModestHeaderViewPrivate *priv = NULL;
1487 self = MODEST_HEADER_VIEW (widget);
1488 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1490 /* Set the data. Do not use the current selection because it
1491 could be different than the selection at the beginning of
1493 modest_dnd_selection_data_set_paths (selection_data,
1494 priv->drag_begin_cached_selected_rows);
1498 * We're caching the selected rows at the beginning because the
1499 * selection could change between drag-begin and drag-data-get, for
1500 * example if we have a set of rows already selected, and then we
1501 * click in one of them (without SHIFT key pressed) and begin a drag,
1502 * the selection at that moment contains all the selected lines, but
1503 * after dropping the selection, the release event provokes that only
1504 * the row used to begin the drag is selected, so at the end the
1505 * drag&drop affects only one rows instead of all the selected ones.
1509 drag_begin_cb (GtkWidget *widget,
1510 GdkDragContext *context,
1513 ModestHeaderView *self = NULL;
1514 ModestHeaderViewPrivate *priv = NULL;
1515 GtkTreeSelection *selection;
1517 self = MODEST_HEADER_VIEW (widget);
1518 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1520 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1521 priv->drag_begin_cached_selected_rows =
1522 gtk_tree_selection_get_selected_rows (selection, NULL);
1526 * We use the drag-end signal to clear the cached selection, we use
1527 * this because this allways happens, whether or not the d&d was a
1531 drag_end_cb (GtkWidget *widget,
1535 ModestHeaderView *self = NULL;
1536 ModestHeaderViewPrivate *priv = NULL;
1538 self = MODEST_HEADER_VIEW (widget);
1539 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1541 /* Free cached data */
1542 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1543 g_list_free (priv->drag_begin_cached_selected_rows);
1544 priv->drag_begin_cached_selected_rows = NULL;
1547 /* Header view drag types */
1548 const GtkTargetEntry header_view_drag_types[] = {
1549 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1553 enable_drag_and_drop (GtkWidget *self)
1555 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1556 header_view_drag_types,
1557 G_N_ELEMENTS (header_view_drag_types),
1558 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1562 disable_drag_and_drop (GtkWidget *self)
1564 gtk_drag_source_unset (self);
1568 setup_drag_and_drop (GtkWidget *self)
1570 enable_drag_and_drop(self);
1571 g_signal_connect(G_OBJECT (self), "drag_data_get",
1572 G_CALLBACK(drag_data_get_cb), NULL);
1574 g_signal_connect(G_OBJECT (self), "drag_begin",
1575 G_CALLBACK(drag_begin_cb), NULL);
1577 g_signal_connect(G_OBJECT (self), "drag_end",
1578 G_CALLBACK(drag_end_cb), NULL);
1581 static GtkTreePath *
1582 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1584 GtkTreePath *path = NULL;
1585 GtkTreeSelection *sel = NULL;
1588 sel = gtk_tree_view_get_selection(self);
1589 rows = gtk_tree_selection_get_selected_rows (sel, model);
1591 if ((rows == NULL) || (g_list_length(rows) != 1))
1594 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1599 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1606 * This function moves the tree view scroll to the current selected
1607 * row when the widget grabs the focus
1610 on_focus_in (GtkWidget *self,
1611 GdkEventFocus *event,
1614 GtkTreeSelection *selection;
1615 GtkTreeModel *model;
1616 GList *selected = NULL;
1617 GtkTreePath *selected_path = NULL;
1619 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1623 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1624 /* If none selected yet, pick the first one */
1625 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1629 /* Return if the model is empty */
1630 if (!gtk_tree_model_get_iter_first (model, &iter))
1633 path = gtk_tree_model_get_path (model, &iter);
1634 gtk_tree_selection_select_path (selection, path);
1635 gtk_tree_path_free (path);
1638 /* Need to get the all the rows because is selection multiple */
1639 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1640 if (selected == NULL) return FALSE;
1641 selected_path = (GtkTreePath *) selected->data;
1643 /* Check if we need to scroll */
1644 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1645 GtkTreePath *start_path = NULL;
1646 GtkTreePath *end_path = NULL;
1647 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1651 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1652 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1654 /* Scroll to first path */
1655 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1664 gtk_tree_path_free (start_path);
1666 gtk_tree_path_free (end_path);
1668 #endif /* GTK_CHECK_VERSION */
1671 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1672 g_list_free (selected);
1678 on_focus_out (GtkWidget *self,
1679 GdkEventFocus *event,
1683 if (!gtk_widget_is_focus (self)) {
1684 GtkTreeSelection *selection = NULL;
1685 GList *selected_rows = NULL;
1686 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1687 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1688 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1689 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1690 gtk_tree_selection_unselect_all (selection);
1691 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1692 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1693 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1694 g_list_free (selected_rows);
1701 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1703 enable_drag_and_drop(self);
1708 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1710 GtkTreeSelection *selection = NULL;
1711 GtkTreePath *path = NULL;
1712 gboolean already_selected = FALSE;
1714 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1715 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1716 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1719 /* Enable drag and drop onlly if the user clicks on a row that
1720 it's already selected. If not, let him select items using
1722 if (!already_selected) {
1723 disable_drag_and_drop(self);
1727 gtk_tree_path_free(path);
1734 folder_monitor_update (TnyFolderObserver *self,
1735 TnyFolderChange *change)
1737 ModestHeaderViewPrivate *priv = NULL;
1738 TnyFolderChangeChanged changed;
1739 TnyFolder *folder = NULL;
1741 changed = tny_folder_change_get_changed (change);
1743 /* Do not notify the observers if the folder of the header
1744 view has changed before this call to the observer
1746 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1747 folder = tny_folder_change_get_folder (change);
1748 if (folder != priv->folder)
1751 MODEST_DEBUG_BLOCK (
1752 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1753 g_print ("ADDED %d/%d (r/t) \n",
1754 tny_folder_change_get_new_unread_count (change),
1755 tny_folder_change_get_new_all_count (change));
1756 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1757 g_print ("ALL COUNT %d\n",
1758 tny_folder_change_get_new_all_count (change));
1759 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1760 g_print ("UNREAD COUNT %d\n",
1761 tny_folder_change_get_new_unread_count (change));
1762 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1763 g_print ("EXPUNGED %d/%d (r/t) \n",
1764 tny_folder_change_get_new_unread_count (change),
1765 tny_folder_change_get_new_all_count (change));
1766 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1767 g_print ("FOLDER RENAME\n");
1768 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1769 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1770 tny_folder_change_get_new_unread_count (change),
1771 tny_folder_change_get_new_all_count (change));
1772 g_print ("---------------------------------------------------\n");
1775 /* Check folder count */
1776 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1777 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1779 g_mutex_lock (priv->observers_lock);
1781 /* Emit signal to evaluate how headers changes affects
1782 to the window view */
1783 g_signal_emit (G_OBJECT(self),
1784 signals[MSG_COUNT_CHANGED_SIGNAL],
1787 /* Added or removed headers, so data stored on cliboard are invalid */
1788 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1789 modest_email_clipboard_clear (priv->clipboard);
1791 g_mutex_unlock (priv->observers_lock);
1797 g_object_unref (folder);
1801 modest_header_view_is_empty (ModestHeaderView *self)
1803 ModestHeaderViewPrivate *priv;
1805 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1807 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1809 return priv->status == HEADER_VIEW_EMPTY;
1813 modest_header_view_clear (ModestHeaderView *self)
1815 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1817 modest_header_view_set_folder (self, NULL, NULL, NULL);
1821 modest_header_view_copy_selection (ModestHeaderView *header_view)
1823 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1825 /* Copy selection */
1826 _clipboard_set_selected_data (header_view, FALSE);
1830 modest_header_view_cut_selection (ModestHeaderView *header_view)
1832 ModestHeaderViewPrivate *priv = NULL;
1833 const gchar **hidding = NULL;
1834 guint i, n_selected;
1836 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1838 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1840 /* Copy selection */
1841 _clipboard_set_selected_data (header_view, TRUE);
1843 /* Get hidding ids */
1844 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1846 /* Clear hidding array created by previous cut operation */
1847 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1849 /* Copy hidding array */
1850 priv->n_selected = n_selected;
1851 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1852 for (i=0; i < n_selected; i++)
1853 priv->hidding_ids[i] = g_strdup(hidding[i]);
1855 /* Hide cut headers */
1856 modest_header_view_refilter (header_view);
1863 _clipboard_set_selected_data (ModestHeaderView *header_view,
1866 ModestHeaderViewPrivate *priv = NULL;
1867 TnyList *headers = NULL;
1869 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1870 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1872 /* Set selected data on clipboard */
1873 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1874 headers = modest_header_view_get_selected_headers (header_view);
1875 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1878 g_object_unref (headers);
1882 ModestHeaderView *self;
1887 notify_filter_change (gpointer data)
1889 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
1891 g_signal_emit (info->self,
1892 signals[MSG_COUNT_CHANGED_SIGNAL],
1893 0, info->folder, NULL);
1899 notify_filter_change_destroy (gpointer data)
1901 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
1903 g_object_unref (info->self);
1904 g_object_unref (info->folder);
1905 g_slice_free (NotifyFilterInfo, info);
1909 filter_row (GtkTreeModel *model,
1913 ModestHeaderViewPrivate *priv = NULL;
1914 TnyHeaderFlags flags;
1915 TnyHeader *header = NULL;
1918 gboolean visible = TRUE;
1919 gboolean found = FALSE;
1920 GValue value = {0,};
1921 HeaderViewStatus old_status;
1923 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1924 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1926 /* Get header from model */
1927 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
1928 flags = (TnyHeaderFlags) g_value_get_int (&value);
1929 g_value_unset (&value);
1930 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
1931 header = (TnyHeader *) g_value_get_object (&value);
1932 g_value_unset (&value);
1934 /* Hide deleted and mark as deleted heders */
1935 if (flags & TNY_HEADER_FLAG_DELETED ||
1936 flags & TNY_HEADER_FLAG_EXPUNGED) {
1941 /* If no data on clipboard, return always TRUE */
1942 if (modest_email_clipboard_cleared(priv->clipboard)) {
1947 /* Get message id from header (ensure is a valid id) */
1954 if (priv->hidding_ids != NULL) {
1955 id = g_strdup(tny_header_get_message_id (header));
1956 for (i=0; i < priv->n_selected && !found; i++)
1957 if (priv->hidding_ids[i] != NULL && id != NULL)
1958 found = (!strcmp (priv->hidding_ids[i], id));
1965 old_status = priv->status;
1966 priv->status = ((gboolean) priv->status) && !visible;
1967 if (priv->status != old_status) {
1968 NotifyFilterInfo *info;
1970 if (priv->status_timeout)
1971 g_source_remove (priv->status_timeout);
1973 info = g_slice_new0 (NotifyFilterInfo);
1974 info->self = g_object_ref (G_OBJECT (user_data));
1975 info->folder = tny_header_get_folder (header);
1976 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
1977 notify_filter_change,
1979 notify_filter_change_destroy);
1986 _clear_hidding_filter (ModestHeaderView *header_view)
1988 ModestHeaderViewPrivate *priv = NULL;
1991 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1992 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1994 if (priv->hidding_ids != NULL) {
1995 for (i=0; i < priv->n_selected; i++)
1996 g_free (priv->hidding_ids[i]);
1997 g_free(priv->hidding_ids);
2002 modest_header_view_refilter (ModestHeaderView *header_view)
2004 GtkTreeModel *model = NULL;
2005 ModestHeaderViewPrivate *priv = NULL;
2007 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2008 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2010 /* Hide cut headers */
2011 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2012 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2013 priv->status = HEADER_VIEW_INIT;
2014 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2019 * Called when an account is removed. If I'm showing a folder of the
2020 * account that has been removed then clear the view
2023 on_account_removed (TnyAccountStore *self,
2024 TnyAccount *account,
2027 ModestHeaderViewPrivate *priv = NULL;
2029 /* Ignore changes in transport accounts */
2030 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2033 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2036 TnyAccount *my_account;
2038 my_account = tny_folder_get_account (priv->folder);
2039 if (my_account == account)
2040 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2041 g_object_unref (account);
2046 modest_header_view_add_observer(ModestHeaderView *header_view,
2047 ModestHeaderViewObserver *observer)
2049 ModestHeaderViewPrivate *priv;
2051 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2052 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2054 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2056 g_mutex_lock(priv->observer_list_lock);
2057 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2058 g_mutex_unlock(priv->observer_list_lock);
2062 modest_header_view_remove_observer(ModestHeaderView *header_view,
2063 ModestHeaderViewObserver *observer)
2065 ModestHeaderViewPrivate *priv;
2067 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2068 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2070 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2072 g_mutex_lock(priv->observer_list_lock);
2073 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2074 g_mutex_unlock(priv->observer_list_lock);
2078 modest_header_view_notify_observers(ModestHeaderView *header_view,
2079 GtkTreeModel *model,
2080 const gchar *tny_folder_id)
2082 ModestHeaderViewPrivate *priv = NULL;
2084 ModestHeaderViewObserver *observer;
2087 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2089 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2091 g_mutex_lock(priv->observer_list_lock);
2092 iter = priv->observer_list;
2093 while(iter != NULL){
2094 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2095 modest_header_view_observer_update(observer, model,
2097 iter = g_slist_next(iter);
2099 g_mutex_unlock(priv->observer_list_lock);