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>
43 #include <modest-ui-actions.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 = 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) {
1305 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1306 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1307 modest_platform_information_banner (NULL, NULL, msg);
1313 g_signal_emit (G_OBJECT(self),
1314 signals[HEADER_ACTIVATED_SIGNAL],
1320 g_object_unref (G_OBJECT (header));
1325 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1327 GtkTreeModel *model;
1328 TnyHeader *header = NULL;
1329 GtkTreePath *path = NULL;
1331 ModestHeaderView *self;
1332 ModestHeaderViewPrivate *priv;
1333 GList *selected = NULL;
1335 g_return_if_fail (sel);
1336 g_return_if_fail (user_data);
1338 self = MODEST_HEADER_VIEW (user_data);
1339 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1341 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1342 if (selected != NULL)
1343 path = (GtkTreePath *) selected->data;
1344 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1345 return; /* msg was _un_selected */
1347 gtk_tree_model_get (model, &iter,
1348 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1352 g_signal_emit (G_OBJECT(self),
1353 signals[HEADER_SELECTED_SIGNAL],
1356 g_object_unref (G_OBJECT (header));
1358 /* free all items in 'selected' */
1359 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1360 g_list_free (selected);
1364 /* PROTECTED method. It's useful when we want to force a given
1365 selection to reload a msg. For example if we have selected a header
1366 in offline mode, when Modest become online, we want to reload the
1367 message automatically without an user click over the header */
1369 _modest_header_view_change_selection (GtkTreeSelection *selection,
1372 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1373 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1375 on_selection_changed (selection, user_data);
1379 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1386 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1390 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1394 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1402 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1409 /* static int counter = 0; */
1411 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1412 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1413 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1417 case TNY_HEADER_FLAG_ATTACHMENTS:
1419 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1420 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1421 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1422 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1424 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1425 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1427 return cmp ? cmp : t1 - t2;
1429 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1430 TnyHeader *header1 = NULL, *header2 = NULL;
1432 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1433 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1434 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1435 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1437 /* This is for making priority values respect the intuitive sort relationship
1438 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1440 if (header1 && header2) {
1441 cmp = compare_priorities (tny_header_get_priority (header1),
1442 tny_header_get_priority (header2));
1443 g_object_unref (header1);
1444 g_object_unref (header2);
1446 return cmp ? cmp : t1 - t2;
1452 return &iter1 - &iter2; /* oughhhh */
1457 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1463 /* static int counter = 0; */
1465 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1467 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1468 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1469 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1470 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1472 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1473 val2 + modest_text_utils_get_subject_prefix_len(val2),
1480 /* Drag and drop stuff */
1482 drag_data_get_cb (GtkWidget *widget,
1483 GdkDragContext *context,
1484 GtkSelectionData *selection_data,
1489 ModestHeaderView *self = NULL;
1490 ModestHeaderViewPrivate *priv = NULL;
1492 self = MODEST_HEADER_VIEW (widget);
1493 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1495 /* Set the data. Do not use the current selection because it
1496 could be different than the selection at the beginning of
1498 modest_dnd_selection_data_set_paths (selection_data,
1499 priv->drag_begin_cached_selected_rows);
1503 * We're caching the selected rows at the beginning because the
1504 * selection could change between drag-begin and drag-data-get, for
1505 * example if we have a set of rows already selected, and then we
1506 * click in one of them (without SHIFT key pressed) and begin a drag,
1507 * the selection at that moment contains all the selected lines, but
1508 * after dropping the selection, the release event provokes that only
1509 * the row used to begin the drag is selected, so at the end the
1510 * drag&drop affects only one rows instead of all the selected ones.
1514 drag_begin_cb (GtkWidget *widget,
1515 GdkDragContext *context,
1518 ModestHeaderView *self = NULL;
1519 ModestHeaderViewPrivate *priv = NULL;
1520 GtkTreeSelection *selection;
1522 self = MODEST_HEADER_VIEW (widget);
1523 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1525 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1526 priv->drag_begin_cached_selected_rows =
1527 gtk_tree_selection_get_selected_rows (selection, NULL);
1531 * We use the drag-end signal to clear the cached selection, we use
1532 * this because this allways happens, whether or not the d&d was a
1536 drag_end_cb (GtkWidget *widget,
1540 ModestHeaderView *self = NULL;
1541 ModestHeaderViewPrivate *priv = NULL;
1543 self = MODEST_HEADER_VIEW (widget);
1544 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1546 /* Free cached data */
1547 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1548 g_list_free (priv->drag_begin_cached_selected_rows);
1549 priv->drag_begin_cached_selected_rows = NULL;
1552 /* Header view drag types */
1553 const GtkTargetEntry header_view_drag_types[] = {
1554 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1558 enable_drag_and_drop (GtkWidget *self)
1560 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1561 header_view_drag_types,
1562 G_N_ELEMENTS (header_view_drag_types),
1563 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1567 disable_drag_and_drop (GtkWidget *self)
1569 gtk_drag_source_unset (self);
1573 setup_drag_and_drop (GtkWidget *self)
1575 enable_drag_and_drop(self);
1576 g_signal_connect(G_OBJECT (self), "drag_data_get",
1577 G_CALLBACK(drag_data_get_cb), NULL);
1579 g_signal_connect(G_OBJECT (self), "drag_begin",
1580 G_CALLBACK(drag_begin_cb), NULL);
1582 g_signal_connect(G_OBJECT (self), "drag_end",
1583 G_CALLBACK(drag_end_cb), NULL);
1586 static GtkTreePath *
1587 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1589 GtkTreePath *path = NULL;
1590 GtkTreeSelection *sel = NULL;
1593 sel = gtk_tree_view_get_selection(self);
1594 rows = gtk_tree_selection_get_selected_rows (sel, model);
1596 if ((rows == NULL) || (g_list_length(rows) != 1))
1599 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1604 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1611 * This function moves the tree view scroll to the current selected
1612 * row when the widget grabs the focus
1615 on_focus_in (GtkWidget *self,
1616 GdkEventFocus *event,
1619 GtkTreeSelection *selection;
1620 GtkTreeModel *model;
1621 GList *selected = NULL;
1622 GtkTreePath *selected_path = NULL;
1624 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1628 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1629 /* If none selected yet, pick the first one */
1630 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1634 /* Return if the model is empty */
1635 if (!gtk_tree_model_get_iter_first (model, &iter))
1638 path = gtk_tree_model_get_path (model, &iter);
1639 gtk_tree_selection_select_path (selection, path);
1640 gtk_tree_path_free (path);
1643 /* Need to get the all the rows because is selection multiple */
1644 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1645 if (selected == NULL) return FALSE;
1646 selected_path = (GtkTreePath *) selected->data;
1648 /* Check if we need to scroll */
1649 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1650 GtkTreePath *start_path = NULL;
1651 GtkTreePath *end_path = NULL;
1652 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1656 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1657 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1659 /* Scroll to first path */
1660 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1669 gtk_tree_path_free (start_path);
1671 gtk_tree_path_free (end_path);
1673 #endif /* GTK_CHECK_VERSION */
1676 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1677 g_list_free (selected);
1683 on_focus_out (GtkWidget *self,
1684 GdkEventFocus *event,
1688 if (!gtk_widget_is_focus (self)) {
1689 GtkTreeSelection *selection = NULL;
1690 GList *selected_rows = NULL;
1691 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1692 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1693 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1694 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1695 gtk_tree_selection_unselect_all (selection);
1696 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1697 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1698 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1699 g_list_free (selected_rows);
1706 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1708 enable_drag_and_drop(self);
1713 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1715 GtkTreeSelection *selection = NULL;
1716 GtkTreePath *path = NULL;
1717 gboolean already_selected = FALSE;
1719 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1720 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1721 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1724 /* Enable drag and drop onlly if the user clicks on a row that
1725 it's already selected. If not, let him select items using
1727 if (!already_selected) {
1728 disable_drag_and_drop(self);
1732 gtk_tree_path_free(path);
1739 folder_monitor_update (TnyFolderObserver *self,
1740 TnyFolderChange *change)
1742 ModestHeaderViewPrivate *priv = NULL;
1743 TnyFolderChangeChanged changed;
1744 TnyFolder *folder = NULL;
1746 changed = tny_folder_change_get_changed (change);
1748 /* Do not notify the observers if the folder of the header
1749 view has changed before this call to the observer
1751 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1752 folder = tny_folder_change_get_folder (change);
1753 if (folder != priv->folder)
1756 MODEST_DEBUG_BLOCK (
1757 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1758 g_print ("ADDED %d/%d (r/t) \n",
1759 tny_folder_change_get_new_unread_count (change),
1760 tny_folder_change_get_new_all_count (change));
1761 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1762 g_print ("ALL COUNT %d\n",
1763 tny_folder_change_get_new_all_count (change));
1764 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1765 g_print ("UNREAD COUNT %d\n",
1766 tny_folder_change_get_new_unread_count (change));
1767 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1768 g_print ("EXPUNGED %d/%d (r/t) \n",
1769 tny_folder_change_get_new_unread_count (change),
1770 tny_folder_change_get_new_all_count (change));
1771 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1772 g_print ("FOLDER RENAME\n");
1773 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1774 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1775 tny_folder_change_get_new_unread_count (change),
1776 tny_folder_change_get_new_all_count (change));
1777 g_print ("---------------------------------------------------\n");
1780 /* Check folder count */
1781 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1782 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1784 g_mutex_lock (priv->observers_lock);
1786 /* Emit signal to evaluate how headers changes affects
1787 to the window view */
1788 g_signal_emit (G_OBJECT(self),
1789 signals[MSG_COUNT_CHANGED_SIGNAL],
1792 /* Added or removed headers, so data stored on cliboard are invalid */
1793 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1794 modest_email_clipboard_clear (priv->clipboard);
1796 g_mutex_unlock (priv->observers_lock);
1802 g_object_unref (folder);
1806 modest_header_view_is_empty (ModestHeaderView *self)
1808 ModestHeaderViewPrivate *priv;
1810 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
1812 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1814 return priv->status == HEADER_VIEW_EMPTY;
1818 modest_header_view_clear (ModestHeaderView *self)
1820 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1822 modest_header_view_set_folder (self, NULL, NULL, NULL);
1826 modest_header_view_copy_selection (ModestHeaderView *header_view)
1828 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
1830 /* Copy selection */
1831 _clipboard_set_selected_data (header_view, FALSE);
1835 modest_header_view_cut_selection (ModestHeaderView *header_view)
1837 ModestHeaderViewPrivate *priv = NULL;
1838 const gchar **hidding = NULL;
1839 guint i, n_selected;
1841 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
1843 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1845 /* Copy selection */
1846 _clipboard_set_selected_data (header_view, TRUE);
1848 /* Get hidding ids */
1849 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1851 /* Clear hidding array created by previous cut operation */
1852 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1854 /* Copy hidding array */
1855 priv->n_selected = n_selected;
1856 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1857 for (i=0; i < n_selected; i++)
1858 priv->hidding_ids[i] = g_strdup(hidding[i]);
1860 /* Hide cut headers */
1861 modest_header_view_refilter (header_view);
1868 _clipboard_set_selected_data (ModestHeaderView *header_view,
1871 ModestHeaderViewPrivate *priv = NULL;
1872 TnyList *headers = NULL;
1874 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1875 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1877 /* Set selected data on clipboard */
1878 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1879 headers = modest_header_view_get_selected_headers (header_view);
1880 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1883 g_object_unref (headers);
1887 ModestHeaderView *self;
1892 notify_filter_change (gpointer data)
1894 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
1896 g_signal_emit (info->self,
1897 signals[MSG_COUNT_CHANGED_SIGNAL],
1898 0, info->folder, NULL);
1904 notify_filter_change_destroy (gpointer data)
1906 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
1908 g_object_unref (info->self);
1909 g_object_unref (info->folder);
1910 g_slice_free (NotifyFilterInfo, info);
1914 filter_row (GtkTreeModel *model,
1918 ModestHeaderViewPrivate *priv = NULL;
1919 TnyHeaderFlags flags;
1920 TnyHeader *header = NULL;
1923 gboolean visible = TRUE;
1924 gboolean found = FALSE;
1925 GValue value = {0,};
1926 HeaderViewStatus old_status;
1928 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1929 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1931 /* Get header from model */
1932 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
1933 flags = (TnyHeaderFlags) g_value_get_int (&value);
1934 g_value_unset (&value);
1935 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
1936 header = (TnyHeader *) g_value_get_object (&value);
1937 g_value_unset (&value);
1939 /* Hide deleted and mark as deleted heders */
1940 if (flags & TNY_HEADER_FLAG_DELETED ||
1941 flags & TNY_HEADER_FLAG_EXPUNGED) {
1946 /* If no data on clipboard, return always TRUE */
1947 if (modest_email_clipboard_cleared(priv->clipboard)) {
1952 /* Get message id from header (ensure is a valid id) */
1959 if (priv->hidding_ids != NULL) {
1960 id = g_strdup(tny_header_get_message_id (header));
1961 for (i=0; i < priv->n_selected && !found; i++)
1962 if (priv->hidding_ids[i] != NULL && id != NULL)
1963 found = (!strcmp (priv->hidding_ids[i], id));
1970 old_status = priv->status;
1971 priv->status = ((gboolean) priv->status) && !visible;
1972 if (priv->status != old_status) {
1973 NotifyFilterInfo *info;
1975 if (priv->status_timeout)
1976 g_source_remove (priv->status_timeout);
1978 info = g_slice_new0 (NotifyFilterInfo);
1979 info->self = g_object_ref (G_OBJECT (user_data));
1980 info->folder = tny_header_get_folder (header);
1981 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
1982 notify_filter_change,
1984 notify_filter_change_destroy);
1991 _clear_hidding_filter (ModestHeaderView *header_view)
1993 ModestHeaderViewPrivate *priv = NULL;
1996 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1997 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1999 if (priv->hidding_ids != NULL) {
2000 for (i=0; i < priv->n_selected; i++)
2001 g_free (priv->hidding_ids[i]);
2002 g_free(priv->hidding_ids);
2007 modest_header_view_refilter (ModestHeaderView *header_view)
2009 GtkTreeModel *model = NULL;
2010 ModestHeaderViewPrivate *priv = NULL;
2012 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2013 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2015 /* Hide cut headers */
2016 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2017 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2018 priv->status = HEADER_VIEW_INIT;
2019 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2024 * Called when an account is removed. If I'm showing a folder of the
2025 * account that has been removed then clear the view
2028 on_account_removed (TnyAccountStore *self,
2029 TnyAccount *account,
2032 ModestHeaderViewPrivate *priv = NULL;
2034 /* Ignore changes in transport accounts */
2035 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2038 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2041 TnyAccount *my_account;
2043 my_account = tny_folder_get_account (priv->folder);
2044 if (my_account == account)
2045 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2046 g_object_unref (account);
2051 modest_header_view_add_observer(ModestHeaderView *header_view,
2052 ModestHeaderViewObserver *observer)
2054 ModestHeaderViewPrivate *priv;
2056 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2057 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2059 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2061 g_mutex_lock(priv->observer_list_lock);
2062 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2063 g_mutex_unlock(priv->observer_list_lock);
2067 modest_header_view_remove_observer(ModestHeaderView *header_view,
2068 ModestHeaderViewObserver *observer)
2070 ModestHeaderViewPrivate *priv;
2072 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2073 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2075 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2077 g_mutex_lock(priv->observer_list_lock);
2078 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2079 g_mutex_unlock(priv->observer_list_lock);
2083 modest_header_view_notify_observers(ModestHeaderView *header_view,
2084 GtkTreeModel *model,
2085 const gchar *tny_folder_id)
2087 ModestHeaderViewPrivate *priv = NULL;
2089 ModestHeaderViewObserver *observer;
2092 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2094 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2096 g_mutex_lock(priv->observer_list_lock);
2097 iter = priv->observer_list;
2098 while(iter != NULL){
2099 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2100 modest_header_view_observer_update(observer, model,
2102 iter = g_slist_next(iter);
2104 g_mutex_unlock(priv->observer_list_lock);