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-ui-actions.h>
45 #include <modest-marshal.h>
46 #include <modest-text-utils.h>
47 #include <modest-icon-names.h>
48 #include <modest-runtime.h>
49 #include "modest-platform.h"
50 #include <modest-hbox-cell-renderer.h>
51 #include <modest-vbox-cell-renderer.h>
52 #include <modest-datetime-formatter.h>
53 #include <modest-ui-constants.h>
54 #ifdef MODEST_TOOLKIT_HILDON2
55 #include <hildon/hildon.h>
58 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
59 static void modest_header_view_init (ModestHeaderView *obj);
60 static void modest_header_view_finalize (GObject *obj);
61 static void modest_header_view_dispose (GObject *obj);
63 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
64 GtkTreeViewColumn *column, gpointer userdata);
66 static gint cmp_rows (GtkTreeModel *tree_model,
71 static gint cmp_subject_rows (GtkTreeModel *tree_model,
76 static gboolean filter_row (GtkTreeModel *model,
80 static void on_account_removed (TnyAccountStore *self,
84 static void on_selection_changed (GtkTreeSelection *sel,
87 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
90 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
93 static void setup_drag_and_drop (GtkWidget *self);
95 static void enable_drag_and_drop (GtkWidget *self);
97 static void disable_drag_and_drop (GtkWidget *self);
99 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
101 #ifndef MODEST_TOOLKIT_HILDON2
102 static gboolean on_focus_in (GtkWidget *sef,
103 GdkEventFocus *event,
106 static gboolean on_focus_out (GtkWidget *self,
107 GdkEventFocus *event,
111 static void folder_monitor_update (TnyFolderObserver *self,
112 TnyFolderChange *change);
114 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
116 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
118 static void _clear_hidding_filter (ModestHeaderView *header_view);
120 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
122 const gchar *tny_folder_id);
124 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
125 GdkEventExpose *event,
128 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
129 static void update_style (ModestHeaderView *self);
132 HEADER_VIEW_NON_EMPTY,
137 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
138 struct _ModestHeaderViewPrivate {
140 ModestHeaderViewStyle style;
143 TnyFolderMonitor *monitor;
144 GMutex *observers_lock;
146 /*header-view-observer observer*/
147 GMutex *observer_list_lock;
148 GSList *observer_list;
150 /* not unref this object, its a singlenton */
151 ModestEmailClipboard *clipboard;
153 /* Filter tree model */
156 GtkTreeRowReference *autoselect_reference;
157 ModestHeaderViewFilter filter;
158 #ifdef MODEST_TOOLKIT_HILDON2
159 GtkWidget *live_search;
160 guint live_search_timeout;
163 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
164 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
166 gulong selection_changed_handler;
167 gulong acc_removed_handler;
169 GList *drag_begin_cached_selected_rows;
171 HeaderViewStatus status;
172 guint status_timeout;
173 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
175 ModestDatetimeFormatter *datetime_formatter;
177 GtkCellRenderer *renderer_subject;
178 GtkCellRenderer *renderer_address;
179 GtkCellRenderer *renderer_date_status;
181 GdkColor active_color;
182 GdkColor secondary_color;
184 gchar *filter_string;
185 gchar **filter_string_splitted;
186 gboolean filter_date_range;
187 time_t date_range_start;
188 time_t date_range_end;
191 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
192 struct _HeadersCountChangedHelper {
193 ModestHeaderView *self;
194 TnyFolderChange *change;
198 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
199 MODEST_TYPE_HEADER_VIEW, \
200 ModestHeaderViewPrivate))
204 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
206 #define _HEADER_VIEW_SUBJECT_FOLD "_subject_modest_header_view"
207 #define _HEADER_VIEW_FROM_FOLD "_from_modest_header_view"
208 #define _HEADER_VIEW_TO_FOLD "_to_modest_header_view"
209 #define _HEADER_VIEW_CC_FOLD "_cc_modest_header_view"
210 #define _HEADER_VIEW_BCC_FOLD "_bcc_modest_header_view"
213 HEADER_SELECTED_SIGNAL,
214 HEADER_ACTIVATED_SIGNAL,
215 ITEM_NOT_FOUND_SIGNAL,
216 MSG_COUNT_CHANGED_SIGNAL,
217 UPDATING_MSG_LIST_SIGNAL,
222 static GObjectClass *parent_class = NULL;
224 /* uncomment the following if you have defined any signals */
225 static guint signals[LAST_SIGNAL] = {0};
228 modest_header_view_get_type (void)
230 static GType my_type = 0;
232 static const GTypeInfo my_info = {
233 sizeof(ModestHeaderViewClass),
234 NULL, /* base init */
235 NULL, /* base finalize */
236 (GClassInitFunc) modest_header_view_class_init,
237 NULL, /* class finalize */
238 NULL, /* class data */
239 sizeof(ModestHeaderView),
241 (GInstanceInitFunc) modest_header_view_init,
245 static const GInterfaceInfo tny_folder_observer_info =
247 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
248 NULL, /* interface_finalize */
249 NULL /* interface_data */
251 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
255 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
256 &tny_folder_observer_info);
264 modest_header_view_class_init (ModestHeaderViewClass *klass)
266 GObjectClass *gobject_class;
267 gobject_class = (GObjectClass*) klass;
269 parent_class = g_type_class_peek_parent (klass);
270 gobject_class->finalize = modest_header_view_finalize;
271 gobject_class->dispose = modest_header_view_dispose;
273 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
275 signals[HEADER_SELECTED_SIGNAL] =
276 g_signal_new ("header_selected",
277 G_TYPE_FROM_CLASS (gobject_class),
279 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
281 g_cclosure_marshal_VOID__POINTER,
282 G_TYPE_NONE, 1, G_TYPE_POINTER);
284 signals[HEADER_ACTIVATED_SIGNAL] =
285 g_signal_new ("header_activated",
286 G_TYPE_FROM_CLASS (gobject_class),
288 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
290 gtk_marshal_VOID__POINTER_POINTER,
291 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
294 signals[ITEM_NOT_FOUND_SIGNAL] =
295 g_signal_new ("item_not_found",
296 G_TYPE_FROM_CLASS (gobject_class),
298 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
300 g_cclosure_marshal_VOID__INT,
301 G_TYPE_NONE, 1, G_TYPE_INT);
303 signals[MSG_COUNT_CHANGED_SIGNAL] =
304 g_signal_new ("msg_count_changed",
305 G_TYPE_FROM_CLASS (gobject_class),
307 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
309 modest_marshal_VOID__POINTER_POINTER,
310 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
312 signals[UPDATING_MSG_LIST_SIGNAL] =
313 g_signal_new ("updating-msg-list",
314 G_TYPE_FROM_CLASS (gobject_class),
316 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
318 g_cclosure_marshal_VOID__BOOLEAN,
319 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
321 #ifdef MODEST_TOOLKIT_HILDON2
322 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
328 tny_folder_observer_init (TnyFolderObserverIface *klass)
330 klass->update = folder_monitor_update;
333 static GtkTreeViewColumn*
334 get_new_column (const gchar *name, GtkCellRenderer *renderer,
335 gboolean resizable, gint sort_col_id, gboolean show_as_text,
336 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
338 GtkTreeViewColumn *column;
340 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
341 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
343 gtk_tree_view_column_set_resizable (column, resizable);
345 gtk_tree_view_column_set_expand (column, TRUE);
348 gtk_tree_view_column_add_attribute (column, renderer, "text",
350 if (sort_col_id >= 0)
351 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
353 gtk_tree_view_column_set_sort_indicator (column, FALSE);
354 gtk_tree_view_column_set_reorderable (column, TRUE);
357 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
364 remove_all_columns (ModestHeaderView *obj)
366 GList *columns, *cursor;
368 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
370 for (cursor = columns; cursor; cursor = cursor->next)
371 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
372 GTK_TREE_VIEW_COLUMN(cursor->data));
373 g_list_free (columns);
377 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
379 GtkTreeModel *sortable, *filter_model;
380 GtkTreeViewColumn *column=NULL;
381 GtkTreeSelection *selection = NULL;
382 GtkCellRenderer *renderer_header,
383 *renderer_attach, *renderer_compact_date_or_status;
384 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
385 *renderer_subject_box, *renderer_recpt,
387 ModestHeaderViewPrivate *priv;
388 GtkTreeViewColumn *compact_column = NULL;
391 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
392 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
394 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
396 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
398 /* TODO: check whether these renderers need to be freed */
399 renderer_attach = gtk_cell_renderer_pixbuf_new ();
400 renderer_priority = gtk_cell_renderer_pixbuf_new ();
401 renderer_header = gtk_cell_renderer_text_new ();
403 renderer_compact_header = modest_vbox_cell_renderer_new ();
404 renderer_recpt_box = modest_hbox_cell_renderer_new ();
405 renderer_subject_box = modest_hbox_cell_renderer_new ();
406 renderer_recpt = gtk_cell_renderer_text_new ();
407 priv->renderer_address = renderer_recpt;
408 priv->renderer_subject = gtk_cell_renderer_text_new ();
409 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
410 priv->renderer_date_status = renderer_compact_date_or_status;
412 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
413 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
414 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
415 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
416 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
417 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
418 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
419 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
420 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
421 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
422 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
423 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
424 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
425 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
427 #ifdef MODEST_TOOLKIT_HILDON2
428 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
430 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
431 #ifndef MODEST_TOOLKIT_GTK
432 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
433 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
435 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
436 g_object_set(G_OBJECT(renderer_header),
437 "ellipsize", PANGO_ELLIPSIZE_END,
439 g_object_set (G_OBJECT (priv->renderer_subject),
440 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
442 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
443 g_object_set (G_OBJECT (renderer_recpt),
444 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
446 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
447 g_object_set(G_OBJECT(renderer_compact_date_or_status),
448 "xalign", 1.0, "yalign", 0.1,
450 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
451 #ifdef MODEST_TOOLKIT_HILDON2
452 g_object_set (G_OBJECT (renderer_priority),
454 "xalign", 0.0, NULL);
455 g_object_set (G_OBJECT (renderer_attach),
457 "xalign", 0.0, NULL);
459 g_object_set (G_OBJECT (renderer_priority),
460 "yalign", 0.5, NULL);
461 g_object_set (G_OBJECT (renderer_attach),
462 "yalign", 0.0, NULL);
465 #ifdef MODEST_TOOLKIT_HILDON1
466 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
467 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
468 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
469 #elif MODEST_TOOLKIT_HILDON2
470 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
471 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
472 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
474 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
475 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
476 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
479 remove_all_columns (self);
481 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
482 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
484 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
485 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
486 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
489 /* Add new columns */
490 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
491 ModestHeaderViewColumn col =
492 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
494 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
495 g_printerr ("modest: invalid column %d in column list\n", col);
501 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
502 column = get_new_column (_("A"), renderer_attach, FALSE,
503 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
505 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
507 gtk_tree_view_column_set_fixed_width (column, 45);
511 case MODEST_HEADER_VIEW_COLUMN_FROM:
512 column = get_new_column (_("From"), renderer_header, TRUE,
513 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
515 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
516 GINT_TO_POINTER(TRUE));
519 case MODEST_HEADER_VIEW_COLUMN_TO:
520 column = get_new_column (_("To"), renderer_header, TRUE,
521 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
523 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
524 GINT_TO_POINTER(FALSE));
527 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
528 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
529 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
531 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
532 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
533 compact_column = column;
536 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
537 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
538 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
540 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
541 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
542 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
543 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
544 compact_column = column;
548 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
549 column = get_new_column (_("Subject"), renderer_header, TRUE,
550 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
552 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
556 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
557 column = get_new_column (_("Received"), renderer_header, TRUE,
558 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
560 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
561 GINT_TO_POINTER(TRUE));
564 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
565 column = get_new_column (_("Sent"), renderer_header, TRUE,
566 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
568 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
569 GINT_TO_POINTER(FALSE));
572 case MODEST_HEADER_VIEW_COLUMN_SIZE:
573 column = get_new_column (_("Size"), renderer_header, TRUE,
574 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
576 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
579 case MODEST_HEADER_VIEW_COLUMN_STATUS:
580 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
581 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
583 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
588 g_return_val_if_reached(FALSE);
591 /* we keep the column id around */
592 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
593 GINT_TO_POINTER(col));
595 /* we need this ptr when sorting the rows */
596 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
598 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
602 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
603 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
604 (GtkTreeIterCompareFunc) cmp_rows,
605 compact_column, NULL);
606 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
607 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
608 (GtkTreeIterCompareFunc) cmp_subject_rows,
609 compact_column, NULL);
613 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
619 datetime_format_changed (ModestDatetimeFormatter *formatter,
620 ModestHeaderView *self)
622 gtk_widget_queue_draw (GTK_WIDGET (self));
626 modest_header_view_init (ModestHeaderView *obj)
628 ModestHeaderViewPrivate *priv;
631 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
634 priv->is_outbox = FALSE;
636 priv->monitor = NULL;
637 priv->observers_lock = g_mutex_new ();
638 priv->autoselect_reference = NULL;
640 priv->status = HEADER_VIEW_INIT;
641 priv->status_timeout = 0;
642 priv->notify_status = TRUE;
644 priv->observer_list_lock = g_mutex_new();
645 priv->observer_list = NULL;
647 priv->clipboard = modest_runtime_get_email_clipboard ();
648 priv->hidding_ids = NULL;
649 priv->n_selected = 0;
650 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
651 #ifdef MODEST_TOOLKIT_HILDON2
652 priv->live_search = NULL;
654 priv->filter_string = NULL;
655 priv->filter_string_splitted = NULL;
656 priv->filter_date_range = FALSE;
657 priv->selection_changed_handler = 0;
658 priv->acc_removed_handler = 0;
660 /* Sort parameters */
661 for (j=0; j < 2; j++) {
662 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
663 priv->sort_colid[j][i] = -1;
664 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
668 priv->datetime_formatter = modest_datetime_formatter_new ();
669 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
670 G_CALLBACK (datetime_format_changed), (gpointer) obj);
672 setup_drag_and_drop (GTK_WIDGET(obj));
676 modest_header_view_dispose (GObject *obj)
678 ModestHeaderView *self;
679 ModestHeaderViewPrivate *priv;
680 GtkTreeSelection *sel;
682 self = MODEST_HEADER_VIEW(obj);
683 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
685 if (priv->datetime_formatter) {
686 g_object_unref (priv->datetime_formatter);
687 priv->datetime_formatter = NULL;
690 /* Free in the dispose to avoid unref cycles */
692 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
693 g_object_unref (G_OBJECT (priv->folder));
697 /* We need to do this here in the dispose because the
698 selection won't exist when finalizing */
699 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
700 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
701 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
702 priv->selection_changed_handler = 0;
705 G_OBJECT_CLASS(parent_class)->dispose (obj);
709 modest_header_view_finalize (GObject *obj)
711 ModestHeaderView *self;
712 ModestHeaderViewPrivate *priv;
714 self = MODEST_HEADER_VIEW(obj);
715 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
717 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
718 priv->acc_removed_handler)) {
719 g_signal_handler_disconnect (modest_runtime_get_account_store (),
720 priv->acc_removed_handler);
723 /* There is no need to lock because there should not be any
724 * reference to self now. */
725 g_mutex_free(priv->observer_list_lock);
726 g_slist_free(priv->observer_list);
728 g_mutex_lock (priv->observers_lock);
730 tny_folder_monitor_stop (priv->monitor);
731 g_object_unref (G_OBJECT (priv->monitor));
733 g_mutex_unlock (priv->observers_lock);
734 g_mutex_free (priv->observers_lock);
736 /* Clear hidding array created by cut operation */
737 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
739 if (priv->autoselect_reference != NULL) {
740 gtk_tree_row_reference_free (priv->autoselect_reference);
741 priv->autoselect_reference = NULL;
744 if (priv->filter_string) {
745 g_free (priv->filter_string);
748 if (priv->filter_string_splitted) {
749 g_strfreev (priv->filter_string_splitted);
752 G_OBJECT_CLASS(parent_class)->finalize (obj);
757 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
760 GtkTreeSelection *sel;
761 ModestHeaderView *self;
762 ModestHeaderViewPrivate *priv;
764 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
767 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
768 self = MODEST_HEADER_VIEW(obj);
769 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
771 modest_header_view_set_style (self, style);
773 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
774 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
775 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
777 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
778 TRUE); /* alternating row colors */
780 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
781 priv->selection_changed_handler =
782 g_signal_connect_after (sel, "changed",
783 G_CALLBACK(on_selection_changed), self);
785 g_signal_connect (self, "row-activated",
786 G_CALLBACK (on_header_row_activated), NULL);
788 #ifndef MODEST_TOOLKIT_HILDON2
789 g_signal_connect (self, "focus-in-event",
790 G_CALLBACK(on_focus_in), NULL);
791 g_signal_connect (self, "focus-out-event",
792 G_CALLBACK(on_focus_out), NULL);
795 g_signal_connect (self, "button-press-event",
796 G_CALLBACK(on_button_press_event), NULL);
797 g_signal_connect (self, "button-release-event",
798 G_CALLBACK(on_button_release_event), NULL);
800 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
802 G_CALLBACK (on_account_removed),
805 g_signal_connect (self, "expose-event",
806 G_CALLBACK(modest_header_view_on_expose_event),
809 return GTK_WIDGET(self);
814 modest_header_view_count_selected_headers (ModestHeaderView *self)
816 GtkTreeSelection *sel;
819 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
821 /* Get selection object and check selected rows count */
822 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
823 selected_rows = gtk_tree_selection_count_selected_rows (sel);
825 return selected_rows;
829 modest_header_view_has_selected_headers (ModestHeaderView *self)
831 GtkTreeSelection *sel;
834 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
836 /* Get selection object and check selected rows count */
837 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
838 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
845 modest_header_view_get_selected_headers (ModestHeaderView *self)
847 GtkTreeSelection *sel;
848 TnyList *header_list = NULL;
850 GList *list, *tmp = NULL;
851 GtkTreeModel *tree_model = NULL;
854 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
857 /* Get selected rows */
858 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
859 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
862 header_list = tny_simple_list_new();
864 list = g_list_reverse (list);
867 /* get header from selection */
868 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
869 gtk_tree_model_get (tree_model, &iter,
870 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
872 /* Prepend to list */
873 tny_list_prepend (header_list, G_OBJECT (header));
874 g_object_unref (G_OBJECT (header));
876 tmp = g_list_next (tmp);
879 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
886 /* scroll our list view so the selected item is visible */
888 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
890 #ifdef MODEST_TOOLKIT_GTK
892 GtkTreePath *selected_path;
893 GtkTreePath *start, *end;
897 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
898 selected_path = gtk_tree_model_get_path (model, iter);
900 start = gtk_tree_path_new ();
901 end = gtk_tree_path_new ();
903 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
905 if (gtk_tree_path_compare (selected_path, start) < 0 ||
906 gtk_tree_path_compare (end, selected_path) < 0)
907 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
908 selected_path, NULL, TRUE,
911 gtk_tree_path_free (selected_path);
912 gtk_tree_path_free (start);
913 gtk_tree_path_free (end);
915 #endif /* MODEST_TOOLKIT_GTK */
920 modest_header_view_select_next (ModestHeaderView *self)
922 GtkTreeSelection *sel;
927 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
929 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
930 path = get_selected_row (GTK_TREE_VIEW(self), &model);
931 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
932 /* Unselect previous path */
933 gtk_tree_selection_unselect_path (sel, path);
935 /* Move path down and selects new one */
936 if (gtk_tree_model_iter_next (model, &iter)) {
937 gtk_tree_selection_select_iter (sel, &iter);
938 scroll_to_selected (self, &iter, FALSE);
940 gtk_tree_path_free(path);
946 modest_header_view_select_prev (ModestHeaderView *self)
948 GtkTreeSelection *sel;
953 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
955 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
956 path = get_selected_row (GTK_TREE_VIEW(self), &model);
957 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
958 /* Unselect previous path */
959 gtk_tree_selection_unselect_path (sel, path);
962 if (gtk_tree_path_prev (path)) {
963 gtk_tree_model_get_iter (model, &iter, path);
965 /* Select the new one */
966 gtk_tree_selection_select_iter (sel, &iter);
967 scroll_to_selected (self, &iter, TRUE);
970 gtk_tree_path_free (path);
975 modest_header_view_get_columns (ModestHeaderView *self)
977 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
979 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
985 modest_header_view_set_style (ModestHeaderView *self,
986 ModestHeaderViewStyle style)
988 ModestHeaderViewPrivate *priv;
989 gboolean show_col_headers = FALSE;
990 ModestHeaderViewStyle old_style;
992 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
993 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
996 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
997 if (priv->style == style)
998 return TRUE; /* nothing to do */
1001 case MODEST_HEADER_VIEW_STYLE_DETAILS:
1002 show_col_headers = TRUE;
1004 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1007 g_return_val_if_reached (FALSE);
1009 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1010 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1012 old_style = priv->style;
1013 priv->style = style;
1019 ModestHeaderViewStyle
1020 modest_header_view_get_style (ModestHeaderView *self)
1022 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1024 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1027 /* This is used to automatically select the first header if the user
1028 * has not selected any header yet.
1031 modest_header_view_on_expose_event(GtkTreeView *header_view,
1032 GdkEventExpose *event,
1035 GtkTreeSelection *sel;
1036 GtkTreeModel *model;
1037 GtkTreeIter tree_iter;
1038 ModestHeaderViewPrivate *priv;
1040 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1041 model = gtk_tree_view_get_model(header_view);
1046 #ifdef MODEST_TOOLKIT_HILDON2
1049 sel = gtk_tree_view_get_selection(header_view);
1050 if(!gtk_tree_selection_count_selected_rows(sel)) {
1051 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1052 GtkTreePath *tree_iter_path;
1053 /* Prevent the widget from getting the focus
1054 when selecting the first item */
1055 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1056 g_object_set(header_view, "can-focus", FALSE, NULL);
1057 gtk_tree_selection_select_iter(sel, &tree_iter);
1058 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1059 g_object_set(header_view, "can-focus", TRUE, NULL);
1060 if (priv->autoselect_reference) {
1061 gtk_tree_row_reference_free (priv->autoselect_reference);
1063 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1064 gtk_tree_path_free (tree_iter_path);
1067 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1068 gboolean moved_selection = FALSE;
1069 GtkTreePath * last_path;
1070 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1071 moved_selection = TRUE;
1075 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1076 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1077 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1078 moved_selection = TRUE;
1079 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1081 gtk_tree_path_free (last_path);
1083 if (moved_selection) {
1084 gtk_tree_row_reference_free (priv->autoselect_reference);
1085 priv->autoselect_reference = NULL;
1088 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1089 GtkTreePath *current_path;
1090 current_path = gtk_tree_model_get_path (model, &tree_iter);
1091 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1092 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1093 g_object_set(header_view, "can-focus", FALSE, NULL);
1094 gtk_tree_selection_unselect_all (sel);
1095 gtk_tree_selection_select_iter(sel, &tree_iter);
1096 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1097 g_object_set(header_view, "can-focus", TRUE, NULL);
1098 gtk_tree_row_reference_free (priv->autoselect_reference);
1099 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1101 gtk_tree_path_free (current_path);
1102 gtk_tree_path_free (last_path);
1112 modest_header_view_get_folder (ModestHeaderView *self)
1114 ModestHeaderViewPrivate *priv;
1116 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1118 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1121 g_object_ref (priv->folder);
1123 return priv->folder;
1127 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1133 ModestHeaderView *self;
1134 ModestHeaderViewPrivate *priv;
1136 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1138 self = MODEST_HEADER_VIEW (user_data);
1139 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1141 if (cancelled || err)
1144 /* Add IDLE observer (monitor) and another folder observer for
1145 new messages (self) */
1146 g_mutex_lock (priv->observers_lock);
1147 if (priv->monitor) {
1148 tny_folder_monitor_stop (priv->monitor);
1149 g_object_unref (G_OBJECT (priv->monitor));
1151 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1152 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1153 tny_folder_monitor_start (priv->monitor);
1154 g_mutex_unlock (priv->observers_lock);
1158 modest_header_view_set_folder_intern (ModestHeaderView *self,
1164 ModestHeaderViewPrivate *priv;
1165 GList *cols, *cursor;
1166 GtkTreeModel *filter_model, *sortable;
1168 GtkSortType sort_type;
1170 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1172 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1173 tny_gtk_header_list_model_set_update_in_batches (TNY_GTK_HEADER_LIST_MODEL (headers), 300);
1175 /* Start the monitor in the callback of the
1176 tny_gtk_header_list_model_set_folder call. It's crucial to
1177 do it there and not just after the call because we want the
1178 monitor to observe only the headers returned by the
1179 tny_folder_get_headers_async call that it's inside the
1180 tny_gtk_header_list_model_set_folder call. This way the
1181 monitor infrastructure could successfully cope with
1182 duplicates. For example if a tny_folder_add_msg_async is
1183 happening while tny_gtk_header_list_model_set_folder is
1184 invoked, then the first call could add a header that will
1185 be added again by tny_gtk_header_list_model_set_folder, so
1186 we'd end up with duplicate headers. sergio */
1187 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1189 set_folder_intern_get_headers_async_cb,
1192 /* Init filter_row function to examine empty status */
1193 priv->status = HEADER_VIEW_INIT;
1195 /* Create sortable model */
1196 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1197 g_object_unref (headers);
1199 /* Create a tree model filter to hide and show rows for cut operations */
1200 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1201 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1202 filter_row, self, NULL);
1203 g_object_unref (sortable);
1205 /* install our special sorting functions */
1206 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1208 /* Restore sort column id */
1210 type = modest_tny_folder_guess_folder_type (folder);
1211 if (type == TNY_FOLDER_TYPE_INVALID)
1212 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1214 sort_colid = modest_header_view_get_sort_column_id (self, type);
1215 sort_type = modest_header_view_get_sort_type (self, type);
1216 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1219 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1220 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1221 (GtkTreeIterCompareFunc) cmp_rows,
1223 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1224 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1225 (GtkTreeIterCompareFunc) cmp_subject_rows,
1230 gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1231 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1232 g_object_unref (filter_model);
1239 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1241 GtkSortType sort_type)
1243 ModestHeaderViewPrivate *priv = NULL;
1244 GtkTreeModel *sortable = NULL, *filter_model = NULL;
1247 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1248 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1250 /* Get model and private data */
1251 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1252 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1253 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1254 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
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)
2155 gchar *subject_fold;
2161 gchar **current_word;
2164 subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2165 if (subject_fold == NULL) {
2167 subject = tny_header_dup_subject (header);
2168 if (subject != NULL) {
2169 subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2170 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2171 subject_fold, (GDestroyNotify) g_free);
2176 from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2177 if (from_fold == NULL) {
2179 from = tny_header_dup_from (header);
2181 from_fold = from?g_utf8_casefold (from, -1):NULL;
2182 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2183 from_fold, (GDestroyNotify) g_free);
2188 to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2189 if (to_fold == NULL) {
2191 to = tny_header_dup_to (header);
2193 to_fold = to?g_utf8_casefold (to, -1):NULL;
2194 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2195 to_fold, (GDestroyNotify) g_free);
2200 cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2201 if (cc_fold == NULL) {
2203 cc = tny_header_dup_cc (header);
2205 cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2206 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2207 cc_fold, (GDestroyNotify) g_free);
2212 bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2213 if (bcc_fold == NULL) {
2215 bcc = tny_header_dup_bcc (header);
2217 bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2218 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2219 bcc_fold, (GDestroyNotify) g_free);
2226 for (current_word = words; *current_word != NULL; current_word++) {
2228 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2229 || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2230 || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2231 || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2232 || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2244 filter_row (GtkTreeModel *model,
2248 ModestHeaderViewPrivate *priv = NULL;
2249 TnyHeaderFlags flags;
2250 TnyHeader *header = NULL;
2253 gboolean visible = TRUE;
2254 gboolean found = FALSE;
2255 GValue value = {0,};
2256 HeaderViewStatus old_status;
2258 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2259 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2261 /* Get header from model */
2262 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2263 header = (TnyHeader *) g_value_get_object (&value);
2264 g_value_unset (&value);
2265 flags = tny_header_get_flags (header);
2267 /* Get message id from header (ensure is a valid id) */
2273 /* Hide deleted and mark as deleted heders */
2274 if (flags & TNY_HEADER_FLAG_DELETED ||
2275 flags & TNY_HEADER_FLAG_EXPUNGED) {
2280 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2281 if (current_folder_needs_filtering (priv) &&
2282 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2288 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2289 if (current_folder_needs_filtering (priv) &&
2290 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2296 if (visible && priv->filter_string) {
2297 if (!header_match_string (header, priv->filter_string_splitted)) {
2301 if (priv->filter_date_range) {
2302 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2303 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2310 /* If no data on clipboard, return always TRUE */
2311 if (modest_email_clipboard_cleared(priv->clipboard)) {
2317 if (priv->hidding_ids != NULL) {
2318 id = tny_header_dup_message_id (header);
2319 for (i=0; i < priv->n_selected && !found; i++)
2320 if (priv->hidding_ids[i] != NULL && id != NULL)
2321 found = (!strcmp (priv->hidding_ids[i], id));
2328 old_status = priv->status;
2329 priv->status = ((gboolean) priv->status) && !visible;
2330 if ((priv->notify_status) && (priv->status != old_status)) {
2331 if (priv->status_timeout)
2332 g_source_remove (priv->status_timeout);
2335 NotifyFilterInfo *info;
2337 info = g_slice_new0 (NotifyFilterInfo);
2338 info->self = g_object_ref (G_OBJECT (user_data));
2340 info->folder = tny_header_get_folder (header);
2341 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2342 notify_filter_change,
2344 notify_filter_change_destroy);
2352 _clear_hidding_filter (ModestHeaderView *header_view)
2354 ModestHeaderViewPrivate *priv = NULL;
2357 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2358 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2360 if (priv->hidding_ids != NULL) {
2361 for (i=0; i < priv->n_selected; i++)
2362 g_free (priv->hidding_ids[i]);
2363 g_free(priv->hidding_ids);
2368 modest_header_view_refilter (ModestHeaderView *header_view)
2370 GtkTreeModel *filter_model = NULL;
2371 ModestHeaderViewPrivate *priv = NULL;
2373 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2374 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2376 /* Hide cut headers */
2377 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2378 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2379 priv->status = HEADER_VIEW_INIT;
2380 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2385 * Called when an account is removed. If I'm showing a folder of the
2386 * account that has been removed then clear the view
2389 on_account_removed (TnyAccountStore *self,
2390 TnyAccount *account,
2393 ModestHeaderViewPrivate *priv = NULL;
2395 /* Ignore changes in transport accounts */
2396 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2399 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2402 TnyAccount *my_account;
2404 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2405 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2406 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2407 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2409 my_account = tny_folder_get_account (priv->folder);
2413 if (my_account == account)
2414 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2415 g_object_unref (my_account);
2421 modest_header_view_add_observer(ModestHeaderView *header_view,
2422 ModestHeaderViewObserver *observer)
2424 ModestHeaderViewPrivate *priv;
2426 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2427 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2429 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2431 g_mutex_lock(priv->observer_list_lock);
2432 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2433 g_mutex_unlock(priv->observer_list_lock);
2437 modest_header_view_remove_observer(ModestHeaderView *header_view,
2438 ModestHeaderViewObserver *observer)
2440 ModestHeaderViewPrivate *priv;
2442 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2443 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2445 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2447 g_mutex_lock(priv->observer_list_lock);
2448 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2449 g_mutex_unlock(priv->observer_list_lock);
2453 modest_header_view_notify_observers(ModestHeaderView *header_view,
2454 GtkTreeModel *model,
2455 const gchar *tny_folder_id)
2457 ModestHeaderViewPrivate *priv = NULL;
2459 ModestHeaderViewObserver *observer;
2462 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2464 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2466 g_mutex_lock(priv->observer_list_lock);
2467 iter = priv->observer_list;
2468 while(iter != NULL){
2469 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2470 modest_header_view_observer_update(observer, model,
2472 iter = g_slist_next(iter);
2474 g_mutex_unlock(priv->observer_list_lock);
2478 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2480 ModestHeaderViewPrivate *priv = NULL;
2482 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2483 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2487 modest_header_view_set_filter (ModestHeaderView *self,
2488 ModestHeaderViewFilter filter)
2490 ModestHeaderViewPrivate *priv;
2492 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2493 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2495 priv->filter |= filter;
2497 if (current_folder_needs_filtering (priv))
2498 modest_header_view_refilter (self);
2502 modest_header_view_unset_filter (ModestHeaderView *self,
2503 ModestHeaderViewFilter filter)
2505 ModestHeaderViewPrivate *priv;
2507 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2508 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2510 priv->filter &= ~filter;
2512 if (current_folder_needs_filtering (priv))
2513 modest_header_view_refilter (self);
2517 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2519 if (strcmp ("style", spec->name) == 0) {
2520 update_style (MODEST_HEADER_VIEW (obj));
2521 gtk_widget_queue_draw (GTK_WIDGET (obj));
2526 update_style (ModestHeaderView *self)
2528 ModestHeaderViewPrivate *priv;
2529 GdkColor style_color;
2530 GdkColor style_active_color;
2531 PangoAttrList *attr_list;
2533 PangoAttribute *attr;
2535 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2536 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2540 attr_list = pango_attr_list_new ();
2541 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2542 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2544 priv->secondary_color = style_color;
2545 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2546 pango_attr_list_insert (attr_list, attr);
2549 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2551 "SmallSystemFont", NULL,
2554 attr = pango_attr_font_desc_new (pango_font_description_copy
2555 (style->font_desc));
2556 pango_attr_list_insert (attr_list, attr);
2558 g_object_set (G_OBJECT (priv->renderer_address),
2559 "foreground-gdk", &(priv->secondary_color),
2560 "foreground-set", TRUE,
2561 "attributes", attr_list,
2563 g_object_set (G_OBJECT (priv->renderer_date_status),
2564 "foreground-gdk", &(priv->secondary_color),
2565 "foreground-set", TRUE,
2566 "attributes", attr_list,
2568 pango_attr_list_unref (attr_list);
2570 g_object_set (G_OBJECT (priv->renderer_address),
2571 "foreground-gdk", &(priv->secondary_color),
2572 "foreground-set", TRUE,
2573 "scale", PANGO_SCALE_SMALL,
2576 g_object_set (G_OBJECT (priv->renderer_date_status),
2577 "foreground-gdk", &(priv->secondary_color),
2578 "foreground-set", TRUE,
2579 "scale", PANGO_SCALE_SMALL,
2584 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2585 priv->active_color = style_active_color;
2586 #ifdef MODEST_TOOLKIT_HILDON2
2587 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2588 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2591 #ifdef MODEST_TOOLKIT_HILDON2
2592 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2598 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2603 GtkTreeModel *tree_model;
2608 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2616 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2617 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2621 gtk_tree_model_get (tree_model, &iter,
2622 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2629 parse_date_side (const gchar *string, time_t *date_side)
2635 gboolean result = FALSE;
2637 if (string && string[0] == '\0') {
2642 casefold = g_utf8_casefold (string, -1);
2643 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2644 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2645 date = g_date_new ();
2647 if (g_utf8_collate (casefold, today) == 0) {
2648 *date_side = time (NULL);
2653 if (g_utf8_collate (casefold, yesterday) == 0) {
2654 *date_side = time (NULL) - 24*60*60;
2659 g_date_set_parse (date, string);
2660 if (g_date_valid (date)) {
2662 g_date_to_struct_tm (date, &tm);
2663 *date_side = mktime (&tm);
2678 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2683 parts = g_strsplit (string, "..", 2);
2686 if (g_strv_length (parts) != 2) {
2693 if (!parse_date_side (parts[0], date_range_start)) {
2698 if (parse_date_side (parts[1], date_range_end)) {
2699 if (*date_range_end == 0) {
2700 *date_range_end = (time_t) -1;
2702 *date_range_end += (24*60*60 - 1);
2715 modest_header_view_set_filter_string (ModestHeaderView *self,
2716 const gchar *filter_string)
2718 ModestHeaderViewPrivate *priv;
2720 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2721 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2723 if (priv->filter_string)
2724 g_free (priv->filter_string);
2726 priv->filter_string = g_strdup (filter_string);
2727 priv->filter_date_range = FALSE;
2729 if (priv->filter_string_splitted) {
2730 g_strfreev (priv->filter_string_splitted);
2731 priv->filter_string_splitted = NULL;
2734 if (priv->filter_string) {
2735 gchar **split, **current, **current_target;
2737 split = g_strsplit (priv->filter_string, " ", 0);
2739 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2740 current_target = priv->filter_string_splitted;
2741 for (current = split; *current != 0; current ++) {
2742 gboolean has_date_range = FALSE;;
2743 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2744 time_t range_start, range_end;
2745 /* It contains .. but it's not ".." so it may be a date range */
2746 if (parse_date_range (*current, &range_start, &range_end)) {
2747 priv->filter_date_range = TRUE;
2748 has_date_range = TRUE;
2749 priv->date_range_start = range_start;
2750 priv->date_range_end = range_end;
2753 if (!has_date_range) {
2754 *current_target = g_utf8_casefold (*current, -1);
2758 *current_target = '\0';
2761 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2764 #ifdef MODEST_TOOLKIT_HILDON2
2766 on_live_search_timeout (ModestHeaderView *self)
2768 const gchar *needle;
2769 ModestHeaderViewPrivate *priv;
2771 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2773 needle = hildon_live_search_get_text ((HildonLiveSearch *) priv->live_search);
2774 if (needle && needle[0] != '\0') {
2775 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2777 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2780 priv->live_search_timeout = 0;
2786 on_live_search_refilter (HildonLiveSearch *livesearch,
2787 ModestHeaderView *self)
2789 ModestHeaderViewPrivate *priv;
2790 GtkTreeModel *model, *sortable, *filter;
2792 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2794 if (priv->live_search_timeout > 0) {
2795 g_source_remove (priv->live_search_timeout);
2796 priv->live_search_timeout = 0;
2800 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2801 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2802 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2803 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2804 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2808 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2809 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2811 on_live_search_timeout (self);
2818 modest_header_view_setup_live_search (ModestHeaderView *self)
2820 ModestHeaderViewPrivate *priv;
2822 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2823 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2824 priv->live_search = hildon_live_search_new ();
2826 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2828 return priv->live_search;