1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
37 #include <modest-header-view.h>
38 #include <modest-header-view-priv.h>
39 #include <modest-dnd.h>
40 #include <modest-tny-folder.h>
42 #include <modest-main-window.h>
44 #include <modest-marshal.h>
45 #include <modest-text-utils.h>
46 #include <modest-icon-names.h>
47 #include <modest-runtime.h>
48 #include "modest-platform.h"
49 #include <modest-hbox-cell-renderer.h>
50 #include <modest-vbox-cell-renderer.h>
52 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
53 static void modest_header_view_init (ModestHeaderView *obj);
54 static void modest_header_view_finalize (GObject *obj);
55 static void modest_header_view_dispose (GObject *obj);
57 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
58 GtkTreeViewColumn *column, gpointer userdata);
60 static gint cmp_rows (GtkTreeModel *tree_model,
65 static gint cmp_subject_rows (GtkTreeModel *tree_model,
70 static gboolean filter_row (GtkTreeModel *model,
74 static void on_account_removed (TnyAccountStore *self,
78 static void on_selection_changed (GtkTreeSelection *sel,
81 static void setup_drag_and_drop (GtkTreeView *self);
83 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
85 static gboolean on_focus_in (GtkWidget *sef,
89 static gboolean on_focus_out (GtkWidget *self,
93 static void folder_monitor_update (TnyFolderObserver *self,
94 TnyFolderChange *change);
96 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
98 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
100 static void _clear_hidding_filter (ModestHeaderView *header_view);
102 static void modest_header_view_notify_observers(
103 ModestHeaderView *header_view,
105 const gchar *tny_folder_id);
107 static gboolean modest_header_view_on_expose_event(
108 GtkTreeView *header_view,
109 GdkEventExpose *event,
113 HEADER_VIEW_NON_EMPTY,
118 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
119 struct _ModestHeaderViewPrivate {
121 ModestHeaderViewStyle style;
123 TnyFolderMonitor *monitor;
124 GMutex *observers_lock;
126 /*header-view-observer observer*/
127 GMutex *observer_list_lock;
128 GSList *observer_list;
130 /* not unref this object, its a singlenton */
131 ModestEmailClipboard *clipboard;
133 /* Filter tree model */
137 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
138 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
140 gulong selection_changed_handler;
141 gulong acc_removed_handler;
143 HeaderViewStatus status;
144 GList *drag_begin_cached_selected_rows;
147 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
148 struct _HeadersCountChangedHelper {
149 ModestHeaderView *self;
150 TnyFolderChange *change;
154 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
155 MODEST_TYPE_HEADER_VIEW, \
156 ModestHeaderViewPrivate))
160 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
163 HEADER_SELECTED_SIGNAL,
164 HEADER_ACTIVATED_SIGNAL,
165 ITEM_NOT_FOUND_SIGNAL,
166 MSG_COUNT_CHANGED_SIGNAL,
167 UPDATING_MSG_LIST_SIGNAL,
172 static GObjectClass *parent_class = NULL;
174 /* uncomment the following if you have defined any signals */
175 static guint signals[LAST_SIGNAL] = {0};
178 modest_header_view_get_type (void)
180 static GType my_type = 0;
182 static const GTypeInfo my_info = {
183 sizeof(ModestHeaderViewClass),
184 NULL, /* base init */
185 NULL, /* base finalize */
186 (GClassInitFunc) modest_header_view_class_init,
187 NULL, /* class finalize */
188 NULL, /* class data */
189 sizeof(ModestHeaderView),
191 (GInstanceInitFunc) modest_header_view_init,
195 static const GInterfaceInfo tny_folder_observer_info =
197 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
198 NULL, /* interface_finalize */
199 NULL /* interface_data */
201 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
205 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
206 &tny_folder_observer_info);
214 modest_header_view_class_init (ModestHeaderViewClass *klass)
216 GObjectClass *gobject_class;
217 gobject_class = (GObjectClass*) klass;
219 parent_class = g_type_class_peek_parent (klass);
220 gobject_class->finalize = modest_header_view_finalize;
221 gobject_class->dispose = modest_header_view_dispose;
223 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
225 signals[HEADER_SELECTED_SIGNAL] =
226 g_signal_new ("header_selected",
227 G_TYPE_FROM_CLASS (gobject_class),
229 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
231 g_cclosure_marshal_VOID__POINTER,
232 G_TYPE_NONE, 1, G_TYPE_POINTER);
234 signals[HEADER_ACTIVATED_SIGNAL] =
235 g_signal_new ("header_activated",
236 G_TYPE_FROM_CLASS (gobject_class),
238 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
240 g_cclosure_marshal_VOID__POINTER,
241 G_TYPE_NONE, 1, G_TYPE_POINTER);
244 signals[ITEM_NOT_FOUND_SIGNAL] =
245 g_signal_new ("item_not_found",
246 G_TYPE_FROM_CLASS (gobject_class),
248 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
250 g_cclosure_marshal_VOID__INT,
251 G_TYPE_NONE, 1, G_TYPE_INT);
253 signals[MSG_COUNT_CHANGED_SIGNAL] =
254 g_signal_new ("msg_count_changed",
255 G_TYPE_FROM_CLASS (gobject_class),
257 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
259 modest_marshal_VOID__POINTER_POINTER,
260 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
262 signals[UPDATING_MSG_LIST_SIGNAL] =
263 g_signal_new ("updating-msg-list",
264 G_TYPE_FROM_CLASS (gobject_class),
266 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
268 g_cclosure_marshal_VOID__BOOLEAN,
269 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
273 tny_folder_observer_init (TnyFolderObserverIface *klass)
275 klass->update_func = folder_monitor_update;
278 static GtkTreeViewColumn*
279 get_new_column (const gchar *name, GtkCellRenderer *renderer,
280 gboolean resizable, gint sort_col_id, gboolean show_as_text,
281 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
283 GtkTreeViewColumn *column;
285 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
286 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
288 gtk_tree_view_column_set_resizable (column, resizable);
290 gtk_tree_view_column_set_expand (column, TRUE);
293 gtk_tree_view_column_add_attribute (column, renderer, "text",
295 if (sort_col_id >= 0)
296 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
298 gtk_tree_view_column_set_sort_indicator (column, FALSE);
299 gtk_tree_view_column_set_reorderable (column, TRUE);
302 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
309 remove_all_columns (ModestHeaderView *obj)
311 GList *columns, *cursor;
313 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
315 for (cursor = columns; cursor; cursor = cursor->next)
316 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
317 GTK_TREE_VIEW_COLUMN(cursor->data));
318 g_list_free (columns);
322 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
324 GtkTreeModel *tree_filter, *sortable;
325 GtkTreeViewColumn *column=NULL;
326 GtkTreeSelection *selection = NULL;
327 GtkCellRenderer *renderer_msgtype,*renderer_header,
328 *renderer_attach, *renderer_compact_date_or_status;
329 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
330 *renderer_subject, *renderer_subject_box, *renderer_recpt,
332 ModestHeaderViewPrivate *priv;
333 GtkTreeViewColumn *compact_column = NULL;
336 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
338 /* FIXME: check whether these renderers need to be freed */
339 renderer_msgtype = gtk_cell_renderer_pixbuf_new ();
340 renderer_attach = gtk_cell_renderer_pixbuf_new ();
341 renderer_priority = gtk_cell_renderer_pixbuf_new ();
342 renderer_header = gtk_cell_renderer_text_new ();
344 renderer_compact_header = modest_vbox_cell_renderer_new ();
345 renderer_recpt_box = modest_hbox_cell_renderer_new ();
346 renderer_subject_box = modest_hbox_cell_renderer_new ();
347 renderer_recpt = gtk_cell_renderer_text_new ();
348 renderer_subject = gtk_cell_renderer_text_new ();
349 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
351 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
352 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
353 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
354 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
355 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
356 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
357 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_subject, TRUE);
358 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", renderer_subject);
359 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
360 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
361 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
362 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
363 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
364 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
366 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
367 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
368 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
369 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
370 g_object_set(G_OBJECT(renderer_header),
371 "ellipsize", PANGO_ELLIPSIZE_END,
373 g_object_set (G_OBJECT (renderer_subject),
374 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
376 g_object_set (G_OBJECT (renderer_recpt),
377 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0,
379 g_object_set(G_OBJECT(renderer_compact_date_or_status),
380 "xalign", 1.0, "yalign", 0.0,
382 g_object_set (G_OBJECT (renderer_priority),
383 "yalign", 1.0, NULL);
384 g_object_set (G_OBJECT (renderer_attach),
385 "yalign", 0.0, NULL);
387 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
388 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
389 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
391 remove_all_columns (self);
393 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
394 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
395 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
396 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
398 /* Add new columns */
399 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
400 ModestHeaderViewColumn col =
401 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
403 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
404 g_printerr ("modest: invalid column %d in column list\n", col);
410 case MODEST_HEADER_VIEW_COLUMN_MSGTYPE:
411 column = get_new_column (_("M"), renderer_msgtype, FALSE,
412 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
414 (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data,
416 gtk_tree_view_column_set_fixed_width (column, 45);
419 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
420 column = get_new_column (_("A"), renderer_attach, FALSE,
421 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
423 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
425 gtk_tree_view_column_set_fixed_width (column, 45);
429 case MODEST_HEADER_VIEW_COLUMN_FROM:
430 column = get_new_column (_("From"), renderer_header, TRUE,
431 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
433 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
434 GINT_TO_POINTER(TRUE));
437 case MODEST_HEADER_VIEW_COLUMN_TO:
438 column = get_new_column (_("To"), renderer_header, TRUE,
439 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
441 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
442 GINT_TO_POINTER(FALSE));
445 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
446 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
447 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
449 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
450 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
451 compact_column = column;
454 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
455 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
456 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
458 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
459 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
460 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
461 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
462 compact_column = column;
466 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
467 column = get_new_column (_("Subject"), renderer_header, TRUE,
468 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
470 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
474 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
475 column = get_new_column (_("Received"), renderer_header, TRUE,
476 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
478 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
479 GINT_TO_POINTER(TRUE));
482 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
483 column = get_new_column (_("Sent"), renderer_header, TRUE,
484 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
486 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
487 GINT_TO_POINTER(FALSE));
490 case MODEST_HEADER_VIEW_COLUMN_SIZE:
491 column = get_new_column (_("Size"), renderer_header, TRUE,
492 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
494 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
497 case MODEST_HEADER_VIEW_COLUMN_STATUS:
498 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
499 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
501 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
506 g_return_val_if_reached(FALSE);
509 /* we keep the column id around */
510 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
511 GINT_TO_POINTER(col));
513 /* we need this ptr when sorting the rows */
514 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
516 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
520 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
521 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
522 (GtkTreeIterCompareFunc) cmp_rows,
523 compact_column, NULL);
524 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
525 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
526 (GtkTreeIterCompareFunc) cmp_subject_rows,
527 compact_column, NULL);
535 modest_header_view_init (ModestHeaderView *obj)
537 ModestHeaderViewPrivate *priv;
540 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
544 priv->monitor = NULL;
545 priv->observers_lock = g_mutex_new ();
547 priv->status = HEADER_VIEW_INIT;
549 priv->observer_list_lock = g_mutex_new();
550 priv->observer_list = NULL;
552 priv->clipboard = modest_runtime_get_email_clipboard ();
553 priv->hidding_ids = NULL;
554 priv->n_selected = 0;
555 priv->selection_changed_handler = 0;
556 priv->acc_removed_handler = 0;
558 /* Sort parameters */
559 for (j=0; j < 2; j++) {
560 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
561 priv->sort_colid[j][i] = -1;
562 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
566 setup_drag_and_drop (GTK_TREE_VIEW (obj));
570 modest_header_view_dispose (GObject *obj)
572 ModestHeaderView *self;
573 ModestHeaderViewPrivate *priv;
574 GtkTreeSelection *sel;
576 self = MODEST_HEADER_VIEW(obj);
577 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
579 /* Free in the dispose to avoid unref cycles */
581 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
582 g_object_unref (G_OBJECT (priv->folder));
586 /* We need to do this here in the dispose because the
587 selection won't exist when finalizing */
588 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
589 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
590 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
591 priv->selection_changed_handler = 0;
594 G_OBJECT_CLASS(parent_class)->dispose (obj);
598 modest_header_view_finalize (GObject *obj)
600 ModestHeaderView *self;
601 ModestHeaderViewPrivate *priv;
603 self = MODEST_HEADER_VIEW(obj);
604 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
606 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
607 priv->acc_removed_handler)) {
608 g_signal_handler_disconnect (modest_runtime_get_account_store (),
609 priv->acc_removed_handler);
612 /* There is no need to lock because there should not be any
613 * reference to self now. */
614 g_mutex_free(priv->observer_list_lock);
615 g_slist_free(priv->observer_list);
617 g_mutex_lock (priv->observers_lock);
619 tny_folder_monitor_stop (priv->monitor);
620 g_object_unref (G_OBJECT (priv->monitor));
622 g_mutex_unlock (priv->observers_lock);
623 g_mutex_free (priv->observers_lock);
625 /* Clear hidding array created by cut operation */
626 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
628 G_OBJECT_CLASS(parent_class)->finalize (obj);
633 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
636 GtkTreeSelection *sel;
637 ModestHeaderView *self;
638 ModestHeaderViewPrivate *priv;
640 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
643 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
644 self = MODEST_HEADER_VIEW(obj);
645 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
647 modest_header_view_set_style (self, style);
649 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
650 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
651 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
653 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
654 TRUE); /* alternating row colors */
656 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
657 priv->selection_changed_handler =
658 g_signal_connect_after (sel, "changed",
659 G_CALLBACK(on_selection_changed), self);
661 g_signal_connect (self, "row-activated",
662 G_CALLBACK (on_header_row_activated), NULL);
664 g_signal_connect (self, "focus-in-event",
665 G_CALLBACK(on_focus_in), NULL);
666 g_signal_connect (self, "focus-out-event",
667 G_CALLBACK(on_focus_out), NULL);
669 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
671 G_CALLBACK (on_account_removed),
674 g_signal_connect (self, "expose-event",
675 G_CALLBACK(modest_header_view_on_expose_event),
678 return GTK_WIDGET(self);
683 modest_header_view_count_selected_headers (ModestHeaderView *self)
685 GtkTreeSelection *sel;
688 g_return_val_if_fail (self, 0);
690 /* Get selection object and check selected rows count */
691 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
692 selected_rows = gtk_tree_selection_count_selected_rows (sel);
694 return selected_rows;
698 modest_header_view_has_selected_headers (ModestHeaderView *self)
700 GtkTreeSelection *sel;
703 g_return_val_if_fail (self, FALSE);
705 /* Get selection object and check selected rows count */
706 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
707 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
714 modest_header_view_get_selected_headers (ModestHeaderView *self)
716 GtkTreeSelection *sel;
717 ModestHeaderViewPrivate *priv;
718 TnyList *header_list = NULL;
720 GList *list, *tmp = NULL;
721 GtkTreeModel *tree_model = NULL;
724 g_return_val_if_fail (self, NULL);
726 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
728 /* Get selected rows */
729 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
730 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
733 header_list = tny_simple_list_new();
735 list = g_list_reverse (list);
738 /* get header from selection */
739 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
740 gtk_tree_model_get (tree_model, &iter,
741 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
743 /* Prepend to list */
744 tny_list_prepend (header_list, G_OBJECT (header));
745 g_object_unref (G_OBJECT (header));
747 tmp = g_list_next (tmp);
750 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
757 /* scroll our list view so the selected item is visible */
759 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
761 #ifdef MODEST_PLATFORM_GNOME
763 GtkTreePath *selected_path;
764 GtkTreePath *start, *end;
768 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
769 selected_path = gtk_tree_model_get_path (model, iter);
771 start = gtk_tree_path_new ();
772 end = gtk_tree_path_new ();
774 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
776 if (gtk_tree_path_compare (selected_path, start) < 0 ||
777 gtk_tree_path_compare (end, selected_path) < 0)
778 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
779 selected_path, NULL, TRUE,
782 gtk_tree_path_free (selected_path);
783 gtk_tree_path_free (start);
784 gtk_tree_path_free (end);
786 #endif /* MODEST_PLATFORM_GNOME */
791 modest_header_view_select_next (ModestHeaderView *self)
793 GtkTreeSelection *sel;
798 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
799 path = get_selected_row (GTK_TREE_VIEW(self), &model);
800 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
801 /* Unselect previous path */
802 gtk_tree_selection_unselect_path (sel, path);
804 /* Move path down and selects new one */
805 if (gtk_tree_model_iter_next (model, &iter)) {
806 gtk_tree_selection_select_iter (sel, &iter);
807 scroll_to_selected (self, &iter, FALSE);
809 gtk_tree_path_free(path);
815 modest_header_view_select_prev (ModestHeaderView *self)
817 GtkTreeSelection *sel;
822 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
823 path = get_selected_row (GTK_TREE_VIEW(self), &model);
824 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
825 /* Unselect previous path */
826 gtk_tree_selection_unselect_path (sel, path);
829 if (gtk_tree_path_prev (path)) {
830 gtk_tree_model_get_iter (model, &iter, path);
832 /* Select the new one */
833 gtk_tree_selection_select_iter (sel, &iter);
834 scroll_to_selected (self, &iter, TRUE);
837 gtk_tree_path_free (path);
842 modest_header_view_get_columns (ModestHeaderView *self)
844 g_return_val_if_fail (self, FALSE);
845 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
851 modest_header_view_set_style (ModestHeaderView *self,
852 ModestHeaderViewStyle style)
854 ModestHeaderViewPrivate *priv;
855 gboolean show_col_headers = FALSE;
856 ModestHeaderViewStyle old_style;
858 g_return_val_if_fail (self, FALSE);
859 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
862 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
863 if (priv->style == style)
864 return TRUE; /* nothing to do */
867 case MODEST_HEADER_VIEW_STYLE_DETAILS:
868 show_col_headers = TRUE;
870 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
873 g_return_val_if_reached (FALSE);
875 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
876 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
878 old_style = priv->style;
885 ModestHeaderViewStyle
886 modest_header_view_get_style (ModestHeaderView *self)
888 g_return_val_if_fail (self, FALSE);
889 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
892 /* This is used to automatically select the first header if the user
893 * has not selected any header yet.
896 modest_header_view_on_expose_event(GtkTreeView *header_view,
897 GdkEventExpose *event,
900 GtkTreeSelection *sel;
902 GtkTreeIter tree_iter;
904 /* I'm invalidating this method because it causes an annoying
905 efect, the focus changes to the header view when selecting
906 a folder in the folder view because of this code and it
907 shouldn't. We need to find another way to set the passive
908 focus on it. Sergio. */
911 model = gtk_tree_view_get_model(header_view);
913 sel = gtk_tree_view_get_selection(header_view);
914 if(!gtk_tree_selection_count_selected_rows(sel))
915 if (gtk_tree_model_get_iter_first(model, &tree_iter))
916 gtk_tree_selection_select_iter(sel, &tree_iter);
922 * This function sets a sortable model in the header view. It's just
923 * used for developing purposes, because it only does a
924 * gtk_tree_view_set_model
927 modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model)
929 /* GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); */
930 /* if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { */
931 /* GtkTreeModel *old_model; */
932 /* ModestHeaderViewPrivate *priv; */
933 /* priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); */
934 /* old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); */
936 /* /\* Set new model *\/ */
937 /* gtk_tree_view_set_model (header_view, model); */
939 gtk_tree_view_set_model (header_view, model);
943 modest_header_view_get_folder (ModestHeaderView *self)
945 ModestHeaderViewPrivate *priv;
946 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
949 g_object_ref (priv->folder);
955 modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder)
959 ModestHeaderViewPrivate *priv;
960 GList *cols, *cursor;
961 GtkTreeModel *filter_model, *sortable;
963 GtkSortType sort_type;
965 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
967 headers = TNY_LIST (tny_gtk_header_list_model_new ());
969 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
970 folder, FALSE, NULL, NULL, NULL);
972 /* Add IDLE observer (monitor) and another folder observer for
973 new messages (self) */
974 g_mutex_lock (priv->observers_lock);
976 tny_folder_monitor_stop (priv->monitor);
977 g_object_unref (G_OBJECT (priv->monitor));
979 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
980 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
981 tny_folder_monitor_start (priv->monitor);
982 g_mutex_unlock (priv->observers_lock);
984 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers));
985 g_object_unref (G_OBJECT (headers));
987 /* Init filter_row function to examine empty status */
988 priv->status = HEADER_VIEW_INIT;
990 /* Create a tree model filter to hide and show rows for cut operations */
991 filter_model = gtk_tree_model_filter_new (sortable, NULL);
992 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
996 g_object_unref (G_OBJECT (sortable));
998 /* install our special sorting functions */
999 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1001 /* Restore sort column id */
1003 type = modest_tny_folder_guess_folder_type (folder);
1004 sort_colid = modest_header_view_get_sort_column_id (self, type);
1005 sort_type = modest_header_view_get_sort_type (self, type);
1006 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1009 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1010 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1011 (GtkTreeIterCompareFunc) cmp_rows,
1013 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable),
1014 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1015 (GtkTreeIterCompareFunc) cmp_subject_rows,
1020 modest_header_view_set_model (GTK_TREE_VIEW (self), filter_model);
1021 modest_header_view_notify_observers(self, GTK_TREE_MODEL(filter_model),
1022 tny_folder_get_id(folder));
1023 g_object_unref (G_OBJECT (filter_model));
1024 /* modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); */
1025 /* g_object_unref (G_OBJECT (sortable)); */
1032 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1034 GtkSortType sort_type)
1036 ModestHeaderViewPrivate *priv = NULL;
1037 GtkTreeModel *tree_filter, *sortable = NULL;
1040 /* Get model and private data */
1041 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1042 tree_filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1043 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER(tree_filter));
1044 /* sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); */
1046 /* Sort tree model */
1047 type = modest_tny_folder_guess_folder_type (priv->folder);
1048 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(sortable),
1051 /* Store new sort parameters */
1052 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1054 /* Save GConf parameters */
1055 /* modest_widget_memory_save (modest_runtime_get_conf(), */
1056 /* G_OBJECT(self), "header-view"); */
1061 modest_header_view_set_sort_params (ModestHeaderView *self,
1063 GtkSortType sort_type,
1066 ModestHeaderViewPrivate *priv;
1067 ModestHeaderViewStyle style;
1069 style = modest_header_view_get_style (self);
1070 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1072 priv->sort_colid[style][type] = sort_colid;
1073 priv->sort_type[style][type] = sort_type;
1077 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1080 ModestHeaderViewPrivate *priv;
1081 ModestHeaderViewStyle style;
1083 style = modest_header_view_get_style (self);
1084 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1086 return priv->sort_colid[style][type];
1090 modest_header_view_get_sort_type (ModestHeaderView *self,
1093 ModestHeaderViewPrivate *priv;
1094 ModestHeaderViewStyle style;
1096 style = modest_header_view_get_style (self);
1097 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1099 return priv->sort_type[style][type];
1103 ModestHeaderView *header_view;
1104 RefreshAsyncUserCallback cb;
1109 folder_refreshed_cb (ModestMailOperation *mail_op,
1113 ModestHeaderViewPrivate *priv;
1114 SetFolderHelper *info;
1116 info = (SetFolderHelper*) user_data;
1118 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1122 info->cb (mail_op, folder, info->user_data);
1124 /* Start the folder count changes observer. We do not need it
1125 before the refresh. Note that the monitor could still be
1126 called for this refresh but now we know that the callback
1127 was previously called */
1128 g_mutex_lock (priv->observers_lock);
1129 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1130 g_mutex_unlock (priv->observers_lock);
1132 /* Notify the observers that the update is over */
1133 g_signal_emit (G_OBJECT (info->header_view),
1134 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1141 modest_header_view_set_folder (ModestHeaderView *self,
1143 RefreshAsyncUserCallback callback,
1146 ModestHeaderViewPrivate *priv;
1147 ModestWindowMgr *mgr = NULL;
1148 GObject *source = NULL;
1149 SetFolderHelper *info;
1151 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1154 g_mutex_lock (priv->observers_lock);
1155 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1156 g_object_unref (priv->folder);
1157 priv->folder = NULL;
1158 g_mutex_unlock (priv->observers_lock);
1162 ModestMailOperation *mail_op = NULL;
1163 GtkTreeSelection *selection;
1165 /* Get main window to use it as source of mail operation */
1166 mgr = modest_runtime_get_window_mgr ();
1167 source = G_OBJECT (modest_window_mgr_get_main_window (modest_runtime_get_window_mgr ()));
1169 /* Set folder in the model */
1170 modest_header_view_set_folder_intern (self, folder);
1172 /* Pick my reference. Nothing to do with the mail operation */
1173 priv->folder = g_object_ref (folder);
1175 /* Clear the selection if exists */
1176 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1177 gtk_tree_selection_unselect_all(selection);
1178 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1180 /* Notify the observers that the update begins */
1181 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1184 /* create the helper */
1185 info = g_malloc0 (sizeof(SetFolderHelper));
1186 info->header_view = self;
1187 info->cb = callback;
1188 info->user_data = user_data;
1190 /* Create the mail operation (source will be the parent widget) */
1191 mail_op = modest_mail_operation_new (source);
1192 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1195 /* Refresh the folder asynchronously */
1196 modest_mail_operation_refresh_folder (mail_op,
1198 folder_refreshed_cb,
1202 g_object_unref (mail_op);
1204 g_mutex_lock (priv->observers_lock);
1206 if (priv->monitor) {
1207 tny_folder_monitor_stop (priv->monitor);
1208 g_object_unref (G_OBJECT (priv->monitor));
1209 priv->monitor = NULL;
1211 modest_header_view_set_model (GTK_TREE_VIEW (self), NULL);
1213 modest_header_view_notify_observers(self, NULL, NULL);
1215 g_mutex_unlock (priv->observers_lock);
1217 /* Notify the observers that the update is over */
1218 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1224 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1225 GtkTreeViewColumn *column, gpointer userdata)
1227 ModestHeaderView *self = NULL;
1228 ModestHeaderViewPrivate *priv = NULL;
1230 GtkTreeModel *model = NULL;
1231 TnyHeader *header = NULL;
1232 TnyHeaderFlags flags;
1234 self = MODEST_HEADER_VIEW (treeview);
1235 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1237 model = gtk_tree_view_get_model (treeview);
1238 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1241 /* get the first selected item */
1242 gtk_tree_model_get (model, &iter,
1243 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1244 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1247 /* Dont open DELETED messages */
1248 if (flags & TNY_HEADER_FLAG_DELETED) {
1249 modest_platform_information_banner (NULL, NULL, _("mcen_ib_message_already_deleted"));
1254 g_signal_emit (G_OBJECT(self),
1255 signals[HEADER_ACTIVATED_SIGNAL],
1261 g_object_unref (G_OBJECT (header));
1266 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1268 GtkTreeModel *model;
1269 TnyHeader *header = NULL;
1270 GtkTreePath *path = NULL;
1272 ModestHeaderView *self;
1273 ModestHeaderViewPrivate *priv;
1274 GList *selected = NULL;
1276 g_return_if_fail (sel);
1277 g_return_if_fail (user_data);
1279 self = MODEST_HEADER_VIEW (user_data);
1280 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1282 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1283 if (selected != NULL)
1284 path = (GtkTreePath *) selected->data;
1285 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1286 return; /* msg was _un_selected */
1288 gtk_tree_model_get (model, &iter,
1289 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1293 g_signal_emit (G_OBJECT(self),
1294 signals[HEADER_SELECTED_SIGNAL],
1297 g_object_unref (G_OBJECT (header));
1299 /* free all items in 'selected' */
1300 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1301 g_list_free (selected);
1305 /* PROTECTED method. It's useful when we want to force a given
1306 selection to reload a msg. For example if we have selected a header
1307 in offline mode, when Modest become online, we want to reload the
1308 message automatically without an user click over the header */
1310 _modest_header_view_change_selection (GtkTreeSelection *selection,
1313 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1314 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1316 on_selection_changed (selection, user_data);
1319 static gint compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1321 p1 = p1 & TNY_HEADER_FLAG_PRIORITY;
1322 p2 = p2 & TNY_HEADER_FLAG_PRIORITY;
1324 p1 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1326 p2 = TNY_HEADER_FLAG_LOW_PRIORITY + 1;
1331 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1338 /* static int counter = 0; */
1340 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1341 /* col_id = gtk_tree_sortable_get_sort_column_id (GTK_TREE_SORTABLE (tree_model)); */
1342 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1346 case TNY_HEADER_FLAG_ATTACHMENTS:
1348 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1349 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1350 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1351 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1353 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1354 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1356 return cmp ? cmp : t1 - t2;
1358 case TNY_HEADER_FLAG_PRIORITY:
1359 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1360 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1361 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1362 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1364 /* This is for making priority values respect the intuitive sort relationship
1365 * as HIGH is 11, LOW is 01, and we put NORMAL AS 10 (2) */
1366 cmp = compare_priorities (val1, val2);
1368 return cmp ? cmp : t1 - t2;
1371 return &iter1 - &iter2; /* oughhhh */
1376 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1382 /* static int counter = 0; */
1384 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1386 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1387 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1388 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1389 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1391 cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
1392 val2 + modest_text_utils_get_subject_prefix_len(val2),
1399 /* Drag and drop stuff */
1401 drag_data_get_cb (GtkWidget *widget,
1402 GdkDragContext *context,
1403 GtkSelectionData *selection_data,
1408 ModestHeaderView *self = NULL;
1409 ModestHeaderViewPrivate *priv = NULL;
1411 self = MODEST_HEADER_VIEW (widget);
1412 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1414 /* Set the data. Do not use the current selection because it
1415 could be different than the selection at the beginning of
1417 modest_dnd_selection_data_set_paths (selection_data,
1418 priv->drag_begin_cached_selected_rows);
1422 * We're caching the selected rows at the beginning because the
1423 * selection could change between drag-begin and drag-data-get, for
1424 * example if we have a set of rows already selected, and then we
1425 * click in one of them (without SHIFT key pressed) and begin a drag,
1426 * the selection at that moment contains all the selected lines, but
1427 * after dropping the selection, the release event provokes that only
1428 * the row used to begin the drag is selected, so at the end the
1429 * drag&drop affects only one rows instead of all the selected ones.
1433 drag_begin_cb (GtkWidget *widget,
1434 GdkDragContext *context,
1437 ModestHeaderView *self = NULL;
1438 ModestHeaderViewPrivate *priv = NULL;
1439 GtkTreeSelection *selection;
1441 self = MODEST_HEADER_VIEW (widget);
1442 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1444 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1445 priv->drag_begin_cached_selected_rows =
1446 gtk_tree_selection_get_selected_rows (selection, NULL);
1450 * We use the drag-end signal to clear the cached selection, we use
1451 * this because this allways happens, whether or not the d&d was a
1455 drag_end_cb (GtkWidget *widget,
1459 ModestHeaderView *self = NULL;
1460 ModestHeaderViewPrivate *priv = NULL;
1462 self = MODEST_HEADER_VIEW (widget);
1463 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1465 /* Free cached data */
1466 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1467 g_list_free (priv->drag_begin_cached_selected_rows);
1468 priv->drag_begin_cached_selected_rows = NULL;
1471 /* Header view drag types */
1472 const GtkTargetEntry header_view_drag_types[] = {
1473 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1477 setup_drag_and_drop (GtkTreeView *self)
1479 gtk_drag_source_set (GTK_WIDGET (self),
1481 header_view_drag_types,
1482 G_N_ELEMENTS (header_view_drag_types),
1483 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1485 g_signal_connect(G_OBJECT (self), "drag_data_get",
1486 G_CALLBACK(drag_data_get_cb), NULL);
1488 g_signal_connect(G_OBJECT (self), "drag_begin",
1489 G_CALLBACK(drag_begin_cb), NULL);
1491 g_signal_connect(G_OBJECT (self), "drag_end",
1492 G_CALLBACK(drag_end_cb), NULL);
1495 static GtkTreePath *
1496 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1498 GtkTreePath *path = NULL;
1499 GtkTreeSelection *sel = NULL;
1502 sel = gtk_tree_view_get_selection(self);
1503 rows = gtk_tree_selection_get_selected_rows (sel, model);
1505 if ((rows == NULL) || (g_list_length(rows) != 1))
1508 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1513 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1520 * This function moves the tree view scroll to the current selected
1521 * row when the widget grabs the focus
1524 on_focus_in (GtkWidget *self,
1525 GdkEventFocus *event,
1528 GtkTreeSelection *selection;
1529 GtkTreeModel *model;
1530 GList *selected = NULL;
1531 GtkTreePath *selected_path = NULL;
1533 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1537 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1538 /* If none selected yet, pick the first one */
1539 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1543 /* Return if the model is empty */
1544 if (!gtk_tree_model_get_iter_first (model, &iter))
1547 path = gtk_tree_model_get_path (model, &iter);
1548 gtk_tree_selection_select_path (selection, path);
1549 gtk_tree_path_free (path);
1552 /* Need to get the all the rows because is selection multiple */
1553 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1554 if (selected == NULL) return FALSE;
1555 selected_path = (GtkTreePath *) selected->data;
1557 /* Check if we need to scroll */
1558 #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */
1559 GtkTreePath *start_path = NULL;
1560 GtkTreePath *end_path = NULL;
1561 if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self),
1565 if ((gtk_tree_path_compare (start_path, selected_path) != -1) ||
1566 (gtk_tree_path_compare (end_path, selected_path) != 1)) {
1568 /* Scroll to first path */
1569 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self),
1578 gtk_tree_path_free (start_path);
1580 gtk_tree_path_free (end_path);
1582 #endif /* GTK_CHECK_VERSION */
1585 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1586 g_list_free (selected);
1592 on_focus_out (GtkWidget *self,
1593 GdkEventFocus *event,
1597 if (!gtk_widget_is_focus (self)) {
1598 GtkTreeSelection *selection = NULL;
1599 GList *selected_rows = NULL;
1600 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1601 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1602 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1603 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1604 gtk_tree_selection_unselect_all (selection);
1605 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1606 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1607 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1608 g_list_free (selected_rows);
1615 folder_monitor_update (TnyFolderObserver *self,
1616 TnyFolderChange *change)
1618 ModestHeaderViewPrivate *priv = NULL;
1619 TnyFolderChangeChanged changed;
1620 TnyFolder *folder = NULL;
1622 changed = tny_folder_change_get_changed (change);
1624 /* Do not notify the observers if the folder of the header
1625 view has changed before this call to the observer
1627 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1628 folder = tny_folder_change_get_folder (change);
1629 if (folder != priv->folder)
1632 /* Check folder count */
1633 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1634 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1636 g_mutex_lock (priv->observers_lock);
1638 /* Emit signal to evaluate how headers changes affects
1639 to the window view */
1640 g_signal_emit (G_OBJECT(self),
1641 signals[MSG_COUNT_CHANGED_SIGNAL],
1644 /* Added or removed headers, so data stored on cliboard are invalid */
1645 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1646 modest_email_clipboard_clear (priv->clipboard);
1648 g_mutex_unlock (priv->observers_lock);
1654 g_object_unref (folder);
1658 modest_header_view_is_empty (ModestHeaderView *self)
1660 ModestHeaderViewPrivate *priv = NULL;
1662 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1664 return priv->status == HEADER_VIEW_EMPTY;
1668 modest_header_view_clear (ModestHeaderView *self)
1670 modest_header_view_set_folder (self, NULL, NULL, NULL);
1674 modest_header_view_copy_selection (ModestHeaderView *header_view)
1676 /* Copy selection */
1677 _clipboard_set_selected_data (header_view, FALSE);
1681 modest_header_view_cut_selection (ModestHeaderView *header_view)
1683 ModestHeaderViewPrivate *priv = NULL;
1684 const gchar **hidding = NULL;
1685 guint i, n_selected;
1687 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1688 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1690 /* Copy selection */
1691 _clipboard_set_selected_data (header_view, TRUE);
1693 /* Get hidding ids */
1694 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
1696 /* Clear hidding array created by previous cut operation */
1697 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
1699 /* Copy hidding array */
1700 priv->n_selected = n_selected;
1701 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
1702 for (i=0; i < n_selected; i++)
1703 priv->hidding_ids[i] = g_strdup(hidding[i]);
1705 /* Hide cut headers */
1706 modest_header_view_refilter (header_view);
1713 _clipboard_set_selected_data (ModestHeaderView *header_view,
1716 ModestHeaderViewPrivate *priv = NULL;
1717 TnyList *headers = NULL;
1719 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1720 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
1722 /* Set selected data on clipboard */
1723 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
1724 headers = modest_header_view_get_selected_headers (header_view);
1725 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
1728 g_object_unref (headers);
1734 filter_row (GtkTreeModel *model,
1738 ModestHeaderViewPrivate *priv = NULL;
1739 TnyHeaderFlags flags;
1740 TnyHeader *header = NULL;
1743 gboolean visible = TRUE;
1744 gboolean found = FALSE;
1746 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
1747 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1749 /* Get header from model */
1750 gtk_tree_model_get (model, iter,
1751 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1752 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1755 /* Hide mark as deleted heders */
1756 if (flags & TNY_HEADER_FLAG_DELETED) {
1761 /* If no data on clipboard, return always TRUE */
1762 if (modest_email_clipboard_cleared(priv->clipboard)) {
1767 /* Get message id from header (ensure is a valid id) */
1768 if (!header) return FALSE;
1769 id = g_strdup(tny_header_get_message_id (header));
1772 if (priv->hidding_ids != NULL) {
1773 for (i=0; i < priv->n_selected && !found; i++)
1774 if (priv->hidding_ids[i] != NULL && id != NULL)
1775 found = (!strcmp (priv->hidding_ids[i], id));
1781 priv->status = ((gboolean) priv->status) && !visible;
1785 g_object_unref (header);
1792 _clear_hidding_filter (ModestHeaderView *header_view)
1794 ModestHeaderViewPrivate *priv = NULL;
1797 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1798 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1800 if (priv->hidding_ids != NULL) {
1801 for (i=0; i < priv->n_selected; i++)
1802 g_free (priv->hidding_ids[i]);
1803 g_free(priv->hidding_ids);
1808 modest_header_view_refilter (ModestHeaderView *header_view)
1810 GtkTreeModel *model = NULL;
1811 ModestHeaderViewPrivate *priv = NULL;
1813 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
1814 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1816 /* Hide cut headers */
1817 model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
1818 if (GTK_IS_TREE_MODEL_FILTER (model)) {
1820 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
1825 * Called when an account is removed. If I'm showing a folder of the
1826 * account that has been removed then clear the view
1829 on_account_removed (TnyAccountStore *self,
1830 TnyAccount *account,
1833 ModestHeaderViewPrivate *priv = NULL;
1835 /* Ignore changes in transport accounts */
1836 if (TNY_IS_TRANSPORT_ACCOUNT (account))
1839 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
1842 TnyAccount *my_account;
1844 my_account = tny_folder_get_account (priv->folder);
1845 if (my_account == account)
1846 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
1847 g_object_unref (account);
1851 void modest_header_view_add_observer(
1852 ModestHeaderView *header_view,
1853 ModestHeaderViewObserver *observer)
1855 ModestHeaderViewPrivate *priv = NULL;
1857 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1858 g_assert(observer != NULL);
1859 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1861 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1863 g_mutex_lock(priv->observer_list_lock);
1864 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
1865 g_mutex_unlock(priv->observer_list_lock);
1869 modest_header_view_remove_observer(ModestHeaderView *header_view,
1870 ModestHeaderViewObserver *observer)
1872 ModestHeaderViewPrivate *priv = NULL;
1874 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1875 g_assert(observer != NULL);
1876 g_assert(MODEST_IS_HEADER_VIEW_OBSERVER(observer));
1878 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1880 g_mutex_lock(priv->observer_list_lock);
1881 priv->observer_list = g_slist_remove(priv->observer_list, observer);
1882 g_mutex_unlock(priv->observer_list_lock);
1886 modest_header_view_notify_observers(ModestHeaderView *header_view,
1887 GtkTreeModel *model,
1888 const gchar *tny_folder_id)
1890 ModestHeaderViewPrivate *priv = NULL;
1892 ModestHeaderViewObserver *observer;
1894 g_assert(MODEST_IS_HEADER_VIEW(header_view));
1896 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1898 g_mutex_lock(priv->observer_list_lock);
1899 iter = priv->observer_list;
1900 while(iter != NULL){
1901 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
1902 modest_header_view_observer_update(observer, model,
1904 iter = g_slist_next(iter);
1906 g_mutex_unlock(priv->observer_list_lock);