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>
35 #include <tny-error.h>
36 #include <tny-merge-folder.h>
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-main-window.h>
45 #include <modest-ui-actions.h>
46 #include <modest-marshal.h>
47 #include <modest-text-utils.h>
48 #include <modest-icon-names.h>
49 #include <modest-runtime.h>
50 #include "modest-platform.h"
51 #include <modest-hbox-cell-renderer.h>
52 #include <modest-vbox-cell-renderer.h>
53 #include <modest-datetime-formatter.h>
54 #include <modest-ui-constants.h>
55 #ifdef MODEST_TOOLKIT_HILDON2
56 #include <hildon/hildon.h>
59 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
60 static void modest_header_view_init (ModestHeaderView *obj);
61 static void modest_header_view_finalize (GObject *obj);
62 static void modest_header_view_dispose (GObject *obj);
64 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
65 GtkTreeViewColumn *column, gpointer userdata);
67 static gint cmp_rows (GtkTreeModel *tree_model,
72 static gint cmp_subject_rows (GtkTreeModel *tree_model,
77 static gboolean filter_row (GtkTreeModel *model,
81 static void on_account_removed (TnyAccountStore *self,
85 static void on_selection_changed (GtkTreeSelection *sel,
88 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
91 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
94 static void setup_drag_and_drop (GtkWidget *self);
96 static void enable_drag_and_drop (GtkWidget *self);
98 static void disable_drag_and_drop (GtkWidget *self);
100 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
102 #ifndef MODEST_TOOLKIT_HILDON2
103 static gboolean on_focus_in (GtkWidget *sef,
104 GdkEventFocus *event,
107 static gboolean on_focus_out (GtkWidget *self,
108 GdkEventFocus *event,
112 static void folder_monitor_update (TnyFolderObserver *self,
113 TnyFolderChange *change);
115 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
117 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
119 static void _clear_hidding_filter (ModestHeaderView *header_view);
121 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
123 const gchar *tny_folder_id);
125 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
126 GdkEventExpose *event,
129 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
130 static void update_style (ModestHeaderView *self);
133 HEADER_VIEW_NON_EMPTY,
138 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
139 struct _ModestHeaderViewPrivate {
141 ModestHeaderViewStyle style;
144 TnyFolderMonitor *monitor;
145 GMutex *observers_lock;
147 /*header-view-observer observer*/
148 GMutex *observer_list_lock;
149 GSList *observer_list;
151 /* not unref this object, its a singlenton */
152 ModestEmailClipboard *clipboard;
154 /* Filter tree model */
157 GtkTreeRowReference *autoselect_reference;
158 ModestHeaderViewFilter filter;
159 #ifdef MODEST_TOOLKIT_HILDON2
160 GtkWidget *live_search;
161 guint live_search_timeout;
164 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
165 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
167 gulong selection_changed_handler;
168 gulong acc_removed_handler;
170 GList *drag_begin_cached_selected_rows;
172 HeaderViewStatus status;
173 guint status_timeout;
174 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
176 ModestDatetimeFormatter *datetime_formatter;
178 GtkCellRenderer *renderer_subject;
179 GtkCellRenderer *renderer_address;
180 GtkCellRenderer *renderer_date_status;
182 GdkColor active_color;
183 GdkColor secondary_color;
187 gchar *filter_string;
188 gchar **filter_string_splitted;
189 gboolean filter_date_range;
190 time_t date_range_start;
191 time_t date_range_end;
194 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
195 struct _HeadersCountChangedHelper {
196 ModestHeaderView *self;
197 TnyFolderChange *change;
201 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
202 MODEST_TYPE_HEADER_VIEW, \
203 ModestHeaderViewPrivate))
207 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
210 HEADER_SELECTED_SIGNAL,
211 HEADER_ACTIVATED_SIGNAL,
212 ITEM_NOT_FOUND_SIGNAL,
213 MSG_COUNT_CHANGED_SIGNAL,
214 UPDATING_MSG_LIST_SIGNAL,
219 static GObjectClass *parent_class = NULL;
221 /* uncomment the following if you have defined any signals */
222 static guint signals[LAST_SIGNAL] = {0};
225 modest_header_view_get_type (void)
227 static GType my_type = 0;
229 static const GTypeInfo my_info = {
230 sizeof(ModestHeaderViewClass),
231 NULL, /* base init */
232 NULL, /* base finalize */
233 (GClassInitFunc) modest_header_view_class_init,
234 NULL, /* class finalize */
235 NULL, /* class data */
236 sizeof(ModestHeaderView),
238 (GInstanceInitFunc) modest_header_view_init,
242 static const GInterfaceInfo tny_folder_observer_info =
244 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
245 NULL, /* interface_finalize */
246 NULL /* interface_data */
248 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
252 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
253 &tny_folder_observer_info);
261 modest_header_view_class_init (ModestHeaderViewClass *klass)
263 GObjectClass *gobject_class;
264 gobject_class = (GObjectClass*) klass;
266 parent_class = g_type_class_peek_parent (klass);
267 gobject_class->finalize = modest_header_view_finalize;
268 gobject_class->dispose = modest_header_view_dispose;
270 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
272 signals[HEADER_SELECTED_SIGNAL] =
273 g_signal_new ("header_selected",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
278 g_cclosure_marshal_VOID__POINTER,
279 G_TYPE_NONE, 1, G_TYPE_POINTER);
281 signals[HEADER_ACTIVATED_SIGNAL] =
282 g_signal_new ("header_activated",
283 G_TYPE_FROM_CLASS (gobject_class),
285 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
287 gtk_marshal_VOID__POINTER_POINTER,
288 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
291 signals[ITEM_NOT_FOUND_SIGNAL] =
292 g_signal_new ("item_not_found",
293 G_TYPE_FROM_CLASS (gobject_class),
295 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
297 g_cclosure_marshal_VOID__INT,
298 G_TYPE_NONE, 1, G_TYPE_INT);
300 signals[MSG_COUNT_CHANGED_SIGNAL] =
301 g_signal_new ("msg_count_changed",
302 G_TYPE_FROM_CLASS (gobject_class),
304 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
306 modest_marshal_VOID__POINTER_POINTER,
307 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
309 signals[UPDATING_MSG_LIST_SIGNAL] =
310 g_signal_new ("updating-msg-list",
311 G_TYPE_FROM_CLASS (gobject_class),
313 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
315 g_cclosure_marshal_VOID__BOOLEAN,
316 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
318 #ifdef MODEST_TOOLKIT_HILDON2
319 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
325 tny_folder_observer_init (TnyFolderObserverIface *klass)
327 klass->update = folder_monitor_update;
330 static GtkTreeViewColumn*
331 get_new_column (const gchar *name, GtkCellRenderer *renderer,
332 gboolean resizable, gint sort_col_id, gboolean show_as_text,
333 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
335 GtkTreeViewColumn *column;
337 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
338 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
340 gtk_tree_view_column_set_resizable (column, resizable);
342 gtk_tree_view_column_set_expand (column, TRUE);
345 gtk_tree_view_column_add_attribute (column, renderer, "text",
347 if (sort_col_id >= 0)
348 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
350 gtk_tree_view_column_set_sort_indicator (column, FALSE);
351 gtk_tree_view_column_set_reorderable (column, TRUE);
354 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
361 remove_all_columns (ModestHeaderView *obj)
363 GList *columns, *cursor;
365 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
367 for (cursor = columns; cursor; cursor = cursor->next)
368 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
369 GTK_TREE_VIEW_COLUMN(cursor->data));
370 g_list_free (columns);
374 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
376 GtkTreeModel *sortable;
377 GtkTreeViewColumn *column=NULL;
378 GtkTreeSelection *selection = NULL;
379 GtkCellRenderer *renderer_header,
380 *renderer_attach, *renderer_compact_date_or_status;
381 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
382 *renderer_subject_box, *renderer_recpt,
384 ModestHeaderViewPrivate *priv;
385 GtkTreeViewColumn *compact_column = NULL;
388 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
389 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
391 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
393 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
395 /* TODO: check whether these renderers need to be freed */
396 renderer_attach = gtk_cell_renderer_pixbuf_new ();
397 renderer_priority = gtk_cell_renderer_pixbuf_new ();
398 renderer_header = gtk_cell_renderer_text_new ();
400 renderer_compact_header = modest_vbox_cell_renderer_new ();
401 renderer_recpt_box = modest_hbox_cell_renderer_new ();
402 renderer_subject_box = modest_hbox_cell_renderer_new ();
403 renderer_recpt = gtk_cell_renderer_text_new ();
404 priv->renderer_address = renderer_recpt;
405 priv->renderer_subject = gtk_cell_renderer_text_new ();
406 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
407 priv->renderer_date_status = renderer_compact_date_or_status;
409 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
410 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
411 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
412 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
413 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
414 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
415 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
416 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
417 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
418 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
419 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
420 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
421 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
422 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
424 #ifdef MODEST_TOOLKIT_HILDON2
425 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
427 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
428 #ifndef MODEST_TOOLKIT_GTK
429 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
430 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
432 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
433 g_object_set(G_OBJECT(renderer_header),
434 "ellipsize", PANGO_ELLIPSIZE_END,
436 g_object_set (G_OBJECT (priv->renderer_subject),
437 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
439 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
440 g_object_set (G_OBJECT (renderer_recpt),
441 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
443 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
444 g_object_set(G_OBJECT(renderer_compact_date_or_status),
445 "xalign", 1.0, "yalign", 0.1,
447 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
448 #ifdef MODEST_TOOLKIT_HILDON2
449 g_object_set (G_OBJECT (renderer_priority),
451 "xalign", 0.0, NULL);
452 g_object_set (G_OBJECT (renderer_attach),
454 "xalign", 0.0, NULL);
456 g_object_set (G_OBJECT (renderer_priority),
457 "yalign", 0.5, NULL);
458 g_object_set (G_OBJECT (renderer_attach),
459 "yalign", 0.0, NULL);
462 #ifdef MODEST_TOOLKIT_HILDON1
463 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
464 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
465 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
466 #elif MODEST_TOOLKIT_HILDON2
467 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
468 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
469 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
471 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
472 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
473 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
476 remove_all_columns (self);
478 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
479 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
480 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
482 /* Add new columns */
483 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
484 ModestHeaderViewColumn col =
485 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
487 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
488 g_printerr ("modest: invalid column %d in column list\n", col);
494 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
495 column = get_new_column (_("A"), renderer_attach, FALSE,
496 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
498 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
500 gtk_tree_view_column_set_fixed_width (column, 45);
504 case MODEST_HEADER_VIEW_COLUMN_FROM:
505 column = get_new_column (_("From"), renderer_header, TRUE,
506 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
508 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
509 GINT_TO_POINTER(TRUE));
512 case MODEST_HEADER_VIEW_COLUMN_TO:
513 column = get_new_column (_("To"), renderer_header, TRUE,
514 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
516 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
517 GINT_TO_POINTER(FALSE));
520 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
521 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
522 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
524 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
525 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
526 compact_column = column;
529 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
530 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
531 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
533 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
534 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
535 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
536 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
537 compact_column = column;
541 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
542 column = get_new_column (_("Subject"), renderer_header, TRUE,
543 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
545 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
549 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
550 column = get_new_column (_("Received"), renderer_header, TRUE,
551 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
553 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
554 GINT_TO_POINTER(TRUE));
557 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
558 column = get_new_column (_("Sent"), renderer_header, TRUE,
559 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
561 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
562 GINT_TO_POINTER(FALSE));
565 case MODEST_HEADER_VIEW_COLUMN_SIZE:
566 column = get_new_column (_("Size"), renderer_header, TRUE,
567 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
569 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
572 case MODEST_HEADER_VIEW_COLUMN_STATUS:
573 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
574 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
576 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
581 g_return_val_if_reached(FALSE);
584 /* we keep the column id around */
585 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
586 GINT_TO_POINTER(col));
588 /* we need this ptr when sorting the rows */
589 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
591 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
595 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
596 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
597 (GtkTreeIterCompareFunc) cmp_rows,
598 compact_column, NULL);
599 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
600 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
601 (GtkTreeIterCompareFunc) cmp_subject_rows,
602 compact_column, NULL);
606 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
612 datetime_format_changed (ModestDatetimeFormatter *formatter,
613 ModestHeaderView *self)
615 gtk_widget_queue_draw (GTK_WIDGET (self));
619 modest_header_view_init (ModestHeaderView *obj)
621 ModestHeaderViewPrivate *priv;
624 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
626 priv->show_latest = 0;
629 priv->is_outbox = FALSE;
631 priv->monitor = NULL;
632 priv->observers_lock = g_mutex_new ();
633 priv->autoselect_reference = NULL;
635 priv->status = HEADER_VIEW_INIT;
636 priv->status_timeout = 0;
637 priv->notify_status = TRUE;
639 priv->observer_list_lock = g_mutex_new();
640 priv->observer_list = NULL;
642 priv->clipboard = modest_runtime_get_email_clipboard ();
643 priv->hidding_ids = NULL;
644 priv->n_selected = 0;
645 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
646 #ifdef MODEST_TOOLKIT_HILDON2
647 priv->live_search = NULL;
648 priv->live_search_timeout = 0;
650 priv->filter_string = NULL;
651 priv->filter_string_splitted = NULL;
652 priv->filter_date_range = FALSE;
653 priv->selection_changed_handler = 0;
654 priv->acc_removed_handler = 0;
656 /* Sort parameters */
657 for (j=0; j < 2; j++) {
658 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
659 priv->sort_colid[j][i] = -1;
660 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
664 priv->datetime_formatter = modest_datetime_formatter_new ();
665 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
666 G_CALLBACK (datetime_format_changed), (gpointer) obj);
668 setup_drag_and_drop (GTK_WIDGET(obj));
672 modest_header_view_dispose (GObject *obj)
674 ModestHeaderView *self;
675 ModestHeaderViewPrivate *priv;
676 GtkTreeSelection *sel;
678 self = MODEST_HEADER_VIEW(obj);
679 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
681 #ifdef MODEST_TOOLKIT_HILDON2
682 if (priv->live_search_timeout > 0) {
683 g_source_remove (priv->live_search_timeout);
684 priv->live_search_timeout = 0;
688 if (priv->datetime_formatter) {
689 g_object_unref (priv->datetime_formatter);
690 priv->datetime_formatter = NULL;
693 /* Free in the dispose to avoid unref cycles */
695 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
696 g_object_unref (G_OBJECT (priv->folder));
700 /* We need to do this here in the dispose because the
701 selection won't exist when finalizing */
702 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
703 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
704 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
705 priv->selection_changed_handler = 0;
708 G_OBJECT_CLASS(parent_class)->dispose (obj);
712 modest_header_view_finalize (GObject *obj)
714 ModestHeaderView *self;
715 ModestHeaderViewPrivate *priv;
717 self = MODEST_HEADER_VIEW(obj);
718 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
720 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
721 priv->acc_removed_handler)) {
722 g_signal_handler_disconnect (modest_runtime_get_account_store (),
723 priv->acc_removed_handler);
726 /* There is no need to lock because there should not be any
727 * reference to self now. */
728 g_mutex_free(priv->observer_list_lock);
729 g_slist_free(priv->observer_list);
731 g_mutex_lock (priv->observers_lock);
733 tny_folder_monitor_stop (priv->monitor);
734 g_object_unref (G_OBJECT (priv->monitor));
736 g_mutex_unlock (priv->observers_lock);
737 g_mutex_free (priv->observers_lock);
739 /* Clear hidding array created by cut operation */
740 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
742 if (priv->autoselect_reference != NULL) {
743 gtk_tree_row_reference_free (priv->autoselect_reference);
744 priv->autoselect_reference = NULL;
747 if (priv->filter_string) {
748 g_free (priv->filter_string);
751 if (priv->filter_string_splitted) {
752 g_strfreev (priv->filter_string_splitted);
755 G_OBJECT_CLASS(parent_class)->finalize (obj);
760 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
763 GtkTreeSelection *sel;
764 ModestHeaderView *self;
765 ModestHeaderViewPrivate *priv;
767 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
770 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
771 self = MODEST_HEADER_VIEW(obj);
772 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
774 modest_header_view_set_style (self, style);
776 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
777 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
778 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
780 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
781 TRUE); /* alternating row colors */
783 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
784 priv->selection_changed_handler =
785 g_signal_connect_after (sel, "changed",
786 G_CALLBACK(on_selection_changed), self);
788 g_signal_connect (self, "row-activated",
789 G_CALLBACK (on_header_row_activated), NULL);
791 #ifndef MODEST_TOOLKIT_HILDON2
792 g_signal_connect (self, "focus-in-event",
793 G_CALLBACK(on_focus_in), NULL);
794 g_signal_connect (self, "focus-out-event",
795 G_CALLBACK(on_focus_out), NULL);
798 g_signal_connect (self, "button-press-event",
799 G_CALLBACK(on_button_press_event), NULL);
800 g_signal_connect (self, "button-release-event",
801 G_CALLBACK(on_button_release_event), NULL);
803 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
805 G_CALLBACK (on_account_removed),
808 g_signal_connect (self, "expose-event",
809 G_CALLBACK(modest_header_view_on_expose_event),
812 return GTK_WIDGET(self);
817 modest_header_view_count_selected_headers (ModestHeaderView *self)
819 GtkTreeSelection *sel;
822 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
824 /* Get selection object and check selected rows count */
825 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
826 selected_rows = gtk_tree_selection_count_selected_rows (sel);
828 return selected_rows;
832 modest_header_view_has_selected_headers (ModestHeaderView *self)
834 GtkTreeSelection *sel;
837 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
839 /* Get selection object and check selected rows count */
840 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
841 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
848 modest_header_view_get_selected_headers (ModestHeaderView *self)
850 GtkTreeSelection *sel;
851 TnyList *header_list = NULL;
853 GList *list, *tmp = NULL;
854 GtkTreeModel *tree_model = NULL;
857 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
860 /* Get selected rows */
861 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
862 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
865 header_list = tny_simple_list_new();
867 list = g_list_reverse (list);
870 /* get header from selection */
871 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
872 gtk_tree_model_get (tree_model, &iter,
873 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
875 /* Prepend to list */
876 tny_list_prepend (header_list, G_OBJECT (header));
877 g_object_unref (G_OBJECT (header));
879 tmp = g_list_next (tmp);
882 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
889 /* scroll our list view so the selected item is visible */
891 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
893 #ifdef MODEST_TOOLKIT_GTK
895 GtkTreePath *selected_path;
896 GtkTreePath *start, *end;
900 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
901 selected_path = gtk_tree_model_get_path (model, iter);
903 start = gtk_tree_path_new ();
904 end = gtk_tree_path_new ();
906 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
908 if (gtk_tree_path_compare (selected_path, start) < 0 ||
909 gtk_tree_path_compare (end, selected_path) < 0)
910 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
911 selected_path, NULL, TRUE,
914 gtk_tree_path_free (selected_path);
915 gtk_tree_path_free (start);
916 gtk_tree_path_free (end);
918 #endif /* MODEST_TOOLKIT_GTK */
923 modest_header_view_select_next (ModestHeaderView *self)
925 GtkTreeSelection *sel;
930 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
932 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
933 path = get_selected_row (GTK_TREE_VIEW(self), &model);
934 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
935 /* Unselect previous path */
936 gtk_tree_selection_unselect_path (sel, path);
938 /* Move path down and selects new one */
939 if (gtk_tree_model_iter_next (model, &iter)) {
940 gtk_tree_selection_select_iter (sel, &iter);
941 scroll_to_selected (self, &iter, FALSE);
943 gtk_tree_path_free(path);
949 modest_header_view_select_prev (ModestHeaderView *self)
951 GtkTreeSelection *sel;
956 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
958 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
959 path = get_selected_row (GTK_TREE_VIEW(self), &model);
960 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
961 /* Unselect previous path */
962 gtk_tree_selection_unselect_path (sel, path);
965 if (gtk_tree_path_prev (path)) {
966 gtk_tree_model_get_iter (model, &iter, path);
968 /* Select the new one */
969 gtk_tree_selection_select_iter (sel, &iter);
970 scroll_to_selected (self, &iter, TRUE);
973 gtk_tree_path_free (path);
978 modest_header_view_get_columns (ModestHeaderView *self)
980 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
982 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
988 modest_header_view_set_style (ModestHeaderView *self,
989 ModestHeaderViewStyle style)
991 ModestHeaderViewPrivate *priv;
992 gboolean show_col_headers = FALSE;
993 ModestHeaderViewStyle old_style;
995 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
996 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
999 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1000 if (priv->style == style)
1001 return TRUE; /* nothing to do */
1004 case MODEST_HEADER_VIEW_STYLE_DETAILS:
1005 show_col_headers = TRUE;
1007 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1010 g_return_val_if_reached (FALSE);
1012 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1013 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1015 old_style = priv->style;
1016 priv->style = style;
1022 ModestHeaderViewStyle
1023 modest_header_view_get_style (ModestHeaderView *self)
1025 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1027 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1030 /* This is used to automatically select the first header if the user
1031 * has not selected any header yet.
1034 modest_header_view_on_expose_event(GtkTreeView *header_view,
1035 GdkEventExpose *event,
1038 GtkTreeSelection *sel;
1039 GtkTreeModel *model;
1040 GtkTreeIter tree_iter;
1041 ModestHeaderViewPrivate *priv;
1043 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1044 model = gtk_tree_view_get_model(header_view);
1049 #ifdef MODEST_TOOLKIT_HILDON2
1052 sel = gtk_tree_view_get_selection(header_view);
1053 if(!gtk_tree_selection_count_selected_rows(sel)) {
1054 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1055 GtkTreePath *tree_iter_path;
1056 /* Prevent the widget from getting the focus
1057 when selecting the first item */
1058 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1059 g_object_set(header_view, "can-focus", FALSE, NULL);
1060 gtk_tree_selection_select_iter(sel, &tree_iter);
1061 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1062 g_object_set(header_view, "can-focus", TRUE, NULL);
1063 if (priv->autoselect_reference) {
1064 gtk_tree_row_reference_free (priv->autoselect_reference);
1066 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1067 gtk_tree_path_free (tree_iter_path);
1070 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1071 gboolean moved_selection = FALSE;
1072 GtkTreePath * last_path;
1073 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1074 moved_selection = TRUE;
1078 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1079 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1080 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1081 moved_selection = TRUE;
1082 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1084 gtk_tree_path_free (last_path);
1086 if (moved_selection) {
1087 gtk_tree_row_reference_free (priv->autoselect_reference);
1088 priv->autoselect_reference = NULL;
1091 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1092 GtkTreePath *current_path;
1093 current_path = gtk_tree_model_get_path (model, &tree_iter);
1094 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1095 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1096 g_object_set(header_view, "can-focus", FALSE, NULL);
1097 gtk_tree_selection_unselect_all (sel);
1098 gtk_tree_selection_select_iter(sel, &tree_iter);
1099 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1100 g_object_set(header_view, "can-focus", TRUE, NULL);
1101 gtk_tree_row_reference_free (priv->autoselect_reference);
1102 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1104 gtk_tree_path_free (current_path);
1105 gtk_tree_path_free (last_path);
1115 modest_header_view_get_folder (ModestHeaderView *self)
1117 ModestHeaderViewPrivate *priv;
1119 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1121 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1124 g_object_ref (priv->folder);
1126 return priv->folder;
1130 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1136 ModestHeaderView *self;
1137 ModestHeaderViewPrivate *priv;
1139 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1141 self = MODEST_HEADER_VIEW (user_data);
1142 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1144 if (cancelled || err)
1147 /* Add IDLE observer (monitor) and another folder observer for
1148 new messages (self) */
1149 g_mutex_lock (priv->observers_lock);
1150 if (priv->monitor) {
1151 tny_folder_monitor_stop (priv->monitor);
1152 g_object_unref (G_OBJECT (priv->monitor));
1154 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1155 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1156 tny_folder_monitor_start (priv->monitor);
1157 g_mutex_unlock (priv->observers_lock);
1161 modest_header_view_set_folder_intern (ModestHeaderView *self,
1167 ModestHeaderViewPrivate *priv;
1168 GList *cols, *cursor;
1169 GtkTreeModel *filter_model, *sortable;
1171 GtkSortType sort_type;
1173 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1175 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1176 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (headers), priv->show_latest);
1178 /* Start the monitor in the callback of the
1179 tny_gtk_header_list_model_set_folder call. It's crucial to
1180 do it there and not just after the call because we want the
1181 monitor to observe only the headers returned by the
1182 tny_folder_get_headers_async call that it's inside the
1183 tny_gtk_header_list_model_set_folder call. This way the
1184 monitor infrastructure could successfully cope with
1185 duplicates. For example if a tny_folder_add_msg_async is
1186 happening while tny_gtk_header_list_model_set_folder is
1187 invoked, then the first call could add a header that will
1188 be added again by tny_gtk_header_list_model_set_folder, so
1189 we'd end up with duplicate headers. sergio */
1190 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1192 set_folder_intern_get_headers_async_cb,
1195 /* Create a tree model filter to hide and show rows for cut operations */
1196 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (headers), NULL);
1197 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1198 filter_row, self, NULL);
1199 g_object_unref (headers);
1201 /* Init filter_row function to examine empty status */
1202 priv->status = HEADER_VIEW_INIT;
1204 /* Create sortable model */
1205 sortable = gtk_tree_model_sort_new_with_model (filter_model);
1206 g_object_unref (filter_model);
1208 /* install our special sorting functions */
1209 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1211 /* Restore sort column id */
1213 type = modest_tny_folder_guess_folder_type (folder);
1214 if (type == TNY_FOLDER_TYPE_INVALID)
1215 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1217 sort_colid = modest_header_view_get_sort_column_id (self, type);
1218 sort_type = modest_header_view_get_sort_type (self, type);
1219 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1222 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1223 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1224 (GtkTreeIterCompareFunc) cmp_rows,
1226 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1227 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1228 (GtkTreeIterCompareFunc) cmp_subject_rows,
1233 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
1234 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1235 g_object_unref (sortable);
1242 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1244 GtkSortType sort_type)
1246 ModestHeaderViewPrivate *priv = NULL;
1247 GtkTreeModel *sortable = NULL;
1250 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1251 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1253 /* Get model and private data */
1254 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1255 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1257 /* Sort tree model */
1258 type = modest_tny_folder_guess_folder_type (priv->folder);
1259 if (type == TNY_FOLDER_TYPE_INVALID)
1260 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1262 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1265 /* Store new sort parameters */
1266 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1271 modest_header_view_set_sort_params (ModestHeaderView *self,
1273 GtkSortType sort_type,
1276 ModestHeaderViewPrivate *priv;
1277 ModestHeaderViewStyle style;
1279 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1280 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1281 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1283 style = modest_header_view_get_style (self);
1284 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1286 priv->sort_colid[style][type] = sort_colid;
1287 priv->sort_type[style][type] = sort_type;
1291 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1294 ModestHeaderViewPrivate *priv;
1295 ModestHeaderViewStyle style;
1297 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1298 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1300 style = modest_header_view_get_style (self);
1301 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1303 return priv->sort_colid[style][type];
1307 modest_header_view_get_sort_type (ModestHeaderView *self,
1310 ModestHeaderViewPrivate *priv;
1311 ModestHeaderViewStyle style;
1313 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1314 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1316 style = modest_header_view_get_style (self);
1317 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1319 return priv->sort_type[style][type];
1323 ModestHeaderView *header_view;
1324 RefreshAsyncUserCallback cb;
1329 folder_refreshed_cb (ModestMailOperation *mail_op,
1333 ModestHeaderViewPrivate *priv;
1334 SetFolderHelper *info;
1336 info = (SetFolderHelper*) user_data;
1338 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1342 info->cb (mail_op, folder, info->user_data);
1344 /* Start the folder count changes observer. We do not need it
1345 before the refresh. Note that the monitor could still be
1346 called for this refresh but now we know that the callback
1347 was previously called */
1348 g_mutex_lock (priv->observers_lock);
1349 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1350 g_mutex_unlock (priv->observers_lock);
1352 /* Notify the observers that the update is over */
1353 g_signal_emit (G_OBJECT (info->header_view),
1354 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1356 /* Allow filtering notifications from now on if the current
1357 folder is still the same (if not then the user has selected
1358 another one to refresh, we should wait until that refresh
1360 if (priv->folder == folder)
1361 priv->notify_status = TRUE;
1364 g_object_unref (info->header_view);
1369 refresh_folder_error_handler (ModestMailOperation *mail_op,
1372 const GError *error = modest_mail_operation_get_error (mail_op);
1374 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1375 error->code == TNY_IO_ERROR_WRITE ||
1376 error->code == TNY_IO_ERROR_READ) {
1377 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1378 /* If the mail op has been cancelled then it's not an error: don't show any message */
1379 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1380 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1381 modest_platform_information_banner (NULL, NULL, msg);
1388 modest_header_view_set_folder (ModestHeaderView *self,
1391 ModestWindow *progress_window,
1392 RefreshAsyncUserCallback callback,
1395 ModestHeaderViewPrivate *priv;
1397 g_return_if_fail (self);
1399 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1402 if (priv->status_timeout) {
1403 g_source_remove (priv->status_timeout);
1404 priv->status_timeout = 0;
1407 g_mutex_lock (priv->observers_lock);
1408 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1409 g_object_unref (priv->folder);
1410 priv->folder = NULL;
1411 g_mutex_unlock (priv->observers_lock);
1415 GtkTreeSelection *selection;
1416 SetFolderHelper *info;
1417 ModestMailOperation *mail_op = NULL;
1419 /* Set folder in the model */
1420 modest_header_view_set_folder_intern (self, folder, refresh);
1422 /* Pick my reference. Nothing to do with the mail operation */
1423 priv->folder = g_object_ref (folder);
1425 /* Do not notify about filterings until the refresh finishes */
1426 priv->notify_status = FALSE;
1428 /* Clear the selection if exists */
1429 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1430 gtk_tree_selection_unselect_all(selection);
1431 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1433 /* Notify the observers that the update begins */
1434 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1437 /* create the helper */
1438 info = g_malloc0 (sizeof (SetFolderHelper));
1439 info->header_view = g_object_ref (self);
1440 info->cb = callback;
1441 info->user_data = user_data;
1443 /* Create the mail operation (source will be the parent widget) */
1444 if (progress_window)
1445 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1446 refresh_folder_error_handler,
1449 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1452 /* Refresh the folder asynchronously */
1453 modest_mail_operation_refresh_folder (mail_op,
1455 folder_refreshed_cb,
1458 folder_refreshed_cb (mail_op, folder, info);
1462 g_object_unref (mail_op);
1464 g_mutex_lock (priv->observers_lock);
1466 if (priv->monitor) {
1467 tny_folder_monitor_stop (priv->monitor);
1468 g_object_unref (G_OBJECT (priv->monitor));
1469 priv->monitor = NULL;
1472 if (priv->autoselect_reference) {
1473 gtk_tree_row_reference_free (priv->autoselect_reference);
1474 priv->autoselect_reference = NULL;
1477 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1479 modest_header_view_notify_observers(self, NULL, NULL);
1481 g_mutex_unlock (priv->observers_lock);
1483 /* Notify the observers that the update is over */
1484 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1490 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1491 GtkTreeViewColumn *column, gpointer userdata)
1493 ModestHeaderView *self = NULL;
1495 GtkTreeModel *model = NULL;
1496 TnyHeader *header = NULL;
1497 TnyHeaderFlags flags;
1499 self = MODEST_HEADER_VIEW (treeview);
1501 model = gtk_tree_view_get_model (treeview);
1502 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1505 /* get the first selected item */
1506 gtk_tree_model_get (model, &iter,
1507 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1508 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1511 /* Dont open DELETED messages */
1512 if (flags & TNY_HEADER_FLAG_DELETED) {
1515 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1516 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1517 modest_platform_information_banner (NULL, NULL, msg);
1523 g_signal_emit (G_OBJECT(self),
1524 signals[HEADER_ACTIVATED_SIGNAL],
1530 g_object_unref (G_OBJECT (header));
1535 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1537 GtkTreeModel *model;
1538 TnyHeader *header = NULL;
1539 GtkTreePath *path = NULL;
1541 ModestHeaderView *self;
1542 GList *selected = NULL;
1544 g_return_if_fail (sel);
1545 g_return_if_fail (user_data);
1547 self = MODEST_HEADER_VIEW (user_data);
1549 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1550 if (selected != NULL)
1551 path = (GtkTreePath *) selected->data;
1552 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1553 return; /* msg was _un_selected */
1555 gtk_tree_model_get (model, &iter,
1556 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1560 g_signal_emit (G_OBJECT(self),
1561 signals[HEADER_SELECTED_SIGNAL],
1564 g_object_unref (G_OBJECT (header));
1566 /* free all items in 'selected' */
1567 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1568 g_list_free (selected);
1572 /* PROTECTED method. It's useful when we want to force a given
1573 selection to reload a msg. For example if we have selected a header
1574 in offline mode, when Modest become online, we want to reload the
1575 message automatically without an user click over the header */
1577 _modest_header_view_change_selection (GtkTreeSelection *selection,
1580 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1581 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1583 on_selection_changed (selection, user_data);
1587 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1594 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1598 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1602 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1610 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1618 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1619 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1623 case TNY_HEADER_FLAG_ATTACHMENTS:
1625 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1626 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1627 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1628 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1630 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1631 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1633 return cmp ? cmp : t1 - t2;
1635 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1636 TnyHeader *header1 = NULL, *header2 = NULL;
1638 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1639 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1640 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1641 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1643 /* This is for making priority values respect the intuitive sort relationship
1644 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1646 if (header1 && header2) {
1647 cmp = compare_priorities (tny_header_get_priority (header1),
1648 tny_header_get_priority (header2));
1649 g_object_unref (header1);
1650 g_object_unref (header2);
1652 return cmp ? cmp : t1 - t2;
1658 return &iter1 - &iter2; /* oughhhh */
1663 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1670 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1672 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1673 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1674 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1675 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1677 /* Do not use the prefixes for sorting. Consume all the blank
1678 spaces for sorting */
1679 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1680 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1683 /* If they're equal based on subject without prefix then just
1684 sort them by length. This will show messages like this.
1691 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1698 /* Drag and drop stuff */
1700 drag_data_get_cb (GtkWidget *widget,
1701 GdkDragContext *context,
1702 GtkSelectionData *selection_data,
1707 ModestHeaderView *self = NULL;
1708 ModestHeaderViewPrivate *priv = NULL;
1710 self = MODEST_HEADER_VIEW (widget);
1711 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1713 /* Set the data. Do not use the current selection because it
1714 could be different than the selection at the beginning of
1716 modest_dnd_selection_data_set_paths (selection_data,
1717 priv->drag_begin_cached_selected_rows);
1721 * We're caching the selected rows at the beginning because the
1722 * selection could change between drag-begin and drag-data-get, for
1723 * example if we have a set of rows already selected, and then we
1724 * click in one of them (without SHIFT key pressed) and begin a drag,
1725 * the selection at that moment contains all the selected lines, but
1726 * after dropping the selection, the release event provokes that only
1727 * the row used to begin the drag is selected, so at the end the
1728 * drag&drop affects only one rows instead of all the selected ones.
1732 drag_begin_cb (GtkWidget *widget,
1733 GdkDragContext *context,
1736 ModestHeaderView *self = NULL;
1737 ModestHeaderViewPrivate *priv = NULL;
1738 GtkTreeSelection *selection;
1740 self = MODEST_HEADER_VIEW (widget);
1741 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1743 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1744 priv->drag_begin_cached_selected_rows =
1745 gtk_tree_selection_get_selected_rows (selection, NULL);
1749 * We use the drag-end signal to clear the cached selection, we use
1750 * this because this allways happens, whether or not the d&d was a
1754 drag_end_cb (GtkWidget *widget,
1758 ModestHeaderView *self = NULL;
1759 ModestHeaderViewPrivate *priv = NULL;
1761 self = MODEST_HEADER_VIEW (widget);
1762 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1764 /* Free cached data */
1765 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1766 g_list_free (priv->drag_begin_cached_selected_rows);
1767 priv->drag_begin_cached_selected_rows = NULL;
1770 /* Header view drag types */
1771 const GtkTargetEntry header_view_drag_types[] = {
1772 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1776 enable_drag_and_drop (GtkWidget *self)
1778 #ifdef MODEST_TOOLKIT_HILDON2
1781 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1782 header_view_drag_types,
1783 G_N_ELEMENTS (header_view_drag_types),
1784 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1788 disable_drag_and_drop (GtkWidget *self)
1790 #ifdef MODEST_TOOLKIT_HILDON2
1793 gtk_drag_source_unset (self);
1797 setup_drag_and_drop (GtkWidget *self)
1799 #ifdef MODEST_TOOLKIT_HILDON2
1802 enable_drag_and_drop(self);
1803 g_signal_connect(G_OBJECT (self), "drag_data_get",
1804 G_CALLBACK(drag_data_get_cb), NULL);
1806 g_signal_connect(G_OBJECT (self), "drag_begin",
1807 G_CALLBACK(drag_begin_cb), NULL);
1809 g_signal_connect(G_OBJECT (self), "drag_end",
1810 G_CALLBACK(drag_end_cb), NULL);
1813 static GtkTreePath *
1814 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1816 GtkTreePath *path = NULL;
1817 GtkTreeSelection *sel = NULL;
1820 sel = gtk_tree_view_get_selection(self);
1821 rows = gtk_tree_selection_get_selected_rows (sel, model);
1823 if ((rows == NULL) || (g_list_length(rows) != 1))
1826 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1831 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1837 #ifndef MODEST_TOOLKIT_HILDON2
1839 * This function moves the tree view scroll to the current selected
1840 * row when the widget grabs the focus
1843 on_focus_in (GtkWidget *self,
1844 GdkEventFocus *event,
1847 GtkTreeSelection *selection;
1848 GtkTreeModel *model;
1849 GList *selected = NULL;
1850 GtkTreePath *selected_path = NULL;
1852 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1856 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1857 /* If none selected yet, pick the first one */
1858 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1862 /* Return if the model is empty */
1863 if (!gtk_tree_model_get_iter_first (model, &iter))
1866 path = gtk_tree_model_get_path (model, &iter);
1867 gtk_tree_selection_select_path (selection, path);
1868 gtk_tree_path_free (path);
1871 /* Need to get the all the rows because is selection multiple */
1872 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1873 if (selected == NULL) return FALSE;
1874 selected_path = (GtkTreePath *) selected->data;
1877 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1878 g_list_free (selected);
1884 on_focus_out (GtkWidget *self,
1885 GdkEventFocus *event,
1889 if (!gtk_widget_is_focus (self)) {
1890 GtkTreeSelection *selection = NULL;
1891 GList *selected_rows = NULL;
1892 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1893 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1894 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1895 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1896 gtk_tree_selection_unselect_all (selection);
1897 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1898 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1899 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1900 g_list_free (selected_rows);
1908 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1910 enable_drag_and_drop(self);
1915 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1917 GtkTreeSelection *selection = NULL;
1918 GtkTreePath *path = NULL;
1919 gboolean already_selected = FALSE, already_opened = FALSE;
1920 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1922 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1924 GtkTreeModel *model;
1926 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1927 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1929 /* Get header from model */
1930 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1931 if (gtk_tree_model_get_iter (model, &iter, path)) {
1932 GValue value = {0,};
1935 gtk_tree_model_get_value (model, &iter,
1936 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1938 header = (TnyHeader *) g_value_get_object (&value);
1939 if (TNY_IS_HEADER (header)) {
1940 status = modest_tny_all_send_queues_get_msg_status (header);
1941 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1944 g_value_unset (&value);
1948 /* Enable drag and drop only if the user clicks on a row that
1949 it's already selected. If not, let him select items using
1950 the pointer. If the message is in an OUTBOX and in sending
1951 status disable drag and drop as well */
1952 if (!already_selected ||
1953 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1955 disable_drag_and_drop(self);
1958 gtk_tree_path_free(path);
1960 /* If it's already opened then do not let the button-press
1961 event go on because it'll perform a message open because
1962 we're clicking on to an already selected header */
1967 folder_monitor_update (TnyFolderObserver *self,
1968 TnyFolderChange *change)
1970 ModestHeaderViewPrivate *priv = NULL;
1971 TnyFolderChangeChanged changed;
1972 TnyFolder *folder = NULL;
1974 changed = tny_folder_change_get_changed (change);
1976 /* Do not notify the observers if the folder of the header
1977 view has changed before this call to the observer
1979 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1980 folder = tny_folder_change_get_folder (change);
1981 if (folder != priv->folder)
1984 MODEST_DEBUG_BLOCK (
1985 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1986 g_print ("ADDED %d/%d (r/t) \n",
1987 tny_folder_change_get_new_unread_count (change),
1988 tny_folder_change_get_new_all_count (change));
1989 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1990 g_print ("ALL COUNT %d\n",
1991 tny_folder_change_get_new_all_count (change));
1992 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1993 g_print ("UNREAD COUNT %d\n",
1994 tny_folder_change_get_new_unread_count (change));
1995 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1996 g_print ("EXPUNGED %d/%d (r/t) \n",
1997 tny_folder_change_get_new_unread_count (change),
1998 tny_folder_change_get_new_all_count (change));
1999 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
2000 g_print ("FOLDER RENAME\n");
2001 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2002 g_print ("MSG RECEIVED %d/%d (r/t) \n",
2003 tny_folder_change_get_new_unread_count (change),
2004 tny_folder_change_get_new_all_count (change));
2005 g_print ("---------------------------------------------------\n");
2008 /* Check folder count */
2009 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2010 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2012 g_mutex_lock (priv->observers_lock);
2014 /* Emit signal to evaluate how headers changes affects
2015 to the window view */
2016 g_signal_emit (G_OBJECT(self),
2017 signals[MSG_COUNT_CHANGED_SIGNAL],
2020 /* Added or removed headers, so data stored on cliboard are invalid */
2021 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2022 modest_email_clipboard_clear (priv->clipboard);
2024 g_mutex_unlock (priv->observers_lock);
2030 g_object_unref (folder);
2034 modest_header_view_is_empty (ModestHeaderView *self)
2036 ModestHeaderViewPrivate *priv;
2038 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2040 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2042 return priv->status == HEADER_VIEW_EMPTY;
2046 modest_header_view_clear (ModestHeaderView *self)
2048 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2050 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2054 modest_header_view_copy_selection (ModestHeaderView *header_view)
2056 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2058 /* Copy selection */
2059 _clipboard_set_selected_data (header_view, FALSE);
2063 modest_header_view_cut_selection (ModestHeaderView *header_view)
2065 ModestHeaderViewPrivate *priv = NULL;
2066 const gchar **hidding = NULL;
2067 guint i, n_selected;
2069 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2071 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2073 /* Copy selection */
2074 _clipboard_set_selected_data (header_view, TRUE);
2076 /* Get hidding ids */
2077 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2079 /* Clear hidding array created by previous cut operation */
2080 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2082 /* Copy hidding array */
2083 priv->n_selected = n_selected;
2084 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2085 for (i=0; i < n_selected; i++)
2086 priv->hidding_ids[i] = g_strdup(hidding[i]);
2088 /* Hide cut headers */
2089 modest_header_view_refilter (header_view);
2096 _clipboard_set_selected_data (ModestHeaderView *header_view,
2099 ModestHeaderViewPrivate *priv = NULL;
2100 TnyList *headers = NULL;
2102 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2103 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2105 /* Set selected data on clipboard */
2106 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2107 headers = modest_header_view_get_selected_headers (header_view);
2108 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2111 g_object_unref (headers);
2115 ModestHeaderView *self;
2120 notify_filter_change (gpointer data)
2122 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2124 g_signal_emit (info->self,
2125 signals[MSG_COUNT_CHANGED_SIGNAL],
2126 0, info->folder, NULL);
2132 notify_filter_change_destroy (gpointer data)
2134 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2135 ModestHeaderViewPrivate *priv;
2137 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2138 priv->status_timeout = 0;
2140 g_object_unref (info->self);
2141 g_object_unref (info->folder);
2142 g_slice_free (NotifyFilterInfo, info);
2146 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2148 /* For the moment we only need to filter outbox */
2149 return priv->is_outbox;
2153 header_match_string (TnyHeader *header, gchar **words)
2160 gchar *subject_fold;
2166 gchar **current_word;
2169 subject = tny_header_dup_subject (header);
2170 cc = tny_header_dup_cc (header);
2171 bcc = tny_header_dup_bcc (header);
2172 to = tny_header_dup_to (header);
2173 from = tny_header_dup_from (header);
2175 subject_fold = g_utf8_casefold (subject, -1);
2177 bcc_fold = g_utf8_casefold (bcc, -1);
2179 cc_fold = g_utf8_casefold (cc, -1);
2181 to_fold = g_utf8_casefold (to, -1);
2183 from_fold = g_utf8_casefold (from, -1);
2188 for (current_word = words; *current_word != NULL; current_word++) {
2190 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2191 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2192 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2193 || (to && g_strstr_len (to_fold, -1, *current_word))
2194 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2202 g_free (subject_fold);
2212 filter_row (GtkTreeModel *model,
2216 ModestHeaderViewPrivate *priv = NULL;
2217 TnyHeaderFlags flags;
2218 TnyHeader *header = NULL;
2221 gboolean visible = TRUE;
2222 gboolean found = FALSE;
2223 GValue value = {0,};
2224 HeaderViewStatus old_status;
2226 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2227 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2229 /* Get header from model */
2230 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2231 flags = (TnyHeaderFlags) g_value_get_int (&value);
2232 g_value_unset (&value);
2233 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2234 header = (TnyHeader *) g_value_get_object (&value);
2235 g_value_unset (&value);
2237 /* Get message id from header (ensure is a valid id) */
2243 /* Hide deleted and mark as deleted heders */
2244 if (flags & TNY_HEADER_FLAG_DELETED ||
2245 flags & TNY_HEADER_FLAG_EXPUNGED) {
2250 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2251 if (current_folder_needs_filtering (priv) &&
2252 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2258 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2259 if (current_folder_needs_filtering (priv) &&
2260 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2266 if (visible && priv->filter_string) {
2267 if (!header_match_string (header, priv->filter_string_splitted)) {
2271 if (priv->filter_date_range) {
2272 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2273 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2280 /* If no data on clipboard, return always TRUE */
2281 if (modest_email_clipboard_cleared(priv->clipboard)) {
2287 if (priv->hidding_ids != NULL) {
2288 id = tny_header_dup_message_id (header);
2289 for (i=0; i < priv->n_selected && !found; i++)
2290 if (priv->hidding_ids[i] != NULL && id != NULL)
2291 found = (!strcmp (priv->hidding_ids[i], id));
2298 old_status = priv->status;
2299 priv->status = ((gboolean) priv->status) && !visible;
2300 if ((priv->notify_status) && (priv->status != old_status)) {
2301 if (priv->status_timeout)
2302 g_source_remove (priv->status_timeout);
2305 NotifyFilterInfo *info;
2307 info = g_slice_new0 (NotifyFilterInfo);
2308 info->self = g_object_ref (G_OBJECT (user_data));
2310 info->folder = tny_header_get_folder (header);
2311 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2312 notify_filter_change,
2314 notify_filter_change_destroy);
2322 _clear_hidding_filter (ModestHeaderView *header_view)
2324 ModestHeaderViewPrivate *priv = NULL;
2327 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2328 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2330 if (priv->hidding_ids != NULL) {
2331 for (i=0; i < priv->n_selected; i++)
2332 g_free (priv->hidding_ids[i]);
2333 g_free(priv->hidding_ids);
2338 modest_header_view_refilter (ModestHeaderView *header_view)
2340 GtkTreeModel *model, *sortable = NULL;
2341 ModestHeaderViewPrivate *priv = NULL;
2343 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2344 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2346 /* Hide cut headers */
2347 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2348 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2349 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2350 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2351 priv->status = HEADER_VIEW_INIT;
2352 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2358 * Called when an account is removed. If I'm showing a folder of the
2359 * account that has been removed then clear the view
2362 on_account_removed (TnyAccountStore *self,
2363 TnyAccount *account,
2366 ModestHeaderViewPrivate *priv = NULL;
2368 /* Ignore changes in transport accounts */
2369 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2372 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2375 TnyAccount *my_account;
2377 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2378 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2379 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2380 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2382 my_account = tny_folder_get_account (priv->folder);
2386 if (my_account == account)
2387 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2388 g_object_unref (my_account);
2394 modest_header_view_add_observer(ModestHeaderView *header_view,
2395 ModestHeaderViewObserver *observer)
2397 ModestHeaderViewPrivate *priv;
2399 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2400 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2402 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2404 g_mutex_lock(priv->observer_list_lock);
2405 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2406 g_mutex_unlock(priv->observer_list_lock);
2410 modest_header_view_remove_observer(ModestHeaderView *header_view,
2411 ModestHeaderViewObserver *observer)
2413 ModestHeaderViewPrivate *priv;
2415 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2416 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2418 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2420 g_mutex_lock(priv->observer_list_lock);
2421 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2422 g_mutex_unlock(priv->observer_list_lock);
2426 modest_header_view_notify_observers(ModestHeaderView *header_view,
2427 GtkTreeModel *model,
2428 const gchar *tny_folder_id)
2430 ModestHeaderViewPrivate *priv = NULL;
2432 ModestHeaderViewObserver *observer;
2435 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2437 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2439 g_mutex_lock(priv->observer_list_lock);
2440 iter = priv->observer_list;
2441 while(iter != NULL){
2442 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2443 modest_header_view_observer_update(observer, model,
2445 iter = g_slist_next(iter);
2447 g_mutex_unlock(priv->observer_list_lock);
2451 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2453 ModestHeaderViewPrivate *priv = NULL;
2455 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2456 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2460 modest_header_view_set_filter (ModestHeaderView *self,
2461 ModestHeaderViewFilter filter)
2463 ModestHeaderViewPrivate *priv;
2465 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2466 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2468 priv->filter |= filter;
2470 if (current_folder_needs_filtering (priv))
2471 modest_header_view_refilter (self);
2475 modest_header_view_unset_filter (ModestHeaderView *self,
2476 ModestHeaderViewFilter filter)
2478 ModestHeaderViewPrivate *priv;
2480 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2481 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2483 priv->filter &= ~filter;
2485 if (current_folder_needs_filtering (priv))
2486 modest_header_view_refilter (self);
2490 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2492 if (strcmp ("style", spec->name) == 0) {
2493 update_style (MODEST_HEADER_VIEW (obj));
2494 gtk_widget_queue_draw (GTK_WIDGET (obj));
2499 update_style (ModestHeaderView *self)
2501 ModestHeaderViewPrivate *priv;
2502 GdkColor style_color;
2503 GdkColor style_active_color;
2504 PangoAttrList *attr_list;
2506 PangoAttribute *attr;
2508 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2509 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2513 attr_list = pango_attr_list_new ();
2514 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
2515 gdk_color_parse ("grey", &style_color);
2517 priv->secondary_color = style_color;
2518 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2519 pango_attr_list_insert (attr_list, attr);
2522 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2524 "SmallSystemFont", NULL,
2527 attr = pango_attr_font_desc_new (pango_font_description_copy
2528 (style->font_desc));
2529 pango_attr_list_insert (attr_list, attr);
2531 g_object_set (G_OBJECT (priv->renderer_address),
2532 "foreground-gdk", &(priv->secondary_color),
2533 "foreground-set", TRUE,
2534 "attributes", attr_list,
2536 g_object_set (G_OBJECT (priv->renderer_date_status),
2537 "foreground-gdk", &(priv->secondary_color),
2538 "foreground-set", TRUE,
2539 "attributes", attr_list,
2541 pango_attr_list_unref (attr_list);
2543 g_object_set (G_OBJECT (priv->renderer_address),
2544 "foreground-gdk", &(priv->secondary_color),
2545 "foreground-set", TRUE,
2546 "scale", PANGO_SCALE_SMALL,
2549 g_object_set (G_OBJECT (priv->renderer_date_status),
2550 "foreground-gdk", &(priv->secondary_color),
2551 "foreground-set", TRUE,
2552 "scale", PANGO_SCALE_SMALL,
2557 if (gtk_style_lookup_color (GTK_WIDGET (self)->style, "ActiveTextColor", &style_active_color)) {
2558 priv->active_color = style_active_color;
2559 #ifdef MODEST_TOOLKIT_HILDON2
2560 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2561 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2564 #ifdef MODEST_TOOLKIT_HILDON2
2565 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2571 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2576 GtkTreeModel *tree_model;
2581 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2589 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2590 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2594 gtk_tree_model_get (tree_model, &iter,
2595 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2602 parse_date_side (const gchar *string, time_t *date_side)
2608 gboolean result = FALSE;
2610 if (string && string[0] == '\0') {
2615 casefold = g_utf8_casefold (string, -1);
2616 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2617 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2618 date = g_date_new ();
2620 if (g_utf8_collate (casefold, today) == 0) {
2621 *date_side = time (NULL);
2626 if (g_utf8_collate (casefold, yesterday) == 0) {
2627 *date_side = time (NULL) - 24*60*60;
2632 g_date_set_parse (date, string);
2633 if (g_date_valid (date)) {
2635 g_date_to_struct_tm (date, &tm);
2636 *date_side = mktime (&tm);
2651 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2656 parts = g_strsplit (string, "..", 2);
2659 if (g_strv_length (parts) != 2) {
2666 if (!parse_date_side (parts[0], date_range_start)) {
2671 if (parse_date_side (parts[1], date_range_end)) {
2672 if (*date_range_end == 0) {
2673 *date_range_end = (time_t) -1;
2675 *date_range_end += (24*60*60 - 1);
2688 modest_header_view_set_show_latest (ModestHeaderView *header_view,
2691 ModestHeaderViewPrivate *priv;
2692 GtkTreeModel *sortable, *filter, *model;
2694 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2695 priv->show_latest = show_latest;
2697 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2698 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2699 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2700 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2701 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2703 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (model), priv->show_latest);
2710 modest_header_view_get_show_latest (ModestHeaderView *header_view)
2712 ModestHeaderViewPrivate *priv;
2713 GtkTreeModel *sortable, *filter, *model;
2716 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2718 result = priv->show_latest;
2719 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2720 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2721 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2722 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2723 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2725 result = tny_gtk_header_list_model_get_show_latest (TNY_GTK_HEADER_LIST_MODEL (model));
2734 modest_header_view_get_not_latest (ModestHeaderView *header_view)
2736 ModestHeaderViewPrivate *priv;
2737 gint not_latest = 0;
2738 GtkTreeModel *sortable, *filter, *model;
2740 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2742 if (priv->show_latest == 0)
2745 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2746 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2747 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2748 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2749 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2751 not_latest = MAX (0, tny_list_get_length (TNY_LIST (model)) - priv->show_latest);
2760 modest_header_view_set_filter_string (ModestHeaderView *self,
2761 const gchar *filter_string)
2763 ModestHeaderViewPrivate *priv;
2765 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2766 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2768 if (priv->filter_string)
2769 g_free (priv->filter_string);
2771 priv->filter_string = g_strdup (filter_string);
2772 priv->filter_date_range = FALSE;
2774 if (priv->filter_string_splitted) {
2775 g_strfreev (priv->filter_string_splitted);
2776 priv->filter_string_splitted = NULL;
2779 if (priv->filter_string) {
2780 gchar **split, **current, **current_target;
2782 split = g_strsplit (priv->filter_string, " ", 0);
2784 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2785 current_target = priv->filter_string_splitted;
2786 for (current = split; *current != 0; current ++) {
2787 gboolean has_date_range = FALSE;;
2788 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2789 time_t range_start, range_end;
2790 /* It contains .. but it's not ".." so it may be a date range */
2791 if (parse_date_range (*current, &range_start, &range_end)) {
2792 priv->filter_date_range = TRUE;
2793 has_date_range = TRUE;
2794 priv->date_range_start = range_start;
2795 priv->date_range_end = range_end;
2798 if (!has_date_range) {
2799 *current_target = g_utf8_casefold (*current, -1);
2803 *current_target = '\0';
2806 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2809 #ifdef MODEST_TOOLKIT_HILDON2
2812 on_live_search_timeout (ModestHeaderView *self)
2814 const gchar *needle;
2815 ModestHeaderViewPrivate *priv;
2817 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2819 needle = hildon_live_search_get_text (HILDON_LIVE_SEARCH (priv->live_search));
2820 if (needle && needle[0] != '\0') {
2821 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2822 if (priv->show_latest > 0)
2823 modest_header_view_set_show_latest (MODEST_HEADER_VIEW (self), 0);
2825 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2828 priv->live_search_timeout = 0;
2834 on_live_search_refilter (HildonLiveSearch *livesearch,
2835 ModestHeaderView *self)
2837 ModestHeaderViewPrivate *priv;
2838 GtkTreeModel *model, *sortable, *filter;
2840 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2842 if (priv->live_search_timeout > 0) {
2843 g_source_remove (priv->live_search_timeout);
2844 priv->live_search_timeout = 0;
2848 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2849 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2850 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2851 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2852 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2856 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2857 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2859 on_live_search_timeout (self);
2866 modest_header_view_setup_live_search (ModestHeaderView *self)
2868 ModestHeaderViewPrivate *priv;
2870 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2871 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2872 priv->live_search = hildon_live_search_new ();
2874 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2876 return priv->live_search;