1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
35 #include <tny-error.h>
36 #include <tny-merge-folder.h>
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-main-window.h>
45 #include <modest-ui-actions.h>
46 #include <modest-marshal.h>
47 #include <modest-text-utils.h>
48 #include <modest-icon-names.h>
49 #include <modest-runtime.h>
50 #include "modest-platform.h"
51 #include <modest-hbox-cell-renderer.h>
52 #include <modest-vbox-cell-renderer.h>
53 #include <modest-datetime-formatter.h>
54 #include <modest-ui-constants.h>
55 #ifdef MODEST_TOOLKIT_HILDON2
56 #include <hildon/hildon.h>
59 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
60 static void modest_header_view_init (ModestHeaderView *obj);
61 static void modest_header_view_finalize (GObject *obj);
62 static void modest_header_view_dispose (GObject *obj);
64 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
65 GtkTreeViewColumn *column, gpointer userdata);
67 static gint cmp_rows (GtkTreeModel *tree_model,
72 static gint cmp_subject_rows (GtkTreeModel *tree_model,
77 static gboolean filter_row (GtkTreeModel *model,
81 static void on_account_removed (TnyAccountStore *self,
85 static void on_selection_changed (GtkTreeSelection *sel,
88 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
91 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
94 static void setup_drag_and_drop (GtkWidget *self);
96 static void enable_drag_and_drop (GtkWidget *self);
98 static void disable_drag_and_drop (GtkWidget *self);
100 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
102 #ifndef MODEST_TOOLKIT_HILDON2
103 static gboolean on_focus_in (GtkWidget *sef,
104 GdkEventFocus *event,
107 static gboolean on_focus_out (GtkWidget *self,
108 GdkEventFocus *event,
112 static void folder_monitor_update (TnyFolderObserver *self,
113 TnyFolderChange *change);
115 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
117 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
119 static void _clear_hidding_filter (ModestHeaderView *header_view);
121 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
123 const gchar *tny_folder_id);
125 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
126 GdkEventExpose *event,
129 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
130 static void update_style (ModestHeaderView *self);
133 HEADER_VIEW_NON_EMPTY,
138 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
139 struct _ModestHeaderViewPrivate {
141 ModestHeaderViewStyle style;
144 TnyFolderMonitor *monitor;
145 GMutex *observers_lock;
147 /*header-view-observer observer*/
148 GMutex *observer_list_lock;
149 GSList *observer_list;
151 /* not unref this object, its a singlenton */
152 ModestEmailClipboard *clipboard;
154 /* Filter tree model */
157 GtkTreeRowReference *autoselect_reference;
158 ModestHeaderViewFilter filter;
159 #ifdef MODEST_TOOLKIT_HILDON2
160 GtkWidget *live_search;
161 guint live_search_timeout;
164 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
165 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
167 gulong selection_changed_handler;
168 gulong acc_removed_handler;
170 GList *drag_begin_cached_selected_rows;
172 HeaderViewStatus status;
173 guint status_timeout;
174 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
176 ModestDatetimeFormatter *datetime_formatter;
178 GtkCellRenderer *renderer_subject;
179 GtkCellRenderer *renderer_address;
180 GtkCellRenderer *renderer_date_status;
182 GdkColor active_color;
183 GdkColor secondary_color;
187 gchar *filter_string;
188 gchar **filter_string_splitted;
189 gboolean filter_date_range;
190 time_t date_range_start;
191 time_t date_range_end;
194 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
195 struct _HeadersCountChangedHelper {
196 ModestHeaderView *self;
197 TnyFolderChange *change;
201 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
202 MODEST_TYPE_HEADER_VIEW, \
203 ModestHeaderViewPrivate))
207 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
210 HEADER_SELECTED_SIGNAL,
211 HEADER_ACTIVATED_SIGNAL,
212 ITEM_NOT_FOUND_SIGNAL,
213 MSG_COUNT_CHANGED_SIGNAL,
214 UPDATING_MSG_LIST_SIGNAL,
219 static GObjectClass *parent_class = NULL;
221 /* uncomment the following if you have defined any signals */
222 static guint signals[LAST_SIGNAL] = {0};
225 modest_header_view_get_type (void)
227 static GType my_type = 0;
229 static const GTypeInfo my_info = {
230 sizeof(ModestHeaderViewClass),
231 NULL, /* base init */
232 NULL, /* base finalize */
233 (GClassInitFunc) modest_header_view_class_init,
234 NULL, /* class finalize */
235 NULL, /* class data */
236 sizeof(ModestHeaderView),
238 (GInstanceInitFunc) modest_header_view_init,
242 static const GInterfaceInfo tny_folder_observer_info =
244 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
245 NULL, /* interface_finalize */
246 NULL /* interface_data */
248 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
252 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
253 &tny_folder_observer_info);
261 modest_header_view_class_init (ModestHeaderViewClass *klass)
263 GObjectClass *gobject_class;
264 gobject_class = (GObjectClass*) klass;
266 parent_class = g_type_class_peek_parent (klass);
267 gobject_class->finalize = modest_header_view_finalize;
268 gobject_class->dispose = modest_header_view_dispose;
270 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
272 signals[HEADER_SELECTED_SIGNAL] =
273 g_signal_new ("header_selected",
274 G_TYPE_FROM_CLASS (gobject_class),
276 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
278 g_cclosure_marshal_VOID__POINTER,
279 G_TYPE_NONE, 1, G_TYPE_POINTER);
281 signals[HEADER_ACTIVATED_SIGNAL] =
282 g_signal_new ("header_activated",
283 G_TYPE_FROM_CLASS (gobject_class),
285 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
287 gtk_marshal_VOID__POINTER_POINTER,
288 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
291 signals[ITEM_NOT_FOUND_SIGNAL] =
292 g_signal_new ("item_not_found",
293 G_TYPE_FROM_CLASS (gobject_class),
295 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
297 g_cclosure_marshal_VOID__INT,
298 G_TYPE_NONE, 1, G_TYPE_INT);
300 signals[MSG_COUNT_CHANGED_SIGNAL] =
301 g_signal_new ("msg_count_changed",
302 G_TYPE_FROM_CLASS (gobject_class),
304 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
306 modest_marshal_VOID__POINTER_POINTER,
307 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
309 signals[UPDATING_MSG_LIST_SIGNAL] =
310 g_signal_new ("updating-msg-list",
311 G_TYPE_FROM_CLASS (gobject_class),
313 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
315 g_cclosure_marshal_VOID__BOOLEAN,
316 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
318 #ifdef MODEST_TOOLKIT_HILDON2
319 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
325 tny_folder_observer_init (TnyFolderObserverIface *klass)
327 klass->update = folder_monitor_update;
330 static GtkTreeViewColumn*
331 get_new_column (const gchar *name, GtkCellRenderer *renderer,
332 gboolean resizable, gint sort_col_id, gboolean show_as_text,
333 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
335 GtkTreeViewColumn *column;
337 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
338 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
340 gtk_tree_view_column_set_resizable (column, resizable);
342 gtk_tree_view_column_set_expand (column, TRUE);
345 gtk_tree_view_column_add_attribute (column, renderer, "text",
347 if (sort_col_id >= 0)
348 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
350 gtk_tree_view_column_set_sort_indicator (column, FALSE);
351 gtk_tree_view_column_set_reorderable (column, TRUE);
354 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
361 remove_all_columns (ModestHeaderView *obj)
363 GList *columns, *cursor;
365 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
367 for (cursor = columns; cursor; cursor = cursor->next)
368 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
369 GTK_TREE_VIEW_COLUMN(cursor->data));
370 g_list_free (columns);
374 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
376 GtkTreeModel *sortable, *filter_model;
377 GtkTreeViewColumn *column=NULL;
378 GtkTreeSelection *selection = NULL;
379 GtkCellRenderer *renderer_header,
380 *renderer_attach, *renderer_compact_date_or_status;
381 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
382 *renderer_subject_box, *renderer_recpt,
384 ModestHeaderViewPrivate *priv;
385 GtkTreeViewColumn *compact_column = NULL;
388 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
389 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
391 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
393 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
395 /* TODO: check whether these renderers need to be freed */
396 renderer_attach = gtk_cell_renderer_pixbuf_new ();
397 renderer_priority = gtk_cell_renderer_pixbuf_new ();
398 renderer_header = gtk_cell_renderer_text_new ();
400 renderer_compact_header = modest_vbox_cell_renderer_new ();
401 renderer_recpt_box = modest_hbox_cell_renderer_new ();
402 renderer_subject_box = modest_hbox_cell_renderer_new ();
403 renderer_recpt = gtk_cell_renderer_text_new ();
404 priv->renderer_address = renderer_recpt;
405 priv->renderer_subject = gtk_cell_renderer_text_new ();
406 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
407 priv->renderer_date_status = renderer_compact_date_or_status;
409 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
410 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
411 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
412 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
413 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
414 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
415 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
416 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
417 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
418 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
419 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
420 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
421 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
422 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
424 #ifdef MODEST_TOOLKIT_HILDON2
425 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
427 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
428 #ifndef MODEST_TOOLKIT_GTK
429 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
430 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
432 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
433 g_object_set(G_OBJECT(renderer_header),
434 "ellipsize", PANGO_ELLIPSIZE_END,
436 g_object_set (G_OBJECT (priv->renderer_subject),
437 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
439 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
440 g_object_set (G_OBJECT (renderer_recpt),
441 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
443 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
444 g_object_set(G_OBJECT(renderer_compact_date_or_status),
445 "xalign", 1.0, "yalign", 0.1,
447 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
448 #ifdef MODEST_TOOLKIT_HILDON2
449 g_object_set (G_OBJECT (renderer_priority),
451 "xalign", 0.0, NULL);
452 g_object_set (G_OBJECT (renderer_attach),
454 "xalign", 0.0, NULL);
456 g_object_set (G_OBJECT (renderer_priority),
457 "yalign", 0.5, NULL);
458 g_object_set (G_OBJECT (renderer_attach),
459 "yalign", 0.0, NULL);
462 #ifdef MODEST_TOOLKIT_HILDON1
463 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
464 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
465 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
466 #elif MODEST_TOOLKIT_HILDON2
467 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
468 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
469 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
471 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
472 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
473 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
476 remove_all_columns (self);
478 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
479 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
481 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
482 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
483 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
486 /* Add new columns */
487 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
488 ModestHeaderViewColumn col =
489 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
491 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
492 g_printerr ("modest: invalid column %d in column list\n", col);
498 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
499 column = get_new_column (_("A"), renderer_attach, FALSE,
500 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
502 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
504 gtk_tree_view_column_set_fixed_width (column, 45);
508 case MODEST_HEADER_VIEW_COLUMN_FROM:
509 column = get_new_column (_("From"), renderer_header, TRUE,
510 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
512 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
513 GINT_TO_POINTER(TRUE));
516 case MODEST_HEADER_VIEW_COLUMN_TO:
517 column = get_new_column (_("To"), renderer_header, TRUE,
518 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
520 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
521 GINT_TO_POINTER(FALSE));
524 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
525 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
526 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
528 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
529 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
530 compact_column = column;
533 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
534 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
535 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
537 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
538 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
539 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
540 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
541 compact_column = column;
545 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
546 column = get_new_column (_("Subject"), renderer_header, TRUE,
547 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
549 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
553 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
554 column = get_new_column (_("Received"), renderer_header, TRUE,
555 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
557 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
558 GINT_TO_POINTER(TRUE));
561 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
562 column = get_new_column (_("Sent"), renderer_header, TRUE,
563 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
565 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
566 GINT_TO_POINTER(FALSE));
569 case MODEST_HEADER_VIEW_COLUMN_SIZE:
570 column = get_new_column (_("Size"), renderer_header, TRUE,
571 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
573 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
576 case MODEST_HEADER_VIEW_COLUMN_STATUS:
577 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
578 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
580 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
585 g_return_val_if_reached(FALSE);
588 /* we keep the column id around */
589 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
590 GINT_TO_POINTER(col));
592 /* we need this ptr when sorting the rows */
593 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
595 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
599 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
600 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
601 (GtkTreeIterCompareFunc) cmp_rows,
602 compact_column, NULL);
603 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
604 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
605 (GtkTreeIterCompareFunc) cmp_subject_rows,
606 compact_column, NULL);
610 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
616 datetime_format_changed (ModestDatetimeFormatter *formatter,
617 ModestHeaderView *self)
619 gtk_widget_queue_draw (GTK_WIDGET (self));
623 modest_header_view_init (ModestHeaderView *obj)
625 ModestHeaderViewPrivate *priv;
628 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
630 priv->show_latest = 0;
633 priv->is_outbox = FALSE;
635 priv->monitor = NULL;
636 priv->observers_lock = g_mutex_new ();
637 priv->autoselect_reference = NULL;
639 priv->status = HEADER_VIEW_INIT;
640 priv->status_timeout = 0;
641 priv->notify_status = TRUE;
643 priv->observer_list_lock = g_mutex_new();
644 priv->observer_list = NULL;
646 priv->clipboard = modest_runtime_get_email_clipboard ();
647 priv->hidding_ids = NULL;
648 priv->n_selected = 0;
649 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
650 #ifdef MODEST_TOOLKIT_HILDON2
651 priv->live_search = NULL;
652 priv->live_search_timeout = 0;
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 #ifdef MODEST_TOOLKIT_HILDON2
686 if (priv->live_search_timeout > 0) {
687 g_source_remove (priv->live_search_timeout);
688 priv->live_search_timeout = 0;
692 if (priv->datetime_formatter) {
693 g_object_unref (priv->datetime_formatter);
694 priv->datetime_formatter = NULL;
697 /* Free in the dispose to avoid unref cycles */
699 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
700 g_object_unref (G_OBJECT (priv->folder));
704 /* We need to do this here in the dispose because the
705 selection won't exist when finalizing */
706 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
707 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
708 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
709 priv->selection_changed_handler = 0;
712 G_OBJECT_CLASS(parent_class)->dispose (obj);
716 modest_header_view_finalize (GObject *obj)
718 ModestHeaderView *self;
719 ModestHeaderViewPrivate *priv;
721 self = MODEST_HEADER_VIEW(obj);
722 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
724 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
725 priv->acc_removed_handler)) {
726 g_signal_handler_disconnect (modest_runtime_get_account_store (),
727 priv->acc_removed_handler);
730 /* There is no need to lock because there should not be any
731 * reference to self now. */
732 g_mutex_free(priv->observer_list_lock);
733 g_slist_free(priv->observer_list);
735 g_mutex_lock (priv->observers_lock);
737 tny_folder_monitor_stop (priv->monitor);
738 g_object_unref (G_OBJECT (priv->monitor));
740 g_mutex_unlock (priv->observers_lock);
741 g_mutex_free (priv->observers_lock);
743 /* Clear hidding array created by cut operation */
744 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
746 if (priv->autoselect_reference != NULL) {
747 gtk_tree_row_reference_free (priv->autoselect_reference);
748 priv->autoselect_reference = NULL;
751 if (priv->filter_string) {
752 g_free (priv->filter_string);
755 if (priv->filter_string_splitted) {
756 g_strfreev (priv->filter_string_splitted);
759 G_OBJECT_CLASS(parent_class)->finalize (obj);
764 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
767 GtkTreeSelection *sel;
768 ModestHeaderView *self;
769 ModestHeaderViewPrivate *priv;
771 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
774 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
775 self = MODEST_HEADER_VIEW(obj);
776 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
778 modest_header_view_set_style (self, style);
780 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
781 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
782 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
784 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
785 TRUE); /* alternating row colors */
787 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
788 priv->selection_changed_handler =
789 g_signal_connect_after (sel, "changed",
790 G_CALLBACK(on_selection_changed), self);
792 g_signal_connect (self, "row-activated",
793 G_CALLBACK (on_header_row_activated), NULL);
795 #ifndef MODEST_TOOLKIT_HILDON2
796 g_signal_connect (self, "focus-in-event",
797 G_CALLBACK(on_focus_in), NULL);
798 g_signal_connect (self, "focus-out-event",
799 G_CALLBACK(on_focus_out), NULL);
802 g_signal_connect (self, "button-press-event",
803 G_CALLBACK(on_button_press_event), NULL);
804 g_signal_connect (self, "button-release-event",
805 G_CALLBACK(on_button_release_event), NULL);
807 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
809 G_CALLBACK (on_account_removed),
812 g_signal_connect (self, "expose-event",
813 G_CALLBACK(modest_header_view_on_expose_event),
816 return GTK_WIDGET(self);
821 modest_header_view_count_selected_headers (ModestHeaderView *self)
823 GtkTreeSelection *sel;
826 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
828 /* Get selection object and check selected rows count */
829 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
830 selected_rows = gtk_tree_selection_count_selected_rows (sel);
832 return selected_rows;
836 modest_header_view_has_selected_headers (ModestHeaderView *self)
838 GtkTreeSelection *sel;
841 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
843 /* Get selection object and check selected rows count */
844 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
845 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
852 modest_header_view_get_selected_headers (ModestHeaderView *self)
854 GtkTreeSelection *sel;
855 TnyList *header_list = NULL;
857 GList *list, *tmp = NULL;
858 GtkTreeModel *tree_model = NULL;
861 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
864 /* Get selected rows */
865 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
866 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
869 header_list = tny_simple_list_new();
871 list = g_list_reverse (list);
874 /* get header from selection */
875 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
876 gtk_tree_model_get (tree_model, &iter,
877 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
879 /* Prepend to list */
880 tny_list_prepend (header_list, G_OBJECT (header));
881 g_object_unref (G_OBJECT (header));
883 tmp = g_list_next (tmp);
886 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
893 /* scroll our list view so the selected item is visible */
895 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
897 #ifdef MODEST_TOOLKIT_GTK
899 GtkTreePath *selected_path;
900 GtkTreePath *start, *end;
904 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
905 selected_path = gtk_tree_model_get_path (model, iter);
907 start = gtk_tree_path_new ();
908 end = gtk_tree_path_new ();
910 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
912 if (gtk_tree_path_compare (selected_path, start) < 0 ||
913 gtk_tree_path_compare (end, selected_path) < 0)
914 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
915 selected_path, NULL, TRUE,
918 gtk_tree_path_free (selected_path);
919 gtk_tree_path_free (start);
920 gtk_tree_path_free (end);
922 #endif /* MODEST_TOOLKIT_GTK */
927 modest_header_view_select_next (ModestHeaderView *self)
929 GtkTreeSelection *sel;
934 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
936 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
937 path = get_selected_row (GTK_TREE_VIEW(self), &model);
938 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
939 /* Unselect previous path */
940 gtk_tree_selection_unselect_path (sel, path);
942 /* Move path down and selects new one */
943 if (gtk_tree_model_iter_next (model, &iter)) {
944 gtk_tree_selection_select_iter (sel, &iter);
945 scroll_to_selected (self, &iter, FALSE);
947 gtk_tree_path_free(path);
953 modest_header_view_select_prev (ModestHeaderView *self)
955 GtkTreeSelection *sel;
960 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
962 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
963 path = get_selected_row (GTK_TREE_VIEW(self), &model);
964 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
965 /* Unselect previous path */
966 gtk_tree_selection_unselect_path (sel, path);
969 if (gtk_tree_path_prev (path)) {
970 gtk_tree_model_get_iter (model, &iter, path);
972 /* Select the new one */
973 gtk_tree_selection_select_iter (sel, &iter);
974 scroll_to_selected (self, &iter, TRUE);
977 gtk_tree_path_free (path);
982 modest_header_view_get_columns (ModestHeaderView *self)
984 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
986 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
992 modest_header_view_set_style (ModestHeaderView *self,
993 ModestHeaderViewStyle style)
995 ModestHeaderViewPrivate *priv;
996 gboolean show_col_headers = FALSE;
997 ModestHeaderViewStyle old_style;
999 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1000 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
1003 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1004 if (priv->style == style)
1005 return TRUE; /* nothing to do */
1008 case MODEST_HEADER_VIEW_STYLE_DETAILS:
1009 show_col_headers = TRUE;
1011 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1014 g_return_val_if_reached (FALSE);
1016 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1017 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1019 old_style = priv->style;
1020 priv->style = style;
1026 ModestHeaderViewStyle
1027 modest_header_view_get_style (ModestHeaderView *self)
1029 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1031 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1034 /* This is used to automatically select the first header if the user
1035 * has not selected any header yet.
1038 modest_header_view_on_expose_event(GtkTreeView *header_view,
1039 GdkEventExpose *event,
1042 GtkTreeSelection *sel;
1043 GtkTreeModel *model;
1044 GtkTreeIter tree_iter;
1045 ModestHeaderViewPrivate *priv;
1047 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1048 model = gtk_tree_view_get_model(header_view);
1053 #ifdef MODEST_TOOLKIT_HILDON2
1056 sel = gtk_tree_view_get_selection(header_view);
1057 if(!gtk_tree_selection_count_selected_rows(sel)) {
1058 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1059 GtkTreePath *tree_iter_path;
1060 /* Prevent the widget from getting the focus
1061 when selecting the first item */
1062 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1063 g_object_set(header_view, "can-focus", FALSE, NULL);
1064 gtk_tree_selection_select_iter(sel, &tree_iter);
1065 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1066 g_object_set(header_view, "can-focus", TRUE, NULL);
1067 if (priv->autoselect_reference) {
1068 gtk_tree_row_reference_free (priv->autoselect_reference);
1070 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1071 gtk_tree_path_free (tree_iter_path);
1074 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1075 gboolean moved_selection = FALSE;
1076 GtkTreePath * last_path;
1077 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1078 moved_selection = TRUE;
1082 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1083 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1084 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1085 moved_selection = TRUE;
1086 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1088 gtk_tree_path_free (last_path);
1090 if (moved_selection) {
1091 gtk_tree_row_reference_free (priv->autoselect_reference);
1092 priv->autoselect_reference = NULL;
1095 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1096 GtkTreePath *current_path;
1097 current_path = gtk_tree_model_get_path (model, &tree_iter);
1098 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1099 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1100 g_object_set(header_view, "can-focus", FALSE, NULL);
1101 gtk_tree_selection_unselect_all (sel);
1102 gtk_tree_selection_select_iter(sel, &tree_iter);
1103 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1104 g_object_set(header_view, "can-focus", TRUE, NULL);
1105 gtk_tree_row_reference_free (priv->autoselect_reference);
1106 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1108 gtk_tree_path_free (current_path);
1109 gtk_tree_path_free (last_path);
1119 modest_header_view_get_folder (ModestHeaderView *self)
1121 ModestHeaderViewPrivate *priv;
1123 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1125 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1128 g_object_ref (priv->folder);
1130 return priv->folder;
1134 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1140 ModestHeaderView *self;
1141 ModestHeaderViewPrivate *priv;
1143 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1145 self = MODEST_HEADER_VIEW (user_data);
1146 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1148 if (cancelled || err)
1151 /* Add IDLE observer (monitor) and another folder observer for
1152 new messages (self) */
1153 g_mutex_lock (priv->observers_lock);
1154 if (priv->monitor) {
1155 tny_folder_monitor_stop (priv->monitor);
1156 g_object_unref (G_OBJECT (priv->monitor));
1158 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1159 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1160 tny_folder_monitor_start (priv->monitor);
1161 g_mutex_unlock (priv->observers_lock);
1165 modest_header_view_set_folder_intern (ModestHeaderView *self,
1171 ModestHeaderViewPrivate *priv;
1172 GList *cols, *cursor;
1173 GtkTreeModel *filter_model, *sortable;
1175 GtkSortType sort_type;
1177 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1179 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1180 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (headers), priv->show_latest);
1182 /* Start the monitor in the callback of the
1183 tny_gtk_header_list_model_set_folder call. It's crucial to
1184 do it there and not just after the call because we want the
1185 monitor to observe only the headers returned by the
1186 tny_folder_get_headers_async call that it's inside the
1187 tny_gtk_header_list_model_set_folder call. This way the
1188 monitor infrastructure could successfully cope with
1189 duplicates. For example if a tny_folder_add_msg_async is
1190 happening while tny_gtk_header_list_model_set_folder is
1191 invoked, then the first call could add a header that will
1192 be added again by tny_gtk_header_list_model_set_folder, so
1193 we'd end up with duplicate headers. sergio */
1194 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1196 set_folder_intern_get_headers_async_cb,
1199 /* Init filter_row function to examine empty status */
1200 priv->status = HEADER_VIEW_INIT;
1202 /* Create sortable model */
1203 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1204 g_object_unref (headers);
1206 /* Create a tree model filter to hide and show rows for cut operations */
1207 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1208 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1209 filter_row, self, NULL);
1210 g_object_unref (sortable);
1212 /* install our special sorting functions */
1213 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1215 /* Restore sort column id */
1217 type = modest_tny_folder_guess_folder_type (folder);
1218 if (type == TNY_FOLDER_TYPE_INVALID)
1219 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1221 sort_colid = modest_header_view_get_sort_column_id (self, type);
1222 sort_type = modest_header_view_get_sort_type (self, type);
1223 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1226 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1227 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1228 (GtkTreeIterCompareFunc) cmp_rows,
1230 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1231 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1232 (GtkTreeIterCompareFunc) cmp_subject_rows,
1237 gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1238 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1239 g_object_unref (filter_model);
1246 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1248 GtkSortType sort_type)
1250 ModestHeaderViewPrivate *priv = NULL;
1251 GtkTreeModel *sortable = NULL, *filter_model = NULL;
1254 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1255 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1257 /* Get model and private data */
1258 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1259 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1260 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1261 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1264 /* Sort tree model */
1265 type = modest_tny_folder_guess_folder_type (priv->folder);
1266 if (type == TNY_FOLDER_TYPE_INVALID)
1267 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1269 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1272 /* Store new sort parameters */
1273 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1278 modest_header_view_set_sort_params (ModestHeaderView *self,
1280 GtkSortType sort_type,
1283 ModestHeaderViewPrivate *priv;
1284 ModestHeaderViewStyle style;
1286 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1287 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1288 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1290 style = modest_header_view_get_style (self);
1291 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1293 priv->sort_colid[style][type] = sort_colid;
1294 priv->sort_type[style][type] = sort_type;
1298 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1301 ModestHeaderViewPrivate *priv;
1302 ModestHeaderViewStyle style;
1304 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1305 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1307 style = modest_header_view_get_style (self);
1308 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1310 return priv->sort_colid[style][type];
1314 modest_header_view_get_sort_type (ModestHeaderView *self,
1317 ModestHeaderViewPrivate *priv;
1318 ModestHeaderViewStyle style;
1320 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1321 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1323 style = modest_header_view_get_style (self);
1324 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1326 return priv->sort_type[style][type];
1330 ModestHeaderView *header_view;
1331 RefreshAsyncUserCallback cb;
1336 folder_refreshed_cb (ModestMailOperation *mail_op,
1340 ModestHeaderViewPrivate *priv;
1341 SetFolderHelper *info;
1343 info = (SetFolderHelper*) user_data;
1345 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1349 info->cb (mail_op, folder, info->user_data);
1351 /* Start the folder count changes observer. We do not need it
1352 before the refresh. Note that the monitor could still be
1353 called for this refresh but now we know that the callback
1354 was previously called */
1355 g_mutex_lock (priv->observers_lock);
1356 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1357 g_mutex_unlock (priv->observers_lock);
1359 /* Notify the observers that the update is over */
1360 g_signal_emit (G_OBJECT (info->header_view),
1361 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1363 /* Allow filtering notifications from now on if the current
1364 folder is still the same (if not then the user has selected
1365 another one to refresh, we should wait until that refresh
1367 if (priv->folder == folder)
1368 priv->notify_status = TRUE;
1371 g_object_unref (info->header_view);
1376 refresh_folder_error_handler (ModestMailOperation *mail_op,
1379 const GError *error = modest_mail_operation_get_error (mail_op);
1381 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1382 error->code == TNY_IO_ERROR_WRITE ||
1383 error->code == TNY_IO_ERROR_READ) {
1384 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1385 /* If the mail op has been cancelled then it's not an error: don't show any message */
1386 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1387 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1388 modest_platform_information_banner (NULL, NULL, msg);
1395 modest_header_view_set_folder (ModestHeaderView *self,
1398 ModestWindow *progress_window,
1399 RefreshAsyncUserCallback callback,
1402 ModestHeaderViewPrivate *priv;
1404 g_return_if_fail (self);
1406 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1409 if (priv->status_timeout) {
1410 g_source_remove (priv->status_timeout);
1411 priv->status_timeout = 0;
1414 g_mutex_lock (priv->observers_lock);
1415 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1416 g_object_unref (priv->folder);
1417 priv->folder = NULL;
1418 g_mutex_unlock (priv->observers_lock);
1422 GtkTreeSelection *selection;
1423 SetFolderHelper *info;
1424 ModestMailOperation *mail_op = NULL;
1426 /* Set folder in the model */
1427 modest_header_view_set_folder_intern (self, folder, refresh);
1429 /* Pick my reference. Nothing to do with the mail operation */
1430 priv->folder = g_object_ref (folder);
1432 /* Do not notify about filterings until the refresh finishes */
1433 priv->notify_status = FALSE;
1435 /* Clear the selection if exists */
1436 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1437 gtk_tree_selection_unselect_all(selection);
1438 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1440 /* Notify the observers that the update begins */
1441 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1444 /* create the helper */
1445 info = g_malloc0 (sizeof (SetFolderHelper));
1446 info->header_view = g_object_ref (self);
1447 info->cb = callback;
1448 info->user_data = user_data;
1450 /* Create the mail operation (source will be the parent widget) */
1451 if (progress_window)
1452 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1453 refresh_folder_error_handler,
1456 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1459 /* Refresh the folder asynchronously */
1460 modest_mail_operation_refresh_folder (mail_op,
1462 folder_refreshed_cb,
1465 folder_refreshed_cb (mail_op, folder, info);
1469 g_object_unref (mail_op);
1471 g_mutex_lock (priv->observers_lock);
1473 if (priv->monitor) {
1474 tny_folder_monitor_stop (priv->monitor);
1475 g_object_unref (G_OBJECT (priv->monitor));
1476 priv->monitor = NULL;
1479 if (priv->autoselect_reference) {
1480 gtk_tree_row_reference_free (priv->autoselect_reference);
1481 priv->autoselect_reference = NULL;
1484 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1486 modest_header_view_notify_observers(self, NULL, NULL);
1488 g_mutex_unlock (priv->observers_lock);
1490 /* Notify the observers that the update is over */
1491 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1497 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1498 GtkTreeViewColumn *column, gpointer userdata)
1500 ModestHeaderView *self = NULL;
1502 GtkTreeModel *model = NULL;
1503 TnyHeader *header = NULL;
1504 TnyHeaderFlags flags;
1506 self = MODEST_HEADER_VIEW (treeview);
1508 model = gtk_tree_view_get_model (treeview);
1509 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1512 /* get the first selected item */
1513 gtk_tree_model_get (model, &iter,
1514 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1515 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1518 /* Dont open DELETED messages */
1519 if (flags & TNY_HEADER_FLAG_DELETED) {
1522 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1523 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1524 modest_platform_information_banner (NULL, NULL, msg);
1530 g_signal_emit (G_OBJECT(self),
1531 signals[HEADER_ACTIVATED_SIGNAL],
1537 g_object_unref (G_OBJECT (header));
1542 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1544 GtkTreeModel *model;
1545 TnyHeader *header = NULL;
1546 GtkTreePath *path = NULL;
1548 ModestHeaderView *self;
1549 GList *selected = NULL;
1551 g_return_if_fail (sel);
1552 g_return_if_fail (user_data);
1554 self = MODEST_HEADER_VIEW (user_data);
1556 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1557 if (selected != NULL)
1558 path = (GtkTreePath *) selected->data;
1559 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1560 return; /* msg was _un_selected */
1562 gtk_tree_model_get (model, &iter,
1563 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1567 g_signal_emit (G_OBJECT(self),
1568 signals[HEADER_SELECTED_SIGNAL],
1571 g_object_unref (G_OBJECT (header));
1573 /* free all items in 'selected' */
1574 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1575 g_list_free (selected);
1579 /* PROTECTED method. It's useful when we want to force a given
1580 selection to reload a msg. For example if we have selected a header
1581 in offline mode, when Modest become online, we want to reload the
1582 message automatically without an user click over the header */
1584 _modest_header_view_change_selection (GtkTreeSelection *selection,
1587 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1588 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1590 on_selection_changed (selection, user_data);
1594 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1601 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1605 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1609 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1617 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1625 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1626 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1630 case TNY_HEADER_FLAG_ATTACHMENTS:
1632 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1633 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1634 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1635 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1637 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1638 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1640 return cmp ? cmp : t1 - t2;
1642 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1643 TnyHeader *header1 = NULL, *header2 = NULL;
1645 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1646 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1647 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1648 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1650 /* This is for making priority values respect the intuitive sort relationship
1651 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1653 if (header1 && header2) {
1654 cmp = compare_priorities (tny_header_get_priority (header1),
1655 tny_header_get_priority (header2));
1656 g_object_unref (header1);
1657 g_object_unref (header2);
1659 return cmp ? cmp : t1 - t2;
1665 return &iter1 - &iter2; /* oughhhh */
1670 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1677 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1679 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1680 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1681 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1682 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1684 /* Do not use the prefixes for sorting. Consume all the blank
1685 spaces for sorting */
1686 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1687 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1690 /* If they're equal based on subject without prefix then just
1691 sort them by length. This will show messages like this.
1698 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1705 /* Drag and drop stuff */
1707 drag_data_get_cb (GtkWidget *widget,
1708 GdkDragContext *context,
1709 GtkSelectionData *selection_data,
1714 ModestHeaderView *self = NULL;
1715 ModestHeaderViewPrivate *priv = NULL;
1717 self = MODEST_HEADER_VIEW (widget);
1718 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1720 /* Set the data. Do not use the current selection because it
1721 could be different than the selection at the beginning of
1723 modest_dnd_selection_data_set_paths (selection_data,
1724 priv->drag_begin_cached_selected_rows);
1728 * We're caching the selected rows at the beginning because the
1729 * selection could change between drag-begin and drag-data-get, for
1730 * example if we have a set of rows already selected, and then we
1731 * click in one of them (without SHIFT key pressed) and begin a drag,
1732 * the selection at that moment contains all the selected lines, but
1733 * after dropping the selection, the release event provokes that only
1734 * the row used to begin the drag is selected, so at the end the
1735 * drag&drop affects only one rows instead of all the selected ones.
1739 drag_begin_cb (GtkWidget *widget,
1740 GdkDragContext *context,
1743 ModestHeaderView *self = NULL;
1744 ModestHeaderViewPrivate *priv = NULL;
1745 GtkTreeSelection *selection;
1747 self = MODEST_HEADER_VIEW (widget);
1748 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1750 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1751 priv->drag_begin_cached_selected_rows =
1752 gtk_tree_selection_get_selected_rows (selection, NULL);
1756 * We use the drag-end signal to clear the cached selection, we use
1757 * this because this allways happens, whether or not the d&d was a
1761 drag_end_cb (GtkWidget *widget,
1765 ModestHeaderView *self = NULL;
1766 ModestHeaderViewPrivate *priv = NULL;
1768 self = MODEST_HEADER_VIEW (widget);
1769 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1771 /* Free cached data */
1772 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1773 g_list_free (priv->drag_begin_cached_selected_rows);
1774 priv->drag_begin_cached_selected_rows = NULL;
1777 /* Header view drag types */
1778 const GtkTargetEntry header_view_drag_types[] = {
1779 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1783 enable_drag_and_drop (GtkWidget *self)
1785 #ifdef MODEST_TOOLKIT_HILDON2
1788 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1789 header_view_drag_types,
1790 G_N_ELEMENTS (header_view_drag_types),
1791 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1795 disable_drag_and_drop (GtkWidget *self)
1797 #ifdef MODEST_TOOLKIT_HILDON2
1800 gtk_drag_source_unset (self);
1804 setup_drag_and_drop (GtkWidget *self)
1806 #ifdef MODEST_TOOLKIT_HILDON2
1809 enable_drag_and_drop(self);
1810 g_signal_connect(G_OBJECT (self), "drag_data_get",
1811 G_CALLBACK(drag_data_get_cb), NULL);
1813 g_signal_connect(G_OBJECT (self), "drag_begin",
1814 G_CALLBACK(drag_begin_cb), NULL);
1816 g_signal_connect(G_OBJECT (self), "drag_end",
1817 G_CALLBACK(drag_end_cb), NULL);
1820 static GtkTreePath *
1821 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1823 GtkTreePath *path = NULL;
1824 GtkTreeSelection *sel = NULL;
1827 sel = gtk_tree_view_get_selection(self);
1828 rows = gtk_tree_selection_get_selected_rows (sel, model);
1830 if ((rows == NULL) || (g_list_length(rows) != 1))
1833 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1838 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1844 #ifndef MODEST_TOOLKIT_HILDON2
1846 * This function moves the tree view scroll to the current selected
1847 * row when the widget grabs the focus
1850 on_focus_in (GtkWidget *self,
1851 GdkEventFocus *event,
1854 GtkTreeSelection *selection;
1855 GtkTreeModel *model;
1856 GList *selected = NULL;
1857 GtkTreePath *selected_path = NULL;
1859 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1863 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1864 /* If none selected yet, pick the first one */
1865 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1869 /* Return if the model is empty */
1870 if (!gtk_tree_model_get_iter_first (model, &iter))
1873 path = gtk_tree_model_get_path (model, &iter);
1874 gtk_tree_selection_select_path (selection, path);
1875 gtk_tree_path_free (path);
1878 /* Need to get the all the rows because is selection multiple */
1879 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1880 if (selected == NULL) return FALSE;
1881 selected_path = (GtkTreePath *) selected->data;
1884 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1885 g_list_free (selected);
1891 on_focus_out (GtkWidget *self,
1892 GdkEventFocus *event,
1896 if (!gtk_widget_is_focus (self)) {
1897 GtkTreeSelection *selection = NULL;
1898 GList *selected_rows = NULL;
1899 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1900 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1901 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1902 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1903 gtk_tree_selection_unselect_all (selection);
1904 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1905 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1906 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1907 g_list_free (selected_rows);
1915 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1917 enable_drag_and_drop(self);
1922 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1924 GtkTreeSelection *selection = NULL;
1925 GtkTreePath *path = NULL;
1926 gboolean already_selected = FALSE, already_opened = FALSE;
1927 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1929 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1931 GtkTreeModel *model;
1933 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1934 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1936 /* Get header from model */
1937 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1938 if (gtk_tree_model_get_iter (model, &iter, path)) {
1939 GValue value = {0,};
1942 gtk_tree_model_get_value (model, &iter,
1943 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1945 header = (TnyHeader *) g_value_get_object (&value);
1946 if (TNY_IS_HEADER (header)) {
1947 status = modest_tny_all_send_queues_get_msg_status (header);
1948 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1951 g_value_unset (&value);
1955 /* Enable drag and drop only if the user clicks on a row that
1956 it's already selected. If not, let him select items using
1957 the pointer. If the message is in an OUTBOX and in sending
1958 status disable drag and drop as well */
1959 if (!already_selected ||
1960 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1962 disable_drag_and_drop(self);
1965 gtk_tree_path_free(path);
1967 /* If it's already opened then do not let the button-press
1968 event go on because it'll perform a message open because
1969 we're clicking on to an already selected header */
1974 folder_monitor_update (TnyFolderObserver *self,
1975 TnyFolderChange *change)
1977 ModestHeaderViewPrivate *priv = NULL;
1978 TnyFolderChangeChanged changed;
1979 TnyFolder *folder = NULL;
1981 changed = tny_folder_change_get_changed (change);
1983 /* Do not notify the observers if the folder of the header
1984 view has changed before this call to the observer
1986 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1987 folder = tny_folder_change_get_folder (change);
1988 if (folder != priv->folder)
1991 MODEST_DEBUG_BLOCK (
1992 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1993 g_print ("ADDED %d/%d (r/t) \n",
1994 tny_folder_change_get_new_unread_count (change),
1995 tny_folder_change_get_new_all_count (change));
1996 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1997 g_print ("ALL COUNT %d\n",
1998 tny_folder_change_get_new_all_count (change));
1999 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
2000 g_print ("UNREAD COUNT %d\n",
2001 tny_folder_change_get_new_unread_count (change));
2002 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
2003 g_print ("EXPUNGED %d/%d (r/t) \n",
2004 tny_folder_change_get_new_unread_count (change),
2005 tny_folder_change_get_new_all_count (change));
2006 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
2007 g_print ("FOLDER RENAME\n");
2008 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2009 g_print ("MSG RECEIVED %d/%d (r/t) \n",
2010 tny_folder_change_get_new_unread_count (change),
2011 tny_folder_change_get_new_all_count (change));
2012 g_print ("---------------------------------------------------\n");
2015 /* Check folder count */
2016 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2017 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2019 g_mutex_lock (priv->observers_lock);
2021 /* Emit signal to evaluate how headers changes affects
2022 to the window view */
2023 g_signal_emit (G_OBJECT(self),
2024 signals[MSG_COUNT_CHANGED_SIGNAL],
2027 /* Added or removed headers, so data stored on cliboard are invalid */
2028 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2029 modest_email_clipboard_clear (priv->clipboard);
2031 g_mutex_unlock (priv->observers_lock);
2037 g_object_unref (folder);
2041 modest_header_view_is_empty (ModestHeaderView *self)
2043 ModestHeaderViewPrivate *priv;
2045 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2047 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2049 return priv->status == HEADER_VIEW_EMPTY;
2053 modest_header_view_clear (ModestHeaderView *self)
2055 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2057 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2061 modest_header_view_copy_selection (ModestHeaderView *header_view)
2063 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2065 /* Copy selection */
2066 _clipboard_set_selected_data (header_view, FALSE);
2070 modest_header_view_cut_selection (ModestHeaderView *header_view)
2072 ModestHeaderViewPrivate *priv = NULL;
2073 const gchar **hidding = NULL;
2074 guint i, n_selected;
2076 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2078 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2080 /* Copy selection */
2081 _clipboard_set_selected_data (header_view, TRUE);
2083 /* Get hidding ids */
2084 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2086 /* Clear hidding array created by previous cut operation */
2087 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2089 /* Copy hidding array */
2090 priv->n_selected = n_selected;
2091 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2092 for (i=0; i < n_selected; i++)
2093 priv->hidding_ids[i] = g_strdup(hidding[i]);
2095 /* Hide cut headers */
2096 modest_header_view_refilter (header_view);
2103 _clipboard_set_selected_data (ModestHeaderView *header_view,
2106 ModestHeaderViewPrivate *priv = NULL;
2107 TnyList *headers = NULL;
2109 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2110 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2112 /* Set selected data on clipboard */
2113 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2114 headers = modest_header_view_get_selected_headers (header_view);
2115 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2118 g_object_unref (headers);
2122 ModestHeaderView *self;
2127 notify_filter_change (gpointer data)
2129 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2131 g_signal_emit (info->self,
2132 signals[MSG_COUNT_CHANGED_SIGNAL],
2133 0, info->folder, NULL);
2139 notify_filter_change_destroy (gpointer data)
2141 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2142 ModestHeaderViewPrivate *priv;
2144 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2145 priv->status_timeout = 0;
2147 g_object_unref (info->self);
2148 g_object_unref (info->folder);
2149 g_slice_free (NotifyFilterInfo, info);
2153 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2155 /* For the moment we only need to filter outbox */
2156 return priv->is_outbox;
2160 header_match_string (TnyHeader *header, gchar **words)
2167 gchar *subject_fold;
2173 gchar **current_word;
2176 subject = tny_header_dup_subject (header);
2177 cc = tny_header_dup_cc (header);
2178 bcc = tny_header_dup_bcc (header);
2179 to = tny_header_dup_to (header);
2180 from = tny_header_dup_from (header);
2182 subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2184 bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2186 cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2188 to_fold = to?g_utf8_casefold (to, -1):NULL;
2190 from_fold = from?g_utf8_casefold (from, -1):NULL;
2195 for (current_word = words; *current_word != NULL; current_word++) {
2197 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2198 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2199 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2200 || (to && g_strstr_len (to_fold, -1, *current_word))
2201 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2209 g_free (subject_fold);
2219 filter_row (GtkTreeModel *model,
2223 ModestHeaderViewPrivate *priv = NULL;
2224 TnyHeaderFlags flags;
2225 TnyHeader *header = NULL;
2228 gboolean visible = TRUE;
2229 gboolean found = FALSE;
2230 GValue value = {0,};
2231 HeaderViewStatus old_status;
2233 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2234 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2236 /* Get header from model */
2237 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2238 flags = (TnyHeaderFlags) g_value_get_int (&value);
2239 g_value_unset (&value);
2240 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2241 header = (TnyHeader *) g_value_get_object (&value);
2242 g_value_unset (&value);
2244 /* Get message id from header (ensure is a valid id) */
2250 /* Hide deleted and mark as deleted heders */
2251 if (flags & TNY_HEADER_FLAG_DELETED ||
2252 flags & TNY_HEADER_FLAG_EXPUNGED) {
2257 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2258 if (current_folder_needs_filtering (priv) &&
2259 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2265 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2266 if (current_folder_needs_filtering (priv) &&
2267 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2273 if (visible && priv->filter_string) {
2274 if (!header_match_string (header, priv->filter_string_splitted)) {
2278 if (priv->filter_date_range) {
2279 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2280 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2287 /* If no data on clipboard, return always TRUE */
2288 if (modest_email_clipboard_cleared(priv->clipboard)) {
2294 if (priv->hidding_ids != NULL) {
2295 id = tny_header_dup_message_id (header);
2296 for (i=0; i < priv->n_selected && !found; i++)
2297 if (priv->hidding_ids[i] != NULL && id != NULL)
2298 found = (!strcmp (priv->hidding_ids[i], id));
2305 old_status = priv->status;
2306 priv->status = ((gboolean) priv->status) && !visible;
2307 if ((priv->notify_status) && (priv->status != old_status)) {
2308 if (priv->status_timeout)
2309 g_source_remove (priv->status_timeout);
2312 NotifyFilterInfo *info;
2314 info = g_slice_new0 (NotifyFilterInfo);
2315 info->self = g_object_ref (G_OBJECT (user_data));
2317 info->folder = tny_header_get_folder (header);
2318 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2319 notify_filter_change,
2321 notify_filter_change_destroy);
2329 _clear_hidding_filter (ModestHeaderView *header_view)
2331 ModestHeaderViewPrivate *priv = NULL;
2334 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2335 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2337 if (priv->hidding_ids != NULL) {
2338 for (i=0; i < priv->n_selected; i++)
2339 g_free (priv->hidding_ids[i]);
2340 g_free(priv->hidding_ids);
2345 modest_header_view_refilter (ModestHeaderView *header_view)
2347 GtkTreeModel *filter_model = NULL;
2348 ModestHeaderViewPrivate *priv = NULL;
2350 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2351 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2353 /* Hide cut headers */
2354 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2355 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2356 priv->status = HEADER_VIEW_INIT;
2357 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2362 * Called when an account is removed. If I'm showing a folder of the
2363 * account that has been removed then clear the view
2366 on_account_removed (TnyAccountStore *self,
2367 TnyAccount *account,
2370 ModestHeaderViewPrivate *priv = NULL;
2372 /* Ignore changes in transport accounts */
2373 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2376 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2379 TnyAccount *my_account;
2381 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2382 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2383 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2384 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2386 my_account = tny_folder_get_account (priv->folder);
2390 if (my_account == account)
2391 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2392 g_object_unref (my_account);
2398 modest_header_view_add_observer(ModestHeaderView *header_view,
2399 ModestHeaderViewObserver *observer)
2401 ModestHeaderViewPrivate *priv;
2403 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2404 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2406 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2408 g_mutex_lock(priv->observer_list_lock);
2409 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2410 g_mutex_unlock(priv->observer_list_lock);
2414 modest_header_view_remove_observer(ModestHeaderView *header_view,
2415 ModestHeaderViewObserver *observer)
2417 ModestHeaderViewPrivate *priv;
2419 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2420 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2422 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2424 g_mutex_lock(priv->observer_list_lock);
2425 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2426 g_mutex_unlock(priv->observer_list_lock);
2430 modest_header_view_notify_observers(ModestHeaderView *header_view,
2431 GtkTreeModel *model,
2432 const gchar *tny_folder_id)
2434 ModestHeaderViewPrivate *priv = NULL;
2436 ModestHeaderViewObserver *observer;
2439 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2441 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2443 g_mutex_lock(priv->observer_list_lock);
2444 iter = priv->observer_list;
2445 while(iter != NULL){
2446 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2447 modest_header_view_observer_update(observer, model,
2449 iter = g_slist_next(iter);
2451 g_mutex_unlock(priv->observer_list_lock);
2455 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2457 ModestHeaderViewPrivate *priv = NULL;
2459 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2460 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2464 modest_header_view_set_filter (ModestHeaderView *self,
2465 ModestHeaderViewFilter filter)
2467 ModestHeaderViewPrivate *priv;
2469 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2470 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2472 priv->filter |= filter;
2474 if (current_folder_needs_filtering (priv))
2475 modest_header_view_refilter (self);
2479 modest_header_view_unset_filter (ModestHeaderView *self,
2480 ModestHeaderViewFilter filter)
2482 ModestHeaderViewPrivate *priv;
2484 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2485 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2487 priv->filter &= ~filter;
2489 if (current_folder_needs_filtering (priv))
2490 modest_header_view_refilter (self);
2494 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2496 if (strcmp ("style", spec->name) == 0) {
2497 update_style (MODEST_HEADER_VIEW (obj));
2498 gtk_widget_queue_draw (GTK_WIDGET (obj));
2503 update_style (ModestHeaderView *self)
2505 ModestHeaderViewPrivate *priv;
2506 GdkColor style_color;
2507 GdkColor style_active_color;
2508 PangoAttrList *attr_list;
2510 PangoAttribute *attr;
2512 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2513 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2517 attr_list = pango_attr_list_new ();
2518 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
2519 gdk_color_parse ("grey", &style_color);
2521 priv->secondary_color = style_color;
2522 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2523 pango_attr_list_insert (attr_list, attr);
2526 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2528 "SmallSystemFont", NULL,
2531 attr = pango_attr_font_desc_new (pango_font_description_copy
2532 (style->font_desc));
2533 pango_attr_list_insert (attr_list, attr);
2535 g_object_set (G_OBJECT (priv->renderer_address),
2536 "foreground-gdk", &(priv->secondary_color),
2537 "foreground-set", TRUE,
2538 "attributes", attr_list,
2540 g_object_set (G_OBJECT (priv->renderer_date_status),
2541 "foreground-gdk", &(priv->secondary_color),
2542 "foreground-set", TRUE,
2543 "attributes", attr_list,
2545 pango_attr_list_unref (attr_list);
2547 g_object_set (G_OBJECT (priv->renderer_address),
2548 "foreground-gdk", &(priv->secondary_color),
2549 "foreground-set", TRUE,
2550 "scale", PANGO_SCALE_SMALL,
2553 g_object_set (G_OBJECT (priv->renderer_date_status),
2554 "foreground-gdk", &(priv->secondary_color),
2555 "foreground-set", TRUE,
2556 "scale", PANGO_SCALE_SMALL,
2561 if (gtk_style_lookup_color (GTK_WIDGET (self)->style, "ActiveTextColor", &style_active_color)) {
2562 priv->active_color = style_active_color;
2563 #ifdef MODEST_TOOLKIT_HILDON2
2564 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2565 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2568 #ifdef MODEST_TOOLKIT_HILDON2
2569 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2575 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2580 GtkTreeModel *tree_model;
2585 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2593 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2594 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2598 gtk_tree_model_get (tree_model, &iter,
2599 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2606 parse_date_side (const gchar *string, time_t *date_side)
2612 gboolean result = FALSE;
2614 if (string && string[0] == '\0') {
2619 casefold = g_utf8_casefold (string, -1);
2620 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2621 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2622 date = g_date_new ();
2624 if (g_utf8_collate (casefold, today) == 0) {
2625 *date_side = time (NULL);
2630 if (g_utf8_collate (casefold, yesterday) == 0) {
2631 *date_side = time (NULL) - 24*60*60;
2636 g_date_set_parse (date, string);
2637 if (g_date_valid (date)) {
2639 g_date_to_struct_tm (date, &tm);
2640 *date_side = mktime (&tm);
2655 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2660 parts = g_strsplit (string, "..", 2);
2663 if (g_strv_length (parts) != 2) {
2670 if (!parse_date_side (parts[0], date_range_start)) {
2675 if (parse_date_side (parts[1], date_range_end)) {
2676 if (*date_range_end == 0) {
2677 *date_range_end = (time_t) -1;
2679 *date_range_end += (24*60*60 - 1);
2692 modest_header_view_set_show_latest (ModestHeaderView *header_view,
2695 ModestHeaderViewPrivate *priv;
2696 GtkTreeModel *sortable, *filter, *model;
2698 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2699 priv->show_latest = show_latest;
2701 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2702 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2703 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2704 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2705 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2707 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (model), priv->show_latest);
2714 modest_header_view_get_show_latest (ModestHeaderView *header_view)
2716 ModestHeaderViewPrivate *priv;
2717 GtkTreeModel *sortable, *filter, *model;
2720 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2722 result = priv->show_latest;
2723 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2724 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2725 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2726 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2727 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2729 result = tny_gtk_header_list_model_get_show_latest (TNY_GTK_HEADER_LIST_MODEL (model));
2738 modest_header_view_get_not_latest (ModestHeaderView *header_view)
2740 ModestHeaderViewPrivate *priv;
2741 gint not_latest = 0;
2742 GtkTreeModel *sortable, *filter, *model;
2744 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2746 if (priv->show_latest == 0)
2749 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2750 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2751 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2752 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2753 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2755 not_latest = MAX (0, tny_list_get_length (TNY_LIST (model)) - priv->show_latest);
2764 modest_header_view_set_filter_string (ModestHeaderView *self,
2765 const gchar *filter_string)
2767 ModestHeaderViewPrivate *priv;
2769 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2770 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2772 if (priv->filter_string)
2773 g_free (priv->filter_string);
2775 priv->filter_string = g_strdup (filter_string);
2776 priv->filter_date_range = FALSE;
2778 if (priv->filter_string_splitted) {
2779 g_strfreev (priv->filter_string_splitted);
2780 priv->filter_string_splitted = NULL;
2783 if (priv->filter_string) {
2784 gchar **split, **current, **current_target;
2786 split = g_strsplit (priv->filter_string, " ", 0);
2788 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2789 current_target = priv->filter_string_splitted;
2790 for (current = split; *current != 0; current ++) {
2791 gboolean has_date_range = FALSE;;
2792 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2793 time_t range_start, range_end;
2794 /* It contains .. but it's not ".." so it may be a date range */
2795 if (parse_date_range (*current, &range_start, &range_end)) {
2796 priv->filter_date_range = TRUE;
2797 has_date_range = TRUE;
2798 priv->date_range_start = range_start;
2799 priv->date_range_end = range_end;
2802 if (!has_date_range) {
2803 *current_target = g_utf8_casefold (*current, -1);
2807 *current_target = '\0';
2810 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2813 #ifdef MODEST_TOOLKIT_HILDON2
2816 on_live_search_timeout (ModestHeaderView *self)
2818 const gchar *needle;
2819 ModestHeaderViewPrivate *priv;
2821 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2823 needle = hildon_live_search_get_text (HILDON_LIVE_SEARCH (priv->live_search));
2824 if (needle && needle[0] != '\0') {
2825 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2826 if (priv->show_latest > 0)
2827 modest_header_view_set_show_latest (MODEST_HEADER_VIEW (self), 0);
2829 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2832 priv->live_search_timeout = 0;
2838 on_live_search_refilter (HildonLiveSearch *livesearch,
2839 ModestHeaderView *self)
2841 ModestHeaderViewPrivate *priv;
2842 GtkTreeModel *model, *sortable, *filter;
2844 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2846 if (priv->live_search_timeout > 0) {
2847 g_source_remove (priv->live_search_timeout);
2848 priv->live_search_timeout = 0;
2852 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2853 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2854 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2855 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2856 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2860 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2861 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2863 on_live_search_timeout (self);
2870 modest_header_view_setup_live_search (ModestHeaderView *self)
2872 ModestHeaderViewPrivate *priv;
2874 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2875 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2876 priv->live_search = hildon_live_search_new ();
2878 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2880 return priv->live_search;