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;
162 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
163 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
165 gulong selection_changed_handler;
166 gulong acc_removed_handler;
168 GList *drag_begin_cached_selected_rows;
170 HeaderViewStatus status;
171 guint status_timeout;
172 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
174 ModestDatetimeFormatter *datetime_formatter;
176 GtkCellRenderer *renderer_subject;
177 GtkCellRenderer *renderer_address;
178 GtkCellRenderer *renderer_date_status;
180 GdkColor active_color;
181 GdkColor secondary_color;
183 gchar *filter_string;
184 gchar **filter_string_splitted;
185 gboolean filter_date_range;
186 time_t date_range_start;
187 time_t date_range_end;
190 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
191 struct _HeadersCountChangedHelper {
192 ModestHeaderView *self;
193 TnyFolderChange *change;
197 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
198 MODEST_TYPE_HEADER_VIEW, \
199 ModestHeaderViewPrivate))
203 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
206 HEADER_SELECTED_SIGNAL,
207 HEADER_ACTIVATED_SIGNAL,
208 ITEM_NOT_FOUND_SIGNAL,
209 MSG_COUNT_CHANGED_SIGNAL,
210 UPDATING_MSG_LIST_SIGNAL,
215 static GObjectClass *parent_class = NULL;
217 /* uncomment the following if you have defined any signals */
218 static guint signals[LAST_SIGNAL] = {0};
221 modest_header_view_get_type (void)
223 static GType my_type = 0;
225 static const GTypeInfo my_info = {
226 sizeof(ModestHeaderViewClass),
227 NULL, /* base init */
228 NULL, /* base finalize */
229 (GClassInitFunc) modest_header_view_class_init,
230 NULL, /* class finalize */
231 NULL, /* class data */
232 sizeof(ModestHeaderView),
234 (GInstanceInitFunc) modest_header_view_init,
238 static const GInterfaceInfo tny_folder_observer_info =
240 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
241 NULL, /* interface_finalize */
242 NULL /* interface_data */
244 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
248 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
249 &tny_folder_observer_info);
257 modest_header_view_class_init (ModestHeaderViewClass *klass)
259 GObjectClass *gobject_class;
260 gobject_class = (GObjectClass*) klass;
262 parent_class = g_type_class_peek_parent (klass);
263 gobject_class->finalize = modest_header_view_finalize;
264 gobject_class->dispose = modest_header_view_dispose;
266 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
268 signals[HEADER_SELECTED_SIGNAL] =
269 g_signal_new ("header_selected",
270 G_TYPE_FROM_CLASS (gobject_class),
272 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
274 g_cclosure_marshal_VOID__POINTER,
275 G_TYPE_NONE, 1, G_TYPE_POINTER);
277 signals[HEADER_ACTIVATED_SIGNAL] =
278 g_signal_new ("header_activated",
279 G_TYPE_FROM_CLASS (gobject_class),
281 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
283 gtk_marshal_VOID__POINTER_POINTER,
284 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
287 signals[ITEM_NOT_FOUND_SIGNAL] =
288 g_signal_new ("item_not_found",
289 G_TYPE_FROM_CLASS (gobject_class),
291 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
293 g_cclosure_marshal_VOID__INT,
294 G_TYPE_NONE, 1, G_TYPE_INT);
296 signals[MSG_COUNT_CHANGED_SIGNAL] =
297 g_signal_new ("msg_count_changed",
298 G_TYPE_FROM_CLASS (gobject_class),
300 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
302 modest_marshal_VOID__POINTER_POINTER,
303 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
305 signals[UPDATING_MSG_LIST_SIGNAL] =
306 g_signal_new ("updating-msg-list",
307 G_TYPE_FROM_CLASS (gobject_class),
309 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
311 g_cclosure_marshal_VOID__BOOLEAN,
312 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
314 #ifdef MODEST_TOOLKIT_HILDON2
315 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
321 tny_folder_observer_init (TnyFolderObserverIface *klass)
323 klass->update = folder_monitor_update;
326 static GtkTreeViewColumn*
327 get_new_column (const gchar *name, GtkCellRenderer *renderer,
328 gboolean resizable, gint sort_col_id, gboolean show_as_text,
329 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
331 GtkTreeViewColumn *column;
333 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
334 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
336 gtk_tree_view_column_set_resizable (column, resizable);
338 gtk_tree_view_column_set_expand (column, TRUE);
341 gtk_tree_view_column_add_attribute (column, renderer, "text",
343 if (sort_col_id >= 0)
344 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
346 gtk_tree_view_column_set_sort_indicator (column, FALSE);
347 gtk_tree_view_column_set_reorderable (column, TRUE);
350 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
357 remove_all_columns (ModestHeaderView *obj)
359 GList *columns, *cursor;
361 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
363 for (cursor = columns; cursor; cursor = cursor->next)
364 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
365 GTK_TREE_VIEW_COLUMN(cursor->data));
366 g_list_free (columns);
370 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
372 GtkTreeModel *sortable;
373 GtkTreeViewColumn *column=NULL;
374 GtkTreeSelection *selection = NULL;
375 GtkCellRenderer *renderer_header,
376 *renderer_attach, *renderer_compact_date_or_status;
377 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
378 *renderer_subject_box, *renderer_recpt,
380 ModestHeaderViewPrivate *priv;
381 GtkTreeViewColumn *compact_column = NULL;
384 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
385 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
387 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
389 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
391 /* TODO: check whether these renderers need to be freed */
392 renderer_attach = gtk_cell_renderer_pixbuf_new ();
393 renderer_priority = gtk_cell_renderer_pixbuf_new ();
394 renderer_header = gtk_cell_renderer_text_new ();
396 renderer_compact_header = modest_vbox_cell_renderer_new ();
397 renderer_recpt_box = modest_hbox_cell_renderer_new ();
398 renderer_subject_box = modest_hbox_cell_renderer_new ();
399 renderer_recpt = gtk_cell_renderer_text_new ();
400 priv->renderer_address = renderer_recpt;
401 priv->renderer_subject = gtk_cell_renderer_text_new ();
402 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
403 priv->renderer_date_status = renderer_compact_date_or_status;
405 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
406 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
407 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
408 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
409 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
410 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
411 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
412 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
413 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
414 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
415 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
416 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
417 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
418 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
420 #ifdef MODEST_TOOLKIT_HILDON2
421 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
423 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
424 #ifndef MODEST_TOOLKIT_GTK
425 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
426 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
428 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
429 g_object_set(G_OBJECT(renderer_header),
430 "ellipsize", PANGO_ELLIPSIZE_END,
432 g_object_set (G_OBJECT (priv->renderer_subject),
433 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
435 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
436 g_object_set (G_OBJECT (renderer_recpt),
437 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
439 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
440 g_object_set(G_OBJECT(renderer_compact_date_or_status),
441 "xalign", 1.0, "yalign", 0.1,
443 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
444 #ifdef MODEST_TOOLKIT_HILDON2
445 g_object_set (G_OBJECT (renderer_priority),
447 "xalign", 0.0, NULL);
448 g_object_set (G_OBJECT (renderer_attach),
450 "xalign", 0.0, NULL);
452 g_object_set (G_OBJECT (renderer_priority),
453 "yalign", 0.5, NULL);
454 g_object_set (G_OBJECT (renderer_attach),
455 "yalign", 0.0, NULL);
458 #ifdef MODEST_TOOLKIT_HILDON1
459 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
460 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
461 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
462 #elif MODEST_TOOLKIT_HILDON2
463 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
464 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
465 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
467 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
468 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
469 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
472 remove_all_columns (self);
474 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
475 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
476 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
478 /* Add new columns */
479 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
480 ModestHeaderViewColumn col =
481 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
483 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
484 g_printerr ("modest: invalid column %d in column list\n", col);
490 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
491 column = get_new_column (_("A"), renderer_attach, FALSE,
492 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
494 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
496 gtk_tree_view_column_set_fixed_width (column, 45);
500 case MODEST_HEADER_VIEW_COLUMN_FROM:
501 column = get_new_column (_("From"), renderer_header, TRUE,
502 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
504 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
505 GINT_TO_POINTER(TRUE));
508 case MODEST_HEADER_VIEW_COLUMN_TO:
509 column = get_new_column (_("To"), renderer_header, TRUE,
510 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
512 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
513 GINT_TO_POINTER(FALSE));
516 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
517 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
518 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
520 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
521 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
522 compact_column = column;
525 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
526 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
527 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
529 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
530 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
531 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
532 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
533 compact_column = column;
537 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
538 column = get_new_column (_("Subject"), renderer_header, TRUE,
539 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
541 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
545 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
546 column = get_new_column (_("Received"), renderer_header, TRUE,
547 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
549 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
550 GINT_TO_POINTER(TRUE));
553 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
554 column = get_new_column (_("Sent"), renderer_header, TRUE,
555 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
557 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
558 GINT_TO_POINTER(FALSE));
561 case MODEST_HEADER_VIEW_COLUMN_SIZE:
562 column = get_new_column (_("Size"), renderer_header, TRUE,
563 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
565 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
568 case MODEST_HEADER_VIEW_COLUMN_STATUS:
569 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
570 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
572 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
577 g_return_val_if_reached(FALSE);
580 /* we keep the column id around */
581 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
582 GINT_TO_POINTER(col));
584 /* we need this ptr when sorting the rows */
585 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
587 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
591 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
592 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
593 (GtkTreeIterCompareFunc) cmp_rows,
594 compact_column, NULL);
595 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
596 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
597 (GtkTreeIterCompareFunc) cmp_subject_rows,
598 compact_column, NULL);
602 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
608 datetime_format_changed (ModestDatetimeFormatter *formatter,
609 ModestHeaderView *self)
611 gtk_widget_queue_draw (GTK_WIDGET (self));
615 modest_header_view_init (ModestHeaderView *obj)
617 ModestHeaderViewPrivate *priv;
620 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
623 priv->is_outbox = FALSE;
625 priv->monitor = NULL;
626 priv->observers_lock = g_mutex_new ();
627 priv->autoselect_reference = NULL;
629 priv->status = HEADER_VIEW_INIT;
630 priv->status_timeout = 0;
631 priv->notify_status = TRUE;
633 priv->observer_list_lock = g_mutex_new();
634 priv->observer_list = NULL;
636 priv->clipboard = modest_runtime_get_email_clipboard ();
637 priv->hidding_ids = NULL;
638 priv->n_selected = 0;
639 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
640 #ifdef MODEST_TOOLKIT_HILDON2
641 priv->live_search = NULL;
643 priv->filter_string = NULL;
644 priv->filter_string_splitted = NULL;
645 priv->filter_date_range = FALSE;
646 priv->selection_changed_handler = 0;
647 priv->acc_removed_handler = 0;
649 /* Sort parameters */
650 for (j=0; j < 2; j++) {
651 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
652 priv->sort_colid[j][i] = -1;
653 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
657 priv->datetime_formatter = modest_datetime_formatter_new ();
658 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
659 G_CALLBACK (datetime_format_changed), (gpointer) obj);
661 setup_drag_and_drop (GTK_WIDGET(obj));
665 modest_header_view_dispose (GObject *obj)
667 ModestHeaderView *self;
668 ModestHeaderViewPrivate *priv;
669 GtkTreeSelection *sel;
671 self = MODEST_HEADER_VIEW(obj);
672 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
674 if (priv->datetime_formatter) {
675 g_object_unref (priv->datetime_formatter);
676 priv->datetime_formatter = NULL;
679 /* Free in the dispose to avoid unref cycles */
681 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
682 g_object_unref (G_OBJECT (priv->folder));
686 /* We need to do this here in the dispose because the
687 selection won't exist when finalizing */
688 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
689 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
690 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
691 priv->selection_changed_handler = 0;
694 G_OBJECT_CLASS(parent_class)->dispose (obj);
698 modest_header_view_finalize (GObject *obj)
700 ModestHeaderView *self;
701 ModestHeaderViewPrivate *priv;
703 self = MODEST_HEADER_VIEW(obj);
704 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
706 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
707 priv->acc_removed_handler)) {
708 g_signal_handler_disconnect (modest_runtime_get_account_store (),
709 priv->acc_removed_handler);
712 /* There is no need to lock because there should not be any
713 * reference to self now. */
714 g_mutex_free(priv->observer_list_lock);
715 g_slist_free(priv->observer_list);
717 g_mutex_lock (priv->observers_lock);
719 tny_folder_monitor_stop (priv->monitor);
720 g_object_unref (G_OBJECT (priv->monitor));
722 g_mutex_unlock (priv->observers_lock);
723 g_mutex_free (priv->observers_lock);
725 /* Clear hidding array created by cut operation */
726 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
728 if (priv->autoselect_reference != NULL) {
729 gtk_tree_row_reference_free (priv->autoselect_reference);
730 priv->autoselect_reference = NULL;
733 if (priv->filter_string) {
734 g_free (priv->filter_string);
737 if (priv->filter_string_splitted) {
738 g_strfreev (priv->filter_string_splitted);
741 G_OBJECT_CLASS(parent_class)->finalize (obj);
746 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
749 GtkTreeSelection *sel;
750 ModestHeaderView *self;
751 ModestHeaderViewPrivate *priv;
753 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
756 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
757 self = MODEST_HEADER_VIEW(obj);
758 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
760 modest_header_view_set_style (self, style);
762 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
763 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
764 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
766 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
767 TRUE); /* alternating row colors */
769 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
770 priv->selection_changed_handler =
771 g_signal_connect_after (sel, "changed",
772 G_CALLBACK(on_selection_changed), self);
774 g_signal_connect (self, "row-activated",
775 G_CALLBACK (on_header_row_activated), NULL);
777 #ifndef MODEST_TOOLKIT_HILDON2
778 g_signal_connect (self, "focus-in-event",
779 G_CALLBACK(on_focus_in), NULL);
780 g_signal_connect (self, "focus-out-event",
781 G_CALLBACK(on_focus_out), NULL);
784 g_signal_connect (self, "button-press-event",
785 G_CALLBACK(on_button_press_event), NULL);
786 g_signal_connect (self, "button-release-event",
787 G_CALLBACK(on_button_release_event), NULL);
789 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
791 G_CALLBACK (on_account_removed),
794 g_signal_connect (self, "expose-event",
795 G_CALLBACK(modest_header_view_on_expose_event),
798 return GTK_WIDGET(self);
803 modest_header_view_count_selected_headers (ModestHeaderView *self)
805 GtkTreeSelection *sel;
808 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
810 /* Get selection object and check selected rows count */
811 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
812 selected_rows = gtk_tree_selection_count_selected_rows (sel);
814 return selected_rows;
818 modest_header_view_has_selected_headers (ModestHeaderView *self)
820 GtkTreeSelection *sel;
823 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
825 /* Get selection object and check selected rows count */
826 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
827 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
834 modest_header_view_get_selected_headers (ModestHeaderView *self)
836 GtkTreeSelection *sel;
837 TnyList *header_list = NULL;
839 GList *list, *tmp = NULL;
840 GtkTreeModel *tree_model = NULL;
843 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
846 /* Get selected rows */
847 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
848 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
851 header_list = tny_simple_list_new();
853 list = g_list_reverse (list);
856 /* get header from selection */
857 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
858 gtk_tree_model_get (tree_model, &iter,
859 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
861 /* Prepend to list */
862 tny_list_prepend (header_list, G_OBJECT (header));
863 g_object_unref (G_OBJECT (header));
865 tmp = g_list_next (tmp);
868 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
875 /* scroll our list view so the selected item is visible */
877 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
879 #ifdef MODEST_TOOLKIT_GTK
881 GtkTreePath *selected_path;
882 GtkTreePath *start, *end;
886 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
887 selected_path = gtk_tree_model_get_path (model, iter);
889 start = gtk_tree_path_new ();
890 end = gtk_tree_path_new ();
892 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
894 if (gtk_tree_path_compare (selected_path, start) < 0 ||
895 gtk_tree_path_compare (end, selected_path) < 0)
896 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
897 selected_path, NULL, TRUE,
900 gtk_tree_path_free (selected_path);
901 gtk_tree_path_free (start);
902 gtk_tree_path_free (end);
904 #endif /* MODEST_TOOLKIT_GTK */
909 modest_header_view_select_next (ModestHeaderView *self)
911 GtkTreeSelection *sel;
916 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
918 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
919 path = get_selected_row (GTK_TREE_VIEW(self), &model);
920 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
921 /* Unselect previous path */
922 gtk_tree_selection_unselect_path (sel, path);
924 /* Move path down and selects new one */
925 if (gtk_tree_model_iter_next (model, &iter)) {
926 gtk_tree_selection_select_iter (sel, &iter);
927 scroll_to_selected (self, &iter, FALSE);
929 gtk_tree_path_free(path);
935 modest_header_view_select_prev (ModestHeaderView *self)
937 GtkTreeSelection *sel;
942 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
944 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
945 path = get_selected_row (GTK_TREE_VIEW(self), &model);
946 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
947 /* Unselect previous path */
948 gtk_tree_selection_unselect_path (sel, path);
951 if (gtk_tree_path_prev (path)) {
952 gtk_tree_model_get_iter (model, &iter, path);
954 /* Select the new one */
955 gtk_tree_selection_select_iter (sel, &iter);
956 scroll_to_selected (self, &iter, TRUE);
959 gtk_tree_path_free (path);
964 modest_header_view_get_columns (ModestHeaderView *self)
966 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
968 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
974 modest_header_view_set_style (ModestHeaderView *self,
975 ModestHeaderViewStyle style)
977 ModestHeaderViewPrivate *priv;
978 gboolean show_col_headers = FALSE;
979 ModestHeaderViewStyle old_style;
981 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
982 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
985 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
986 if (priv->style == style)
987 return TRUE; /* nothing to do */
990 case MODEST_HEADER_VIEW_STYLE_DETAILS:
991 show_col_headers = TRUE;
993 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
996 g_return_val_if_reached (FALSE);
998 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
999 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1001 old_style = priv->style;
1002 priv->style = style;
1008 ModestHeaderViewStyle
1009 modest_header_view_get_style (ModestHeaderView *self)
1011 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1013 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1016 /* This is used to automatically select the first header if the user
1017 * has not selected any header yet.
1020 modest_header_view_on_expose_event(GtkTreeView *header_view,
1021 GdkEventExpose *event,
1024 GtkTreeSelection *sel;
1025 GtkTreeModel *model;
1026 GtkTreeIter tree_iter;
1027 ModestHeaderViewPrivate *priv;
1029 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1030 model = gtk_tree_view_get_model(header_view);
1035 #ifdef MODEST_TOOLKIT_HILDON2
1038 sel = gtk_tree_view_get_selection(header_view);
1039 if(!gtk_tree_selection_count_selected_rows(sel)) {
1040 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1041 GtkTreePath *tree_iter_path;
1042 /* Prevent the widget from getting the focus
1043 when selecting the first item */
1044 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1045 g_object_set(header_view, "can-focus", FALSE, NULL);
1046 gtk_tree_selection_select_iter(sel, &tree_iter);
1047 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1048 g_object_set(header_view, "can-focus", TRUE, NULL);
1049 if (priv->autoselect_reference) {
1050 gtk_tree_row_reference_free (priv->autoselect_reference);
1052 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1053 gtk_tree_path_free (tree_iter_path);
1056 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1057 gboolean moved_selection = FALSE;
1058 GtkTreePath * last_path;
1059 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1060 moved_selection = TRUE;
1064 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1065 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1066 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1067 moved_selection = TRUE;
1068 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1070 gtk_tree_path_free (last_path);
1072 if (moved_selection) {
1073 gtk_tree_row_reference_free (priv->autoselect_reference);
1074 priv->autoselect_reference = NULL;
1077 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1078 GtkTreePath *current_path;
1079 current_path = gtk_tree_model_get_path (model, &tree_iter);
1080 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1081 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1082 g_object_set(header_view, "can-focus", FALSE, NULL);
1083 gtk_tree_selection_unselect_all (sel);
1084 gtk_tree_selection_select_iter(sel, &tree_iter);
1085 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1086 g_object_set(header_view, "can-focus", TRUE, NULL);
1087 gtk_tree_row_reference_free (priv->autoselect_reference);
1088 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1090 gtk_tree_path_free (current_path);
1091 gtk_tree_path_free (last_path);
1101 modest_header_view_get_folder (ModestHeaderView *self)
1103 ModestHeaderViewPrivate *priv;
1105 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1107 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1110 g_object_ref (priv->folder);
1112 return priv->folder;
1116 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1122 ModestHeaderView *self;
1123 ModestHeaderViewPrivate *priv;
1125 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1127 self = MODEST_HEADER_VIEW (user_data);
1128 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1130 if (cancelled || err)
1133 /* Add IDLE observer (monitor) and another folder observer for
1134 new messages (self) */
1135 g_mutex_lock (priv->observers_lock);
1136 if (priv->monitor) {
1137 tny_folder_monitor_stop (priv->monitor);
1138 g_object_unref (G_OBJECT (priv->monitor));
1140 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1141 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1142 tny_folder_monitor_start (priv->monitor);
1143 g_mutex_unlock (priv->observers_lock);
1147 modest_header_view_set_folder_intern (ModestHeaderView *self,
1153 ModestHeaderViewPrivate *priv;
1154 GList *cols, *cursor;
1155 GtkTreeModel *filter_model, *sortable;
1157 GtkSortType sort_type;
1159 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1161 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1163 /* Start the monitor in the callback of the
1164 tny_gtk_header_list_model_set_folder call. It's crucial to
1165 do it there and not just after the call because we want the
1166 monitor to observe only the headers returned by the
1167 tny_folder_get_headers_async call that it's inside the
1168 tny_gtk_header_list_model_set_folder call. This way the
1169 monitor infrastructure could successfully cope with
1170 duplicates. For example if a tny_folder_add_msg_async is
1171 happening while tny_gtk_header_list_model_set_folder is
1172 invoked, then the first call could add a header that will
1173 be added again by tny_gtk_header_list_model_set_folder, so
1174 we'd end up with duplicate headers. sergio */
1175 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1177 set_folder_intern_get_headers_async_cb,
1180 /* Create a tree model filter to hide and show rows for cut operations */
1181 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (headers), NULL);
1182 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1183 filter_row, self, NULL);
1184 g_object_unref (headers);
1186 /* Init filter_row function to examine empty status */
1187 priv->status = HEADER_VIEW_INIT;
1189 /* Create sortable model */
1190 sortable = gtk_tree_model_sort_new_with_model (filter_model);
1191 g_object_unref (filter_model);
1193 /* install our special sorting functions */
1194 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1196 /* Restore sort column id */
1198 type = modest_tny_folder_guess_folder_type (folder);
1199 if (type == TNY_FOLDER_TYPE_INVALID)
1200 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1202 sort_colid = modest_header_view_get_sort_column_id (self, type);
1203 sort_type = modest_header_view_get_sort_type (self, type);
1204 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1207 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1208 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1209 (GtkTreeIterCompareFunc) cmp_rows,
1211 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1212 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1213 (GtkTreeIterCompareFunc) cmp_subject_rows,
1218 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
1219 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1220 g_object_unref (sortable);
1227 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1229 GtkSortType sort_type)
1231 ModestHeaderViewPrivate *priv = NULL;
1232 GtkTreeModel *sortable = NULL;
1235 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1236 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1238 /* Get model and private data */
1239 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1240 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1242 /* Sort tree model */
1243 type = modest_tny_folder_guess_folder_type (priv->folder);
1244 if (type == TNY_FOLDER_TYPE_INVALID)
1245 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1247 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1250 /* Store new sort parameters */
1251 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1256 modest_header_view_set_sort_params (ModestHeaderView *self,
1258 GtkSortType sort_type,
1261 ModestHeaderViewPrivate *priv;
1262 ModestHeaderViewStyle style;
1264 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1265 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1266 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1268 style = modest_header_view_get_style (self);
1269 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1271 priv->sort_colid[style][type] = sort_colid;
1272 priv->sort_type[style][type] = sort_type;
1276 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1279 ModestHeaderViewPrivate *priv;
1280 ModestHeaderViewStyle style;
1282 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1283 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1285 style = modest_header_view_get_style (self);
1286 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1288 return priv->sort_colid[style][type];
1292 modest_header_view_get_sort_type (ModestHeaderView *self,
1295 ModestHeaderViewPrivate *priv;
1296 ModestHeaderViewStyle style;
1298 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1299 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1301 style = modest_header_view_get_style (self);
1302 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1304 return priv->sort_type[style][type];
1308 ModestHeaderView *header_view;
1309 RefreshAsyncUserCallback cb;
1314 folder_refreshed_cb (ModestMailOperation *mail_op,
1318 ModestHeaderViewPrivate *priv;
1319 SetFolderHelper *info;
1321 info = (SetFolderHelper*) user_data;
1323 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1327 info->cb (mail_op, folder, info->user_data);
1329 /* Start the folder count changes observer. We do not need it
1330 before the refresh. Note that the monitor could still be
1331 called for this refresh but now we know that the callback
1332 was previously called */
1333 g_mutex_lock (priv->observers_lock);
1334 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1335 g_mutex_unlock (priv->observers_lock);
1337 /* Notify the observers that the update is over */
1338 g_signal_emit (G_OBJECT (info->header_view),
1339 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1341 /* Allow filtering notifications from now on if the current
1342 folder is still the same (if not then the user has selected
1343 another one to refresh, we should wait until that refresh
1345 if (priv->folder == folder)
1346 priv->notify_status = TRUE;
1349 g_object_unref (info->header_view);
1354 refresh_folder_error_handler (ModestMailOperation *mail_op,
1357 const GError *error = modest_mail_operation_get_error (mail_op);
1359 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1360 error->code == TNY_IO_ERROR_WRITE ||
1361 error->code == TNY_IO_ERROR_READ) {
1362 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1363 /* If the mail op has been cancelled then it's not an error: don't show any message */
1364 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1365 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1366 modest_platform_information_banner (NULL, NULL, msg);
1373 modest_header_view_set_folder (ModestHeaderView *self,
1376 ModestWindow *progress_window,
1377 RefreshAsyncUserCallback callback,
1380 ModestHeaderViewPrivate *priv;
1382 g_return_if_fail (self);
1384 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1387 if (priv->status_timeout) {
1388 g_source_remove (priv->status_timeout);
1389 priv->status_timeout = 0;
1392 g_mutex_lock (priv->observers_lock);
1393 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1394 g_object_unref (priv->folder);
1395 priv->folder = NULL;
1396 g_mutex_unlock (priv->observers_lock);
1400 GtkTreeSelection *selection;
1401 SetFolderHelper *info;
1402 ModestMailOperation *mail_op = NULL;
1404 /* Set folder in the model */
1405 modest_header_view_set_folder_intern (self, folder, refresh);
1407 /* Pick my reference. Nothing to do with the mail operation */
1408 priv->folder = g_object_ref (folder);
1410 /* Do not notify about filterings until the refresh finishes */
1411 priv->notify_status = FALSE;
1413 /* Clear the selection if exists */
1414 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1415 gtk_tree_selection_unselect_all(selection);
1416 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1418 /* Notify the observers that the update begins */
1419 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1422 /* create the helper */
1423 info = g_malloc0 (sizeof (SetFolderHelper));
1424 info->header_view = g_object_ref (self);
1425 info->cb = callback;
1426 info->user_data = user_data;
1428 /* Create the mail operation (source will be the parent widget) */
1429 if (progress_window)
1430 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1431 refresh_folder_error_handler,
1434 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1437 /* Refresh the folder asynchronously */
1438 modest_mail_operation_refresh_folder (mail_op,
1440 folder_refreshed_cb,
1443 folder_refreshed_cb (mail_op, folder, info);
1447 g_object_unref (mail_op);
1449 g_mutex_lock (priv->observers_lock);
1451 if (priv->monitor) {
1452 tny_folder_monitor_stop (priv->monitor);
1453 g_object_unref (G_OBJECT (priv->monitor));
1454 priv->monitor = NULL;
1457 if (priv->autoselect_reference) {
1458 gtk_tree_row_reference_free (priv->autoselect_reference);
1459 priv->autoselect_reference = NULL;
1462 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1464 modest_header_view_notify_observers(self, NULL, NULL);
1466 g_mutex_unlock (priv->observers_lock);
1468 /* Notify the observers that the update is over */
1469 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1475 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1476 GtkTreeViewColumn *column, gpointer userdata)
1478 ModestHeaderView *self = NULL;
1480 GtkTreeModel *model = NULL;
1481 TnyHeader *header = NULL;
1482 TnyHeaderFlags flags;
1484 self = MODEST_HEADER_VIEW (treeview);
1486 model = gtk_tree_view_get_model (treeview);
1487 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1490 /* get the first selected item */
1491 gtk_tree_model_get (model, &iter,
1492 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1493 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1496 /* Dont open DELETED messages */
1497 if (flags & TNY_HEADER_FLAG_DELETED) {
1500 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1501 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1502 modest_platform_information_banner (NULL, NULL, msg);
1508 g_signal_emit (G_OBJECT(self),
1509 signals[HEADER_ACTIVATED_SIGNAL],
1515 g_object_unref (G_OBJECT (header));
1520 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1522 GtkTreeModel *model;
1523 TnyHeader *header = NULL;
1524 GtkTreePath *path = NULL;
1526 ModestHeaderView *self;
1527 GList *selected = NULL;
1529 g_return_if_fail (sel);
1530 g_return_if_fail (user_data);
1532 self = MODEST_HEADER_VIEW (user_data);
1534 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1535 if (selected != NULL)
1536 path = (GtkTreePath *) selected->data;
1537 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1538 return; /* msg was _un_selected */
1540 gtk_tree_model_get (model, &iter,
1541 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1545 g_signal_emit (G_OBJECT(self),
1546 signals[HEADER_SELECTED_SIGNAL],
1549 g_object_unref (G_OBJECT (header));
1551 /* free all items in 'selected' */
1552 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1553 g_list_free (selected);
1557 /* PROTECTED method. It's useful when we want to force a given
1558 selection to reload a msg. For example if we have selected a header
1559 in offline mode, when Modest become online, we want to reload the
1560 message automatically without an user click over the header */
1562 _modest_header_view_change_selection (GtkTreeSelection *selection,
1565 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1566 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1568 on_selection_changed (selection, user_data);
1572 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1579 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1583 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1587 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1595 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1603 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1604 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1608 case TNY_HEADER_FLAG_ATTACHMENTS:
1610 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1611 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1612 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1613 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1615 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1616 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1618 return cmp ? cmp : t1 - t2;
1620 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1621 TnyHeader *header1 = NULL, *header2 = NULL;
1623 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1624 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1625 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1626 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1628 /* This is for making priority values respect the intuitive sort relationship
1629 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1631 if (header1 && header2) {
1632 cmp = compare_priorities (tny_header_get_priority (header1),
1633 tny_header_get_priority (header2));
1634 g_object_unref (header1);
1635 g_object_unref (header2);
1637 return cmp ? cmp : t1 - t2;
1643 return &iter1 - &iter2; /* oughhhh */
1648 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1655 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1657 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1658 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1659 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1660 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1662 /* Do not use the prefixes for sorting. Consume all the blank
1663 spaces for sorting */
1664 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1665 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1668 /* If they're equal based on subject without prefix then just
1669 sort them by length. This will show messages like this.
1676 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1683 /* Drag and drop stuff */
1685 drag_data_get_cb (GtkWidget *widget,
1686 GdkDragContext *context,
1687 GtkSelectionData *selection_data,
1692 ModestHeaderView *self = NULL;
1693 ModestHeaderViewPrivate *priv = NULL;
1695 self = MODEST_HEADER_VIEW (widget);
1696 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1698 /* Set the data. Do not use the current selection because it
1699 could be different than the selection at the beginning of
1701 modest_dnd_selection_data_set_paths (selection_data,
1702 priv->drag_begin_cached_selected_rows);
1706 * We're caching the selected rows at the beginning because the
1707 * selection could change between drag-begin and drag-data-get, for
1708 * example if we have a set of rows already selected, and then we
1709 * click in one of them (without SHIFT key pressed) and begin a drag,
1710 * the selection at that moment contains all the selected lines, but
1711 * after dropping the selection, the release event provokes that only
1712 * the row used to begin the drag is selected, so at the end the
1713 * drag&drop affects only one rows instead of all the selected ones.
1717 drag_begin_cb (GtkWidget *widget,
1718 GdkDragContext *context,
1721 ModestHeaderView *self = NULL;
1722 ModestHeaderViewPrivate *priv = NULL;
1723 GtkTreeSelection *selection;
1725 self = MODEST_HEADER_VIEW (widget);
1726 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1728 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1729 priv->drag_begin_cached_selected_rows =
1730 gtk_tree_selection_get_selected_rows (selection, NULL);
1734 * We use the drag-end signal to clear the cached selection, we use
1735 * this because this allways happens, whether or not the d&d was a
1739 drag_end_cb (GtkWidget *widget,
1743 ModestHeaderView *self = NULL;
1744 ModestHeaderViewPrivate *priv = NULL;
1746 self = MODEST_HEADER_VIEW (widget);
1747 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1749 /* Free cached data */
1750 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1751 g_list_free (priv->drag_begin_cached_selected_rows);
1752 priv->drag_begin_cached_selected_rows = NULL;
1755 /* Header view drag types */
1756 const GtkTargetEntry header_view_drag_types[] = {
1757 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1761 enable_drag_and_drop (GtkWidget *self)
1763 #ifdef MODEST_TOOLKIT_HILDON2
1766 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1767 header_view_drag_types,
1768 G_N_ELEMENTS (header_view_drag_types),
1769 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1773 disable_drag_and_drop (GtkWidget *self)
1775 #ifdef MODEST_TOOLKIT_HILDON2
1778 gtk_drag_source_unset (self);
1782 setup_drag_and_drop (GtkWidget *self)
1784 #ifdef MODEST_TOOLKIT_HILDON2
1787 enable_drag_and_drop(self);
1788 g_signal_connect(G_OBJECT (self), "drag_data_get",
1789 G_CALLBACK(drag_data_get_cb), NULL);
1791 g_signal_connect(G_OBJECT (self), "drag_begin",
1792 G_CALLBACK(drag_begin_cb), NULL);
1794 g_signal_connect(G_OBJECT (self), "drag_end",
1795 G_CALLBACK(drag_end_cb), NULL);
1798 static GtkTreePath *
1799 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1801 GtkTreePath *path = NULL;
1802 GtkTreeSelection *sel = NULL;
1805 sel = gtk_tree_view_get_selection(self);
1806 rows = gtk_tree_selection_get_selected_rows (sel, model);
1808 if ((rows == NULL) || (g_list_length(rows) != 1))
1811 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1816 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1822 #ifndef MODEST_TOOLKIT_HILDON2
1824 * This function moves the tree view scroll to the current selected
1825 * row when the widget grabs the focus
1828 on_focus_in (GtkWidget *self,
1829 GdkEventFocus *event,
1832 GtkTreeSelection *selection;
1833 GtkTreeModel *model;
1834 GList *selected = NULL;
1835 GtkTreePath *selected_path = NULL;
1837 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1841 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1842 /* If none selected yet, pick the first one */
1843 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1847 /* Return if the model is empty */
1848 if (!gtk_tree_model_get_iter_first (model, &iter))
1851 path = gtk_tree_model_get_path (model, &iter);
1852 gtk_tree_selection_select_path (selection, path);
1853 gtk_tree_path_free (path);
1856 /* Need to get the all the rows because is selection multiple */
1857 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1858 if (selected == NULL) return FALSE;
1859 selected_path = (GtkTreePath *) selected->data;
1862 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1863 g_list_free (selected);
1869 on_focus_out (GtkWidget *self,
1870 GdkEventFocus *event,
1874 if (!gtk_widget_is_focus (self)) {
1875 GtkTreeSelection *selection = NULL;
1876 GList *selected_rows = NULL;
1877 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1878 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1879 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1880 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1881 gtk_tree_selection_unselect_all (selection);
1882 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1883 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1884 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1885 g_list_free (selected_rows);
1893 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1895 enable_drag_and_drop(self);
1900 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1902 GtkTreeSelection *selection = NULL;
1903 GtkTreePath *path = NULL;
1904 gboolean already_selected = FALSE, already_opened = FALSE;
1905 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1907 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1909 GtkTreeModel *model;
1911 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1912 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1914 /* Get header from model */
1915 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1916 if (gtk_tree_model_get_iter (model, &iter, path)) {
1917 GValue value = {0,};
1920 gtk_tree_model_get_value (model, &iter,
1921 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1923 header = (TnyHeader *) g_value_get_object (&value);
1924 if (TNY_IS_HEADER (header)) {
1925 status = modest_tny_all_send_queues_get_msg_status (header);
1926 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1929 g_value_unset (&value);
1933 /* Enable drag and drop only if the user clicks on a row that
1934 it's already selected. If not, let him select items using
1935 the pointer. If the message is in an OUTBOX and in sending
1936 status disable drag and drop as well */
1937 if (!already_selected ||
1938 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1940 disable_drag_and_drop(self);
1943 gtk_tree_path_free(path);
1945 /* If it's already opened then do not let the button-press
1946 event go on because it'll perform a message open because
1947 we're clicking on to an already selected header */
1952 folder_monitor_update (TnyFolderObserver *self,
1953 TnyFolderChange *change)
1955 ModestHeaderViewPrivate *priv = NULL;
1956 TnyFolderChangeChanged changed;
1957 TnyFolder *folder = NULL;
1959 changed = tny_folder_change_get_changed (change);
1961 /* Do not notify the observers if the folder of the header
1962 view has changed before this call to the observer
1964 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1965 folder = tny_folder_change_get_folder (change);
1966 if (folder != priv->folder)
1969 MODEST_DEBUG_BLOCK (
1970 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1971 g_print ("ADDED %d/%d (r/t) \n",
1972 tny_folder_change_get_new_unread_count (change),
1973 tny_folder_change_get_new_all_count (change));
1974 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1975 g_print ("ALL COUNT %d\n",
1976 tny_folder_change_get_new_all_count (change));
1977 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1978 g_print ("UNREAD COUNT %d\n",
1979 tny_folder_change_get_new_unread_count (change));
1980 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1981 g_print ("EXPUNGED %d/%d (r/t) \n",
1982 tny_folder_change_get_new_unread_count (change),
1983 tny_folder_change_get_new_all_count (change));
1984 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1985 g_print ("FOLDER RENAME\n");
1986 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1987 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1988 tny_folder_change_get_new_unread_count (change),
1989 tny_folder_change_get_new_all_count (change));
1990 g_print ("---------------------------------------------------\n");
1993 /* Check folder count */
1994 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1995 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1997 g_mutex_lock (priv->observers_lock);
1999 /* Emit signal to evaluate how headers changes affects
2000 to the window view */
2001 g_signal_emit (G_OBJECT(self),
2002 signals[MSG_COUNT_CHANGED_SIGNAL],
2005 /* Added or removed headers, so data stored on cliboard are invalid */
2006 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2007 modest_email_clipboard_clear (priv->clipboard);
2009 g_mutex_unlock (priv->observers_lock);
2015 g_object_unref (folder);
2019 modest_header_view_is_empty (ModestHeaderView *self)
2021 ModestHeaderViewPrivate *priv;
2023 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2025 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2027 return priv->status == HEADER_VIEW_EMPTY;
2031 modest_header_view_clear (ModestHeaderView *self)
2033 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2035 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2039 modest_header_view_copy_selection (ModestHeaderView *header_view)
2041 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2043 /* Copy selection */
2044 _clipboard_set_selected_data (header_view, FALSE);
2048 modest_header_view_cut_selection (ModestHeaderView *header_view)
2050 ModestHeaderViewPrivate *priv = NULL;
2051 const gchar **hidding = NULL;
2052 guint i, n_selected;
2054 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2056 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2058 /* Copy selection */
2059 _clipboard_set_selected_data (header_view, TRUE);
2061 /* Get hidding ids */
2062 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2064 /* Clear hidding array created by previous cut operation */
2065 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2067 /* Copy hidding array */
2068 priv->n_selected = n_selected;
2069 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2070 for (i=0; i < n_selected; i++)
2071 priv->hidding_ids[i] = g_strdup(hidding[i]);
2073 /* Hide cut headers */
2074 modest_header_view_refilter (header_view);
2081 _clipboard_set_selected_data (ModestHeaderView *header_view,
2084 ModestHeaderViewPrivate *priv = NULL;
2085 TnyList *headers = NULL;
2087 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2088 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2090 /* Set selected data on clipboard */
2091 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2092 headers = modest_header_view_get_selected_headers (header_view);
2093 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2096 g_object_unref (headers);
2100 ModestHeaderView *self;
2105 notify_filter_change (gpointer data)
2107 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2109 g_signal_emit (info->self,
2110 signals[MSG_COUNT_CHANGED_SIGNAL],
2111 0, info->folder, NULL);
2117 notify_filter_change_destroy (gpointer data)
2119 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2120 ModestHeaderViewPrivate *priv;
2122 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2123 priv->status_timeout = 0;
2125 g_object_unref (info->self);
2126 g_object_unref (info->folder);
2127 g_slice_free (NotifyFilterInfo, info);
2131 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2133 /* For the moment we only need to filter outbox */
2134 return priv->is_outbox;
2138 header_match_string (TnyHeader *header, gchar **words)
2145 gchar *subject_fold;
2151 gchar **current_word;
2154 subject = tny_header_dup_subject (header);
2155 cc = tny_header_dup_cc (header);
2156 bcc = tny_header_dup_bcc (header);
2157 to = tny_header_dup_to (header);
2158 from = tny_header_dup_from (header);
2160 subject_fold = g_utf8_casefold (subject, -1);
2162 bcc_fold = g_utf8_casefold (bcc, -1);
2164 cc_fold = g_utf8_casefold (cc, -1);
2166 to_fold = g_utf8_casefold (to, -1);
2168 from_fold = g_utf8_casefold (from, -1);
2173 for (current_word = words; *current_word != NULL; current_word++) {
2175 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2176 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2177 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2178 || (to && g_strstr_len (to_fold, -1, *current_word))
2179 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2187 g_free (subject_fold);
2197 filter_row (GtkTreeModel *model,
2201 ModestHeaderViewPrivate *priv = NULL;
2202 TnyHeaderFlags flags;
2203 TnyHeader *header = NULL;
2206 gboolean visible = TRUE;
2207 gboolean found = FALSE;
2208 GValue value = {0,};
2209 HeaderViewStatus old_status;
2211 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2212 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2214 /* Get header from model */
2215 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2216 flags = (TnyHeaderFlags) g_value_get_int (&value);
2217 g_value_unset (&value);
2218 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2219 header = (TnyHeader *) g_value_get_object (&value);
2220 g_value_unset (&value);
2222 /* Get message id from header (ensure is a valid id) */
2228 /* Hide deleted and mark as deleted heders */
2229 if (flags & TNY_HEADER_FLAG_DELETED ||
2230 flags & TNY_HEADER_FLAG_EXPUNGED) {
2235 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2236 if (current_folder_needs_filtering (priv) &&
2237 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2243 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2244 if (current_folder_needs_filtering (priv) &&
2245 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2251 if (visible && priv->filter_string) {
2252 if (!header_match_string (header, priv->filter_string_splitted)) {
2256 if (priv->filter_date_range) {
2257 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2258 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2265 /* If no data on clipboard, return always TRUE */
2266 if (modest_email_clipboard_cleared(priv->clipboard)) {
2272 if (priv->hidding_ids != NULL) {
2273 id = tny_header_dup_message_id (header);
2274 for (i=0; i < priv->n_selected && !found; i++)
2275 if (priv->hidding_ids[i] != NULL && id != NULL)
2276 found = (!strcmp (priv->hidding_ids[i], id));
2283 old_status = priv->status;
2284 priv->status = ((gboolean) priv->status) && !visible;
2285 if ((priv->notify_status) && (priv->status != old_status)) {
2286 if (priv->status_timeout)
2287 g_source_remove (priv->status_timeout);
2290 NotifyFilterInfo *info;
2292 info = g_slice_new0 (NotifyFilterInfo);
2293 info->self = g_object_ref (G_OBJECT (user_data));
2295 info->folder = tny_header_get_folder (header);
2296 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2297 notify_filter_change,
2299 notify_filter_change_destroy);
2307 _clear_hidding_filter (ModestHeaderView *header_view)
2309 ModestHeaderViewPrivate *priv = NULL;
2312 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2313 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2315 if (priv->hidding_ids != NULL) {
2316 for (i=0; i < priv->n_selected; i++)
2317 g_free (priv->hidding_ids[i]);
2318 g_free(priv->hidding_ids);
2323 modest_header_view_refilter (ModestHeaderView *header_view)
2325 GtkTreeModel *model, *sortable = NULL;
2326 ModestHeaderViewPrivate *priv = NULL;
2328 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2329 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2331 /* Hide cut headers */
2332 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2333 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2334 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2335 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2336 priv->status = HEADER_VIEW_INIT;
2337 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2343 * Called when an account is removed. If I'm showing a folder of the
2344 * account that has been removed then clear the view
2347 on_account_removed (TnyAccountStore *self,
2348 TnyAccount *account,
2351 ModestHeaderViewPrivate *priv = NULL;
2353 /* Ignore changes in transport accounts */
2354 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2357 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2360 TnyAccount *my_account;
2362 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2363 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2364 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2365 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2367 my_account = tny_folder_get_account (priv->folder);
2371 if (my_account == account)
2372 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2373 g_object_unref (my_account);
2379 modest_header_view_add_observer(ModestHeaderView *header_view,
2380 ModestHeaderViewObserver *observer)
2382 ModestHeaderViewPrivate *priv;
2384 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2385 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2387 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2389 g_mutex_lock(priv->observer_list_lock);
2390 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2391 g_mutex_unlock(priv->observer_list_lock);
2395 modest_header_view_remove_observer(ModestHeaderView *header_view,
2396 ModestHeaderViewObserver *observer)
2398 ModestHeaderViewPrivate *priv;
2400 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2401 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2403 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2405 g_mutex_lock(priv->observer_list_lock);
2406 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2407 g_mutex_unlock(priv->observer_list_lock);
2411 modest_header_view_notify_observers(ModestHeaderView *header_view,
2412 GtkTreeModel *model,
2413 const gchar *tny_folder_id)
2415 ModestHeaderViewPrivate *priv = NULL;
2417 ModestHeaderViewObserver *observer;
2420 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2422 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2424 g_mutex_lock(priv->observer_list_lock);
2425 iter = priv->observer_list;
2426 while(iter != NULL){
2427 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2428 modest_header_view_observer_update(observer, model,
2430 iter = g_slist_next(iter);
2432 g_mutex_unlock(priv->observer_list_lock);
2436 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2438 ModestHeaderViewPrivate *priv = NULL;
2440 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2441 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2445 modest_header_view_set_filter (ModestHeaderView *self,
2446 ModestHeaderViewFilter filter)
2448 ModestHeaderViewPrivate *priv;
2450 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2451 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2453 priv->filter |= filter;
2455 if (current_folder_needs_filtering (priv))
2456 modest_header_view_refilter (self);
2460 modest_header_view_unset_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 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2477 if (strcmp ("style", spec->name) == 0) {
2478 update_style (MODEST_HEADER_VIEW (obj));
2479 gtk_widget_queue_draw (GTK_WIDGET (obj));
2484 update_style (ModestHeaderView *self)
2486 ModestHeaderViewPrivate *priv;
2487 GdkColor style_color;
2488 GdkColor style_active_color;
2489 PangoAttrList *attr_list;
2491 PangoAttribute *attr;
2493 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2494 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2498 attr_list = pango_attr_list_new ();
2499 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2500 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2502 priv->secondary_color = style_color;
2503 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2504 pango_attr_list_insert (attr_list, attr);
2507 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2509 "SmallSystemFont", NULL,
2512 attr = pango_attr_font_desc_new (pango_font_description_copy
2513 (style->font_desc));
2514 pango_attr_list_insert (attr_list, attr);
2516 g_object_set (G_OBJECT (priv->renderer_address),
2517 "foreground-gdk", &(priv->secondary_color),
2518 "foreground-set", TRUE,
2519 "attributes", attr_list,
2521 g_object_set (G_OBJECT (priv->renderer_date_status),
2522 "foreground-gdk", &(priv->secondary_color),
2523 "foreground-set", TRUE,
2524 "attributes", attr_list,
2526 pango_attr_list_unref (attr_list);
2528 g_object_set (G_OBJECT (priv->renderer_address),
2529 "foreground-gdk", &(priv->secondary_color),
2530 "foreground-set", TRUE,
2531 "scale", PANGO_SCALE_SMALL,
2534 g_object_set (G_OBJECT (priv->renderer_date_status),
2535 "foreground-gdk", &(priv->secondary_color),
2536 "foreground-set", TRUE,
2537 "scale", PANGO_SCALE_SMALL,
2542 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2543 priv->active_color = style_active_color;
2544 #ifdef MODEST_TOOLKIT_HILDON2
2545 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2546 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2549 #ifdef MODEST_TOOLKIT_HILDON2
2550 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2556 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2561 GtkTreeModel *tree_model;
2566 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2574 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2575 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2579 gtk_tree_model_get (tree_model, &iter,
2580 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2587 parse_date_side (const gchar *string, time_t *date_side)
2593 gboolean result = FALSE;
2595 if (string && string[0] == '\0') {
2600 casefold = g_utf8_casefold (string, -1);
2601 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2602 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2603 date = g_date_new ();
2605 if (g_utf8_collate (casefold, today) == 0) {
2606 *date_side = time (NULL);
2611 if (g_utf8_collate (casefold, yesterday) == 0) {
2612 *date_side = time (NULL) - 24*60*60;
2617 g_date_set_parse (date, string);
2618 if (g_date_valid (date)) {
2620 g_date_to_struct_tm (date, &tm);
2621 *date_side = mktime (&tm);
2636 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2641 parts = g_strsplit (string, "..", 2);
2644 if (g_strv_length (parts) != 2) {
2651 if (!parse_date_side (parts[0], date_range_start)) {
2656 if (parse_date_side (parts[1], date_range_end)) {
2657 if (*date_range_end == 0) {
2658 *date_range_end = (time_t) -1;
2660 *date_range_end += (24*60*60 - 1);
2673 modest_header_view_set_filter_string (ModestHeaderView *self,
2674 const gchar *filter_string)
2676 ModestHeaderViewPrivate *priv;
2678 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2679 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2681 if (priv->filter_string)
2682 g_free (priv->filter_string);
2684 priv->filter_string = g_strdup (filter_string);
2685 priv->filter_date_range = FALSE;
2687 if (priv->filter_string_splitted) {
2688 g_strfreev (priv->filter_string_splitted);
2689 priv->filter_string_splitted = NULL;
2692 if (priv->filter_string) {
2693 gchar **split, **current, **current_target;
2695 split = g_strsplit (priv->filter_string, " ", 0);
2697 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2698 current_target = priv->filter_string_splitted;
2699 for (current = split; *current != 0; current ++) {
2700 gboolean has_date_range = FALSE;;
2701 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2702 time_t range_start, range_end;
2703 /* It contains .. but it's not ".." so it may be a date range */
2704 if (parse_date_range (*current, &range_start, &range_end)) {
2705 priv->filter_date_range = TRUE;
2706 has_date_range = TRUE;
2707 priv->date_range_start = range_start;
2708 priv->date_range_end = range_end;
2711 if (!has_date_range) {
2712 *current_target = g_utf8_casefold (*current, -1);
2716 *current_target = '\0';
2719 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2722 #ifdef MODEST_TOOLKIT_HILDON2
2724 on_live_search_refilter (HildonLiveSearch *livesearch,
2725 ModestHeaderView *self)
2727 const gchar *needle;
2729 needle = hildon_live_search_get_text (livesearch);
2730 if (needle && needle[0] != '\0') {
2731 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2733 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2740 modest_header_view_setup_live_search (ModestHeaderView *self)
2742 ModestHeaderViewPrivate *priv;
2744 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2745 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2746 priv->live_search = hildon_live_search_new ();
2748 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2750 return priv->live_search;