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, *filter_model;
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);
477 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
478 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
479 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
482 /* Add new columns */
483 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
484 ModestHeaderViewColumn col =
485 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
487 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
488 g_printerr ("modest: invalid column %d in column list\n", col);
494 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
495 column = get_new_column (_("A"), renderer_attach, FALSE,
496 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
498 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
500 gtk_tree_view_column_set_fixed_width (column, 45);
504 case MODEST_HEADER_VIEW_COLUMN_FROM:
505 column = get_new_column (_("From"), renderer_header, TRUE,
506 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
508 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
509 GINT_TO_POINTER(TRUE));
512 case MODEST_HEADER_VIEW_COLUMN_TO:
513 column = get_new_column (_("To"), renderer_header, TRUE,
514 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
516 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
517 GINT_TO_POINTER(FALSE));
520 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
521 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
522 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
524 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
525 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
526 compact_column = column;
529 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
530 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
531 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
533 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
534 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
535 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
536 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
537 compact_column = column;
541 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
542 column = get_new_column (_("Subject"), renderer_header, TRUE,
543 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
545 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
549 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
550 column = get_new_column (_("Received"), renderer_header, TRUE,
551 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
553 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
554 GINT_TO_POINTER(TRUE));
557 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
558 column = get_new_column (_("Sent"), renderer_header, TRUE,
559 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
561 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
562 GINT_TO_POINTER(FALSE));
565 case MODEST_HEADER_VIEW_COLUMN_SIZE:
566 column = get_new_column (_("Size"), renderer_header, TRUE,
567 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
569 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
572 case MODEST_HEADER_VIEW_COLUMN_STATUS:
573 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
574 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
576 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
581 g_return_val_if_reached(FALSE);
584 /* we keep the column id around */
585 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
586 GINT_TO_POINTER(col));
588 /* we need this ptr when sorting the rows */
589 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
591 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
595 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
596 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
597 (GtkTreeIterCompareFunc) cmp_rows,
598 compact_column, NULL);
599 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
600 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
601 (GtkTreeIterCompareFunc) cmp_subject_rows,
602 compact_column, NULL);
606 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
612 datetime_format_changed (ModestDatetimeFormatter *formatter,
613 ModestHeaderView *self)
615 gtk_widget_queue_draw (GTK_WIDGET (self));
619 modest_header_view_init (ModestHeaderView *obj)
621 ModestHeaderViewPrivate *priv;
624 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
627 priv->is_outbox = FALSE;
629 priv->monitor = NULL;
630 priv->observers_lock = g_mutex_new ();
631 priv->autoselect_reference = NULL;
633 priv->status = HEADER_VIEW_INIT;
634 priv->status_timeout = 0;
635 priv->notify_status = TRUE;
637 priv->observer_list_lock = g_mutex_new();
638 priv->observer_list = NULL;
640 priv->clipboard = modest_runtime_get_email_clipboard ();
641 priv->hidding_ids = NULL;
642 priv->n_selected = 0;
643 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
644 #ifdef MODEST_TOOLKIT_HILDON2
645 priv->live_search = NULL;
647 priv->filter_string = NULL;
648 priv->filter_string_splitted = NULL;
649 priv->filter_date_range = FALSE;
650 priv->selection_changed_handler = 0;
651 priv->acc_removed_handler = 0;
653 /* Sort parameters */
654 for (j=0; j < 2; j++) {
655 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
656 priv->sort_colid[j][i] = -1;
657 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
661 priv->datetime_formatter = modest_datetime_formatter_new ();
662 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
663 G_CALLBACK (datetime_format_changed), (gpointer) obj);
665 setup_drag_and_drop (GTK_WIDGET(obj));
669 modest_header_view_dispose (GObject *obj)
671 ModestHeaderView *self;
672 ModestHeaderViewPrivate *priv;
673 GtkTreeSelection *sel;
675 self = MODEST_HEADER_VIEW(obj);
676 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
678 if (priv->datetime_formatter) {
679 g_object_unref (priv->datetime_formatter);
680 priv->datetime_formatter = NULL;
683 /* Free in the dispose to avoid unref cycles */
685 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
686 g_object_unref (G_OBJECT (priv->folder));
690 /* We need to do this here in the dispose because the
691 selection won't exist when finalizing */
692 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
693 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
694 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
695 priv->selection_changed_handler = 0;
698 G_OBJECT_CLASS(parent_class)->dispose (obj);
702 modest_header_view_finalize (GObject *obj)
704 ModestHeaderView *self;
705 ModestHeaderViewPrivate *priv;
707 self = MODEST_HEADER_VIEW(obj);
708 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
710 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
711 priv->acc_removed_handler)) {
712 g_signal_handler_disconnect (modest_runtime_get_account_store (),
713 priv->acc_removed_handler);
716 /* There is no need to lock because there should not be any
717 * reference to self now. */
718 g_mutex_free(priv->observer_list_lock);
719 g_slist_free(priv->observer_list);
721 g_mutex_lock (priv->observers_lock);
723 tny_folder_monitor_stop (priv->monitor);
724 g_object_unref (G_OBJECT (priv->monitor));
726 g_mutex_unlock (priv->observers_lock);
727 g_mutex_free (priv->observers_lock);
729 /* Clear hidding array created by cut operation */
730 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
732 if (priv->autoselect_reference != NULL) {
733 gtk_tree_row_reference_free (priv->autoselect_reference);
734 priv->autoselect_reference = NULL;
737 if (priv->filter_string) {
738 g_free (priv->filter_string);
741 if (priv->filter_string_splitted) {
742 g_strfreev (priv->filter_string_splitted);
745 G_OBJECT_CLASS(parent_class)->finalize (obj);
750 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
753 GtkTreeSelection *sel;
754 ModestHeaderView *self;
755 ModestHeaderViewPrivate *priv;
757 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
760 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
761 self = MODEST_HEADER_VIEW(obj);
762 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
764 modest_header_view_set_style (self, style);
766 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
767 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
768 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
770 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
771 TRUE); /* alternating row colors */
773 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
774 priv->selection_changed_handler =
775 g_signal_connect_after (sel, "changed",
776 G_CALLBACK(on_selection_changed), self);
778 g_signal_connect (self, "row-activated",
779 G_CALLBACK (on_header_row_activated), NULL);
781 #ifndef MODEST_TOOLKIT_HILDON2
782 g_signal_connect (self, "focus-in-event",
783 G_CALLBACK(on_focus_in), NULL);
784 g_signal_connect (self, "focus-out-event",
785 G_CALLBACK(on_focus_out), NULL);
788 g_signal_connect (self, "button-press-event",
789 G_CALLBACK(on_button_press_event), NULL);
790 g_signal_connect (self, "button-release-event",
791 G_CALLBACK(on_button_release_event), NULL);
793 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
795 G_CALLBACK (on_account_removed),
798 g_signal_connect (self, "expose-event",
799 G_CALLBACK(modest_header_view_on_expose_event),
802 return GTK_WIDGET(self);
807 modest_header_view_count_selected_headers (ModestHeaderView *self)
809 GtkTreeSelection *sel;
812 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
814 /* Get selection object and check selected rows count */
815 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
816 selected_rows = gtk_tree_selection_count_selected_rows (sel);
818 return selected_rows;
822 modest_header_view_has_selected_headers (ModestHeaderView *self)
824 GtkTreeSelection *sel;
827 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
829 /* Get selection object and check selected rows count */
830 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
831 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
838 modest_header_view_get_selected_headers (ModestHeaderView *self)
840 GtkTreeSelection *sel;
841 TnyList *header_list = NULL;
843 GList *list, *tmp = NULL;
844 GtkTreeModel *tree_model = NULL;
847 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
850 /* Get selected rows */
851 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
852 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
855 header_list = tny_simple_list_new();
857 list = g_list_reverse (list);
860 /* get header from selection */
861 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
862 gtk_tree_model_get (tree_model, &iter,
863 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
865 /* Prepend to list */
866 tny_list_prepend (header_list, G_OBJECT (header));
867 g_object_unref (G_OBJECT (header));
869 tmp = g_list_next (tmp);
872 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
879 /* scroll our list view so the selected item is visible */
881 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
883 #ifdef MODEST_TOOLKIT_GTK
885 GtkTreePath *selected_path;
886 GtkTreePath *start, *end;
890 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
891 selected_path = gtk_tree_model_get_path (model, iter);
893 start = gtk_tree_path_new ();
894 end = gtk_tree_path_new ();
896 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
898 if (gtk_tree_path_compare (selected_path, start) < 0 ||
899 gtk_tree_path_compare (end, selected_path) < 0)
900 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
901 selected_path, NULL, TRUE,
904 gtk_tree_path_free (selected_path);
905 gtk_tree_path_free (start);
906 gtk_tree_path_free (end);
908 #endif /* MODEST_TOOLKIT_GTK */
913 modest_header_view_select_next (ModestHeaderView *self)
915 GtkTreeSelection *sel;
920 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
922 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
923 path = get_selected_row (GTK_TREE_VIEW(self), &model);
924 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
925 /* Unselect previous path */
926 gtk_tree_selection_unselect_path (sel, path);
928 /* Move path down and selects new one */
929 if (gtk_tree_model_iter_next (model, &iter)) {
930 gtk_tree_selection_select_iter (sel, &iter);
931 scroll_to_selected (self, &iter, FALSE);
933 gtk_tree_path_free(path);
939 modest_header_view_select_prev (ModestHeaderView *self)
941 GtkTreeSelection *sel;
946 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
948 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
949 path = get_selected_row (GTK_TREE_VIEW(self), &model);
950 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
951 /* Unselect previous path */
952 gtk_tree_selection_unselect_path (sel, path);
955 if (gtk_tree_path_prev (path)) {
956 gtk_tree_model_get_iter (model, &iter, path);
958 /* Select the new one */
959 gtk_tree_selection_select_iter (sel, &iter);
960 scroll_to_selected (self, &iter, TRUE);
963 gtk_tree_path_free (path);
968 modest_header_view_get_columns (ModestHeaderView *self)
970 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
972 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
978 modest_header_view_set_style (ModestHeaderView *self,
979 ModestHeaderViewStyle style)
981 ModestHeaderViewPrivate *priv;
982 gboolean show_col_headers = FALSE;
983 ModestHeaderViewStyle old_style;
985 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
986 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
989 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
990 if (priv->style == style)
991 return TRUE; /* nothing to do */
994 case MODEST_HEADER_VIEW_STYLE_DETAILS:
995 show_col_headers = TRUE;
997 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1000 g_return_val_if_reached (FALSE);
1002 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1003 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1005 old_style = priv->style;
1006 priv->style = style;
1012 ModestHeaderViewStyle
1013 modest_header_view_get_style (ModestHeaderView *self)
1015 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1017 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1020 /* This is used to automatically select the first header if the user
1021 * has not selected any header yet.
1024 modest_header_view_on_expose_event(GtkTreeView *header_view,
1025 GdkEventExpose *event,
1028 GtkTreeSelection *sel;
1029 GtkTreeModel *model;
1030 GtkTreeIter tree_iter;
1031 ModestHeaderViewPrivate *priv;
1033 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1034 model = gtk_tree_view_get_model(header_view);
1039 #ifdef MODEST_TOOLKIT_HILDON2
1042 sel = gtk_tree_view_get_selection(header_view);
1043 if(!gtk_tree_selection_count_selected_rows(sel)) {
1044 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1045 GtkTreePath *tree_iter_path;
1046 /* Prevent the widget from getting the focus
1047 when selecting the first item */
1048 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1049 g_object_set(header_view, "can-focus", FALSE, NULL);
1050 gtk_tree_selection_select_iter(sel, &tree_iter);
1051 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1052 g_object_set(header_view, "can-focus", TRUE, NULL);
1053 if (priv->autoselect_reference) {
1054 gtk_tree_row_reference_free (priv->autoselect_reference);
1056 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1057 gtk_tree_path_free (tree_iter_path);
1060 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1061 gboolean moved_selection = FALSE;
1062 GtkTreePath * last_path;
1063 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1064 moved_selection = TRUE;
1068 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1069 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1070 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1071 moved_selection = TRUE;
1072 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1074 gtk_tree_path_free (last_path);
1076 if (moved_selection) {
1077 gtk_tree_row_reference_free (priv->autoselect_reference);
1078 priv->autoselect_reference = NULL;
1081 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1082 GtkTreePath *current_path;
1083 current_path = gtk_tree_model_get_path (model, &tree_iter);
1084 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1085 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1086 g_object_set(header_view, "can-focus", FALSE, NULL);
1087 gtk_tree_selection_unselect_all (sel);
1088 gtk_tree_selection_select_iter(sel, &tree_iter);
1089 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1090 g_object_set(header_view, "can-focus", TRUE, NULL);
1091 gtk_tree_row_reference_free (priv->autoselect_reference);
1092 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1094 gtk_tree_path_free (current_path);
1095 gtk_tree_path_free (last_path);
1105 modest_header_view_get_folder (ModestHeaderView *self)
1107 ModestHeaderViewPrivate *priv;
1109 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1111 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1114 g_object_ref (priv->folder);
1116 return priv->folder;
1120 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1126 ModestHeaderView *self;
1127 ModestHeaderViewPrivate *priv;
1129 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1131 self = MODEST_HEADER_VIEW (user_data);
1132 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1134 if (cancelled || err)
1137 /* Add IDLE observer (monitor) and another folder observer for
1138 new messages (self) */
1139 g_mutex_lock (priv->observers_lock);
1140 if (priv->monitor) {
1141 tny_folder_monitor_stop (priv->monitor);
1142 g_object_unref (G_OBJECT (priv->monitor));
1144 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1145 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1146 tny_folder_monitor_start (priv->monitor);
1147 g_mutex_unlock (priv->observers_lock);
1151 modest_header_view_set_folder_intern (ModestHeaderView *self,
1157 ModestHeaderViewPrivate *priv;
1158 GList *cols, *cursor;
1159 GtkTreeModel *filter_model, *sortable;
1161 GtkSortType sort_type;
1163 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1165 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1167 /* Start the monitor in the callback of the
1168 tny_gtk_header_list_model_set_folder call. It's crucial to
1169 do it there and not just after the call because we want the
1170 monitor to observe only the headers returned by the
1171 tny_folder_get_headers_async call that it's inside the
1172 tny_gtk_header_list_model_set_folder call. This way the
1173 monitor infrastructure could successfully cope with
1174 duplicates. For example if a tny_folder_add_msg_async is
1175 happening while tny_gtk_header_list_model_set_folder is
1176 invoked, then the first call could add a header that will
1177 be added again by tny_gtk_header_list_model_set_folder, so
1178 we'd end up with duplicate headers. sergio */
1179 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1181 set_folder_intern_get_headers_async_cb,
1184 /* Init filter_row function to examine empty status */
1185 priv->status = HEADER_VIEW_INIT;
1187 /* Create sortable model */
1188 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1189 g_object_unref (headers);
1191 /* Create a tree model filter to hide and show rows for cut operations */
1192 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1193 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1194 filter_row, self, NULL);
1195 g_object_unref (sortable);
1197 /* install our special sorting functions */
1198 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1200 /* Restore sort column id */
1202 type = modest_tny_folder_guess_folder_type (folder);
1203 if (type == TNY_FOLDER_TYPE_INVALID)
1204 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1206 sort_colid = modest_header_view_get_sort_column_id (self, type);
1207 sort_type = modest_header_view_get_sort_type (self, type);
1208 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1211 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1212 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1213 (GtkTreeIterCompareFunc) cmp_rows,
1215 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1216 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1217 (GtkTreeIterCompareFunc) cmp_subject_rows,
1222 gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1223 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1224 g_object_unref (filter_model);
1231 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1233 GtkSortType sort_type)
1235 ModestHeaderViewPrivate *priv = NULL;
1236 GtkTreeModel *sortable = NULL, *filter_model = NULL;
1239 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1240 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1242 /* Get model and private data */
1243 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1244 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1245 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1246 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1249 /* Sort tree model */
1250 type = modest_tny_folder_guess_folder_type (priv->folder);
1251 if (type == TNY_FOLDER_TYPE_INVALID)
1252 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1254 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1257 /* Store new sort parameters */
1258 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1263 modest_header_view_set_sort_params (ModestHeaderView *self,
1265 GtkSortType sort_type,
1268 ModestHeaderViewPrivate *priv;
1269 ModestHeaderViewStyle style;
1271 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1272 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1273 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1275 style = modest_header_view_get_style (self);
1276 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1278 priv->sort_colid[style][type] = sort_colid;
1279 priv->sort_type[style][type] = sort_type;
1283 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1286 ModestHeaderViewPrivate *priv;
1287 ModestHeaderViewStyle style;
1289 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1290 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1292 style = modest_header_view_get_style (self);
1293 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1295 return priv->sort_colid[style][type];
1299 modest_header_view_get_sort_type (ModestHeaderView *self,
1302 ModestHeaderViewPrivate *priv;
1303 ModestHeaderViewStyle style;
1305 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1306 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1308 style = modest_header_view_get_style (self);
1309 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1311 return priv->sort_type[style][type];
1315 ModestHeaderView *header_view;
1316 RefreshAsyncUserCallback cb;
1321 folder_refreshed_cb (ModestMailOperation *mail_op,
1325 ModestHeaderViewPrivate *priv;
1326 SetFolderHelper *info;
1328 info = (SetFolderHelper*) user_data;
1330 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1334 info->cb (mail_op, folder, info->user_data);
1336 /* Start the folder count changes observer. We do not need it
1337 before the refresh. Note that the monitor could still be
1338 called for this refresh but now we know that the callback
1339 was previously called */
1340 g_mutex_lock (priv->observers_lock);
1341 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1342 g_mutex_unlock (priv->observers_lock);
1344 /* Notify the observers that the update is over */
1345 g_signal_emit (G_OBJECT (info->header_view),
1346 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1348 /* Allow filtering notifications from now on if the current
1349 folder is still the same (if not then the user has selected
1350 another one to refresh, we should wait until that refresh
1352 if (priv->folder == folder)
1353 priv->notify_status = TRUE;
1356 g_object_unref (info->header_view);
1361 refresh_folder_error_handler (ModestMailOperation *mail_op,
1364 const GError *error = modest_mail_operation_get_error (mail_op);
1366 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1367 error->code == TNY_IO_ERROR_WRITE ||
1368 error->code == TNY_IO_ERROR_READ) {
1369 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1370 /* If the mail op has been cancelled then it's not an error: don't show any message */
1371 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1372 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1373 modest_platform_information_banner (NULL, NULL, msg);
1380 modest_header_view_set_folder (ModestHeaderView *self,
1383 ModestWindow *progress_window,
1384 RefreshAsyncUserCallback callback,
1387 ModestHeaderViewPrivate *priv;
1389 g_return_if_fail (self);
1391 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1394 if (priv->status_timeout) {
1395 g_source_remove (priv->status_timeout);
1396 priv->status_timeout = 0;
1399 g_mutex_lock (priv->observers_lock);
1400 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1401 g_object_unref (priv->folder);
1402 priv->folder = NULL;
1403 g_mutex_unlock (priv->observers_lock);
1407 GtkTreeSelection *selection;
1408 SetFolderHelper *info;
1409 ModestMailOperation *mail_op = NULL;
1411 /* Set folder in the model */
1412 modest_header_view_set_folder_intern (self, folder, refresh);
1414 /* Pick my reference. Nothing to do with the mail operation */
1415 priv->folder = g_object_ref (folder);
1417 /* Do not notify about filterings until the refresh finishes */
1418 priv->notify_status = FALSE;
1420 /* Clear the selection if exists */
1421 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1422 gtk_tree_selection_unselect_all(selection);
1423 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1425 /* Notify the observers that the update begins */
1426 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1429 /* create the helper */
1430 info = g_malloc0 (sizeof (SetFolderHelper));
1431 info->header_view = g_object_ref (self);
1432 info->cb = callback;
1433 info->user_data = user_data;
1435 /* Create the mail operation (source will be the parent widget) */
1436 if (progress_window)
1437 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1438 refresh_folder_error_handler,
1441 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1444 /* Refresh the folder asynchronously */
1445 modest_mail_operation_refresh_folder (mail_op,
1447 folder_refreshed_cb,
1450 folder_refreshed_cb (mail_op, folder, info);
1454 g_object_unref (mail_op);
1456 g_mutex_lock (priv->observers_lock);
1458 if (priv->monitor) {
1459 tny_folder_monitor_stop (priv->monitor);
1460 g_object_unref (G_OBJECT (priv->monitor));
1461 priv->monitor = NULL;
1464 if (priv->autoselect_reference) {
1465 gtk_tree_row_reference_free (priv->autoselect_reference);
1466 priv->autoselect_reference = NULL;
1469 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1471 modest_header_view_notify_observers(self, NULL, NULL);
1473 g_mutex_unlock (priv->observers_lock);
1475 /* Notify the observers that the update is over */
1476 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1482 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1483 GtkTreeViewColumn *column, gpointer userdata)
1485 ModestHeaderView *self = NULL;
1487 GtkTreeModel *model = NULL;
1488 TnyHeader *header = NULL;
1489 TnyHeaderFlags flags;
1491 self = MODEST_HEADER_VIEW (treeview);
1493 model = gtk_tree_view_get_model (treeview);
1494 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1497 /* get the first selected item */
1498 gtk_tree_model_get (model, &iter,
1499 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1500 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1503 /* Dont open DELETED messages */
1504 if (flags & TNY_HEADER_FLAG_DELETED) {
1507 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1508 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1509 modest_platform_information_banner (NULL, NULL, msg);
1515 g_signal_emit (G_OBJECT(self),
1516 signals[HEADER_ACTIVATED_SIGNAL],
1522 g_object_unref (G_OBJECT (header));
1527 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1529 GtkTreeModel *model;
1530 TnyHeader *header = NULL;
1531 GtkTreePath *path = NULL;
1533 ModestHeaderView *self;
1534 GList *selected = NULL;
1536 g_return_if_fail (sel);
1537 g_return_if_fail (user_data);
1539 self = MODEST_HEADER_VIEW (user_data);
1541 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1542 if (selected != NULL)
1543 path = (GtkTreePath *) selected->data;
1544 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1545 return; /* msg was _un_selected */
1547 gtk_tree_model_get (model, &iter,
1548 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1552 g_signal_emit (G_OBJECT(self),
1553 signals[HEADER_SELECTED_SIGNAL],
1556 g_object_unref (G_OBJECT (header));
1558 /* free all items in 'selected' */
1559 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1560 g_list_free (selected);
1564 /* PROTECTED method. It's useful when we want to force a given
1565 selection to reload a msg. For example if we have selected a header
1566 in offline mode, when Modest become online, we want to reload the
1567 message automatically without an user click over the header */
1569 _modest_header_view_change_selection (GtkTreeSelection *selection,
1572 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1573 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1575 on_selection_changed (selection, user_data);
1579 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1586 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1590 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1594 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1602 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1610 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1611 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1615 case TNY_HEADER_FLAG_ATTACHMENTS:
1617 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1618 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1619 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1620 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1622 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1623 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1625 return cmp ? cmp : t1 - t2;
1627 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1628 TnyHeader *header1 = NULL, *header2 = NULL;
1630 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1631 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1632 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1633 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1635 /* This is for making priority values respect the intuitive sort relationship
1636 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1638 if (header1 && header2) {
1639 cmp = compare_priorities (tny_header_get_priority (header1),
1640 tny_header_get_priority (header2));
1641 g_object_unref (header1);
1642 g_object_unref (header2);
1644 return cmp ? cmp : t1 - t2;
1650 return &iter1 - &iter2; /* oughhhh */
1655 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1662 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1664 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1665 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1666 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1667 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1669 /* Do not use the prefixes for sorting. Consume all the blank
1670 spaces for sorting */
1671 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1672 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1675 /* If they're equal based on subject without prefix then just
1676 sort them by length. This will show messages like this.
1683 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1690 /* Drag and drop stuff */
1692 drag_data_get_cb (GtkWidget *widget,
1693 GdkDragContext *context,
1694 GtkSelectionData *selection_data,
1699 ModestHeaderView *self = NULL;
1700 ModestHeaderViewPrivate *priv = NULL;
1702 self = MODEST_HEADER_VIEW (widget);
1703 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1705 /* Set the data. Do not use the current selection because it
1706 could be different than the selection at the beginning of
1708 modest_dnd_selection_data_set_paths (selection_data,
1709 priv->drag_begin_cached_selected_rows);
1713 * We're caching the selected rows at the beginning because the
1714 * selection could change between drag-begin and drag-data-get, for
1715 * example if we have a set of rows already selected, and then we
1716 * click in one of them (without SHIFT key pressed) and begin a drag,
1717 * the selection at that moment contains all the selected lines, but
1718 * after dropping the selection, the release event provokes that only
1719 * the row used to begin the drag is selected, so at the end the
1720 * drag&drop affects only one rows instead of all the selected ones.
1724 drag_begin_cb (GtkWidget *widget,
1725 GdkDragContext *context,
1728 ModestHeaderView *self = NULL;
1729 ModestHeaderViewPrivate *priv = NULL;
1730 GtkTreeSelection *selection;
1732 self = MODEST_HEADER_VIEW (widget);
1733 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1735 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1736 priv->drag_begin_cached_selected_rows =
1737 gtk_tree_selection_get_selected_rows (selection, NULL);
1741 * We use the drag-end signal to clear the cached selection, we use
1742 * this because this allways happens, whether or not the d&d was a
1746 drag_end_cb (GtkWidget *widget,
1750 ModestHeaderView *self = NULL;
1751 ModestHeaderViewPrivate *priv = NULL;
1753 self = MODEST_HEADER_VIEW (widget);
1754 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1756 /* Free cached data */
1757 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1758 g_list_free (priv->drag_begin_cached_selected_rows);
1759 priv->drag_begin_cached_selected_rows = NULL;
1762 /* Header view drag types */
1763 const GtkTargetEntry header_view_drag_types[] = {
1764 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1768 enable_drag_and_drop (GtkWidget *self)
1770 #ifdef MODEST_TOOLKIT_HILDON2
1773 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1774 header_view_drag_types,
1775 G_N_ELEMENTS (header_view_drag_types),
1776 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1780 disable_drag_and_drop (GtkWidget *self)
1782 #ifdef MODEST_TOOLKIT_HILDON2
1785 gtk_drag_source_unset (self);
1789 setup_drag_and_drop (GtkWidget *self)
1791 #ifdef MODEST_TOOLKIT_HILDON2
1794 enable_drag_and_drop(self);
1795 g_signal_connect(G_OBJECT (self), "drag_data_get",
1796 G_CALLBACK(drag_data_get_cb), NULL);
1798 g_signal_connect(G_OBJECT (self), "drag_begin",
1799 G_CALLBACK(drag_begin_cb), NULL);
1801 g_signal_connect(G_OBJECT (self), "drag_end",
1802 G_CALLBACK(drag_end_cb), NULL);
1805 static GtkTreePath *
1806 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1808 GtkTreePath *path = NULL;
1809 GtkTreeSelection *sel = NULL;
1812 sel = gtk_tree_view_get_selection(self);
1813 rows = gtk_tree_selection_get_selected_rows (sel, model);
1815 if ((rows == NULL) || (g_list_length(rows) != 1))
1818 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1823 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1829 #ifndef MODEST_TOOLKIT_HILDON2
1831 * This function moves the tree view scroll to the current selected
1832 * row when the widget grabs the focus
1835 on_focus_in (GtkWidget *self,
1836 GdkEventFocus *event,
1839 GtkTreeSelection *selection;
1840 GtkTreeModel *model;
1841 GList *selected = NULL;
1842 GtkTreePath *selected_path = NULL;
1844 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1848 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1849 /* If none selected yet, pick the first one */
1850 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1854 /* Return if the model is empty */
1855 if (!gtk_tree_model_get_iter_first (model, &iter))
1858 path = gtk_tree_model_get_path (model, &iter);
1859 gtk_tree_selection_select_path (selection, path);
1860 gtk_tree_path_free (path);
1863 /* Need to get the all the rows because is selection multiple */
1864 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1865 if (selected == NULL) return FALSE;
1866 selected_path = (GtkTreePath *) selected->data;
1869 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1870 g_list_free (selected);
1876 on_focus_out (GtkWidget *self,
1877 GdkEventFocus *event,
1881 if (!gtk_widget_is_focus (self)) {
1882 GtkTreeSelection *selection = NULL;
1883 GList *selected_rows = NULL;
1884 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1885 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1886 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1887 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1888 gtk_tree_selection_unselect_all (selection);
1889 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1890 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1891 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1892 g_list_free (selected_rows);
1900 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1902 enable_drag_and_drop(self);
1907 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1909 GtkTreeSelection *selection = NULL;
1910 GtkTreePath *path = NULL;
1911 gboolean already_selected = FALSE, already_opened = FALSE;
1912 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1914 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1916 GtkTreeModel *model;
1918 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1919 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1921 /* Get header from model */
1922 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1923 if (gtk_tree_model_get_iter (model, &iter, path)) {
1924 GValue value = {0,};
1927 gtk_tree_model_get_value (model, &iter,
1928 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1930 header = (TnyHeader *) g_value_get_object (&value);
1931 if (TNY_IS_HEADER (header)) {
1932 status = modest_tny_all_send_queues_get_msg_status (header);
1933 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1936 g_value_unset (&value);
1940 /* Enable drag and drop only if the user clicks on a row that
1941 it's already selected. If not, let him select items using
1942 the pointer. If the message is in an OUTBOX and in sending
1943 status disable drag and drop as well */
1944 if (!already_selected ||
1945 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1947 disable_drag_and_drop(self);
1950 gtk_tree_path_free(path);
1952 /* If it's already opened then do not let the button-press
1953 event go on because it'll perform a message open because
1954 we're clicking on to an already selected header */
1959 folder_monitor_update (TnyFolderObserver *self,
1960 TnyFolderChange *change)
1962 ModestHeaderViewPrivate *priv = NULL;
1963 TnyFolderChangeChanged changed;
1964 TnyFolder *folder = NULL;
1966 changed = tny_folder_change_get_changed (change);
1968 /* Do not notify the observers if the folder of the header
1969 view has changed before this call to the observer
1971 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1972 folder = tny_folder_change_get_folder (change);
1973 if (folder != priv->folder)
1976 MODEST_DEBUG_BLOCK (
1977 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1978 g_print ("ADDED %d/%d (r/t) \n",
1979 tny_folder_change_get_new_unread_count (change),
1980 tny_folder_change_get_new_all_count (change));
1981 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1982 g_print ("ALL COUNT %d\n",
1983 tny_folder_change_get_new_all_count (change));
1984 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1985 g_print ("UNREAD COUNT %d\n",
1986 tny_folder_change_get_new_unread_count (change));
1987 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1988 g_print ("EXPUNGED %d/%d (r/t) \n",
1989 tny_folder_change_get_new_unread_count (change),
1990 tny_folder_change_get_new_all_count (change));
1991 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1992 g_print ("FOLDER RENAME\n");
1993 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1994 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1995 tny_folder_change_get_new_unread_count (change),
1996 tny_folder_change_get_new_all_count (change));
1997 g_print ("---------------------------------------------------\n");
2000 /* Check folder count */
2001 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2002 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2004 g_mutex_lock (priv->observers_lock);
2006 /* Emit signal to evaluate how headers changes affects
2007 to the window view */
2008 g_signal_emit (G_OBJECT(self),
2009 signals[MSG_COUNT_CHANGED_SIGNAL],
2012 /* Added or removed headers, so data stored on cliboard are invalid */
2013 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2014 modest_email_clipboard_clear (priv->clipboard);
2016 g_mutex_unlock (priv->observers_lock);
2022 g_object_unref (folder);
2026 modest_header_view_is_empty (ModestHeaderView *self)
2028 ModestHeaderViewPrivate *priv;
2030 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2032 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2034 return priv->status == HEADER_VIEW_EMPTY;
2038 modest_header_view_clear (ModestHeaderView *self)
2040 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2042 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2046 modest_header_view_copy_selection (ModestHeaderView *header_view)
2048 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2050 /* Copy selection */
2051 _clipboard_set_selected_data (header_view, FALSE);
2055 modest_header_view_cut_selection (ModestHeaderView *header_view)
2057 ModestHeaderViewPrivate *priv = NULL;
2058 const gchar **hidding = NULL;
2059 guint i, n_selected;
2061 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2063 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2065 /* Copy selection */
2066 _clipboard_set_selected_data (header_view, TRUE);
2068 /* Get hidding ids */
2069 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2071 /* Clear hidding array created by previous cut operation */
2072 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2074 /* Copy hidding array */
2075 priv->n_selected = n_selected;
2076 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2077 for (i=0; i < n_selected; i++)
2078 priv->hidding_ids[i] = g_strdup(hidding[i]);
2080 /* Hide cut headers */
2081 modest_header_view_refilter (header_view);
2088 _clipboard_set_selected_data (ModestHeaderView *header_view,
2091 ModestHeaderViewPrivate *priv = NULL;
2092 TnyList *headers = NULL;
2094 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2095 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2097 /* Set selected data on clipboard */
2098 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2099 headers = modest_header_view_get_selected_headers (header_view);
2100 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2103 g_object_unref (headers);
2107 ModestHeaderView *self;
2112 notify_filter_change (gpointer data)
2114 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2116 g_signal_emit (info->self,
2117 signals[MSG_COUNT_CHANGED_SIGNAL],
2118 0, info->folder, NULL);
2124 notify_filter_change_destroy (gpointer data)
2126 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2127 ModestHeaderViewPrivate *priv;
2129 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2130 priv->status_timeout = 0;
2132 g_object_unref (info->self);
2133 g_object_unref (info->folder);
2134 g_slice_free (NotifyFilterInfo, info);
2138 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2140 /* For the moment we only need to filter outbox */
2141 return priv->is_outbox;
2145 header_match_string (TnyHeader *header, gchar **words)
2152 gchar *subject_fold;
2158 gchar **current_word;
2161 subject = tny_header_dup_subject (header);
2162 cc = tny_header_dup_cc (header);
2163 bcc = tny_header_dup_bcc (header);
2164 to = tny_header_dup_to (header);
2165 from = tny_header_dup_from (header);
2167 subject_fold = g_utf8_casefold (subject, -1);
2169 bcc_fold = g_utf8_casefold (bcc, -1);
2171 cc_fold = g_utf8_casefold (cc, -1);
2173 to_fold = g_utf8_casefold (to, -1);
2175 from_fold = g_utf8_casefold (from, -1);
2180 for (current_word = words; *current_word != NULL; current_word++) {
2182 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2183 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2184 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2185 || (to && g_strstr_len (to_fold, -1, *current_word))
2186 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2194 g_free (subject_fold);
2204 filter_row (GtkTreeModel *model,
2208 ModestHeaderViewPrivate *priv = NULL;
2209 TnyHeaderFlags flags;
2210 TnyHeader *header = NULL;
2213 gboolean visible = TRUE;
2214 gboolean found = FALSE;
2215 GValue value = {0,};
2216 HeaderViewStatus old_status;
2218 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2219 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2221 /* Get header from model */
2222 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2223 flags = (TnyHeaderFlags) g_value_get_int (&value);
2224 g_value_unset (&value);
2225 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2226 header = (TnyHeader *) g_value_get_object (&value);
2227 g_value_unset (&value);
2229 /* Get message id from header (ensure is a valid id) */
2235 /* Hide deleted and mark as deleted heders */
2236 if (flags & TNY_HEADER_FLAG_DELETED ||
2237 flags & TNY_HEADER_FLAG_EXPUNGED) {
2242 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2243 if (current_folder_needs_filtering (priv) &&
2244 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2250 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2251 if (current_folder_needs_filtering (priv) &&
2252 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2258 if (visible && priv->filter_string) {
2259 if (!header_match_string (header, priv->filter_string_splitted)) {
2263 if (priv->filter_date_range) {
2264 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2265 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2272 /* If no data on clipboard, return always TRUE */
2273 if (modest_email_clipboard_cleared(priv->clipboard)) {
2279 if (priv->hidding_ids != NULL) {
2280 id = tny_header_dup_message_id (header);
2281 for (i=0; i < priv->n_selected && !found; i++)
2282 if (priv->hidding_ids[i] != NULL && id != NULL)
2283 found = (!strcmp (priv->hidding_ids[i], id));
2290 old_status = priv->status;
2291 priv->status = ((gboolean) priv->status) && !visible;
2292 if ((priv->notify_status) && (priv->status != old_status)) {
2293 if (priv->status_timeout)
2294 g_source_remove (priv->status_timeout);
2297 NotifyFilterInfo *info;
2299 info = g_slice_new0 (NotifyFilterInfo);
2300 info->self = g_object_ref (G_OBJECT (user_data));
2302 info->folder = tny_header_get_folder (header);
2303 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2304 notify_filter_change,
2306 notify_filter_change_destroy);
2314 _clear_hidding_filter (ModestHeaderView *header_view)
2316 ModestHeaderViewPrivate *priv = NULL;
2319 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2320 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2322 if (priv->hidding_ids != NULL) {
2323 for (i=0; i < priv->n_selected; i++)
2324 g_free (priv->hidding_ids[i]);
2325 g_free(priv->hidding_ids);
2330 modest_header_view_refilter (ModestHeaderView *header_view)
2332 GtkTreeModel *filter_model = NULL;
2333 ModestHeaderViewPrivate *priv = NULL;
2335 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2336 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2338 /* Hide cut headers */
2339 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2340 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2341 priv->status = HEADER_VIEW_INIT;
2342 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2347 * Called when an account is removed. If I'm showing a folder of the
2348 * account that has been removed then clear the view
2351 on_account_removed (TnyAccountStore *self,
2352 TnyAccount *account,
2355 ModestHeaderViewPrivate *priv = NULL;
2357 /* Ignore changes in transport accounts */
2358 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2361 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2364 TnyAccount *my_account;
2366 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2367 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2368 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2369 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2371 my_account = tny_folder_get_account (priv->folder);
2375 if (my_account == account)
2376 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2377 g_object_unref (my_account);
2383 modest_header_view_add_observer(ModestHeaderView *header_view,
2384 ModestHeaderViewObserver *observer)
2386 ModestHeaderViewPrivate *priv;
2388 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2389 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2391 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2393 g_mutex_lock(priv->observer_list_lock);
2394 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2395 g_mutex_unlock(priv->observer_list_lock);
2399 modest_header_view_remove_observer(ModestHeaderView *header_view,
2400 ModestHeaderViewObserver *observer)
2402 ModestHeaderViewPrivate *priv;
2404 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2405 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2407 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2409 g_mutex_lock(priv->observer_list_lock);
2410 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2411 g_mutex_unlock(priv->observer_list_lock);
2415 modest_header_view_notify_observers(ModestHeaderView *header_view,
2416 GtkTreeModel *model,
2417 const gchar *tny_folder_id)
2419 ModestHeaderViewPrivate *priv = NULL;
2421 ModestHeaderViewObserver *observer;
2424 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2426 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2428 g_mutex_lock(priv->observer_list_lock);
2429 iter = priv->observer_list;
2430 while(iter != NULL){
2431 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2432 modest_header_view_observer_update(observer, model,
2434 iter = g_slist_next(iter);
2436 g_mutex_unlock(priv->observer_list_lock);
2440 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2442 ModestHeaderViewPrivate *priv = NULL;
2444 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2445 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2449 modest_header_view_set_filter (ModestHeaderView *self,
2450 ModestHeaderViewFilter filter)
2452 ModestHeaderViewPrivate *priv;
2454 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2455 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2457 priv->filter |= filter;
2459 if (current_folder_needs_filtering (priv))
2460 modest_header_view_refilter (self);
2464 modest_header_view_unset_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 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2481 if (strcmp ("style", spec->name) == 0) {
2482 update_style (MODEST_HEADER_VIEW (obj));
2483 gtk_widget_queue_draw (GTK_WIDGET (obj));
2488 update_style (ModestHeaderView *self)
2490 ModestHeaderViewPrivate *priv;
2491 GdkColor style_color;
2492 GdkColor style_active_color;
2493 PangoAttrList *attr_list;
2495 PangoAttribute *attr;
2497 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2498 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2502 attr_list = pango_attr_list_new ();
2503 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2504 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2506 priv->secondary_color = style_color;
2507 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2508 pango_attr_list_insert (attr_list, attr);
2511 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2513 "SmallSystemFont", NULL,
2516 attr = pango_attr_font_desc_new (pango_font_description_copy
2517 (style->font_desc));
2518 pango_attr_list_insert (attr_list, attr);
2520 g_object_set (G_OBJECT (priv->renderer_address),
2521 "foreground-gdk", &(priv->secondary_color),
2522 "foreground-set", TRUE,
2523 "attributes", attr_list,
2525 g_object_set (G_OBJECT (priv->renderer_date_status),
2526 "foreground-gdk", &(priv->secondary_color),
2527 "foreground-set", TRUE,
2528 "attributes", attr_list,
2530 pango_attr_list_unref (attr_list);
2532 g_object_set (G_OBJECT (priv->renderer_address),
2533 "foreground-gdk", &(priv->secondary_color),
2534 "foreground-set", TRUE,
2535 "scale", PANGO_SCALE_SMALL,
2538 g_object_set (G_OBJECT (priv->renderer_date_status),
2539 "foreground-gdk", &(priv->secondary_color),
2540 "foreground-set", TRUE,
2541 "scale", PANGO_SCALE_SMALL,
2546 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2547 priv->active_color = style_active_color;
2548 #ifdef MODEST_TOOLKIT_HILDON2
2549 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2550 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2553 #ifdef MODEST_TOOLKIT_HILDON2
2554 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2560 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2565 GtkTreeModel *tree_model;
2570 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2578 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2579 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2583 gtk_tree_model_get (tree_model, &iter,
2584 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2591 parse_date_side (const gchar *string, time_t *date_side)
2597 gboolean result = FALSE;
2599 if (string && string[0] == '\0') {
2604 casefold = g_utf8_casefold (string, -1);
2605 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2606 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2607 date = g_date_new ();
2609 if (g_utf8_collate (casefold, today) == 0) {
2610 *date_side = time (NULL);
2615 if (g_utf8_collate (casefold, yesterday) == 0) {
2616 *date_side = time (NULL) - 24*60*60;
2621 g_date_set_parse (date, string);
2622 if (g_date_valid (date)) {
2624 g_date_to_struct_tm (date, &tm);
2625 *date_side = mktime (&tm);
2640 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2645 parts = g_strsplit (string, "..", 2);
2648 if (g_strv_length (parts) != 2) {
2655 if (!parse_date_side (parts[0], date_range_start)) {
2660 if (parse_date_side (parts[1], date_range_end)) {
2661 if (*date_range_end == 0) {
2662 *date_range_end = (time_t) -1;
2664 *date_range_end += (24*60*60 - 1);
2677 modest_header_view_set_filter_string (ModestHeaderView *self,
2678 const gchar *filter_string)
2680 ModestHeaderViewPrivate *priv;
2682 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2683 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2685 if (priv->filter_string)
2686 g_free (priv->filter_string);
2688 priv->filter_string = g_strdup (filter_string);
2689 priv->filter_date_range = FALSE;
2691 if (priv->filter_string_splitted) {
2692 g_strfreev (priv->filter_string_splitted);
2693 priv->filter_string_splitted = NULL;
2696 if (priv->filter_string) {
2697 gchar **split, **current, **current_target;
2699 split = g_strsplit (priv->filter_string, " ", 0);
2701 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2702 current_target = priv->filter_string_splitted;
2703 for (current = split; *current != 0; current ++) {
2704 gboolean has_date_range = FALSE;;
2705 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2706 time_t range_start, range_end;
2707 /* It contains .. but it's not ".." so it may be a date range */
2708 if (parse_date_range (*current, &range_start, &range_end)) {
2709 priv->filter_date_range = TRUE;
2710 has_date_range = TRUE;
2711 priv->date_range_start = range_start;
2712 priv->date_range_end = range_end;
2715 if (!has_date_range) {
2716 *current_target = g_utf8_casefold (*current, -1);
2720 *current_target = '\0';
2723 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2726 #ifdef MODEST_TOOLKIT_HILDON2
2728 on_live_search_refilter (HildonLiveSearch *livesearch,
2729 ModestHeaderView *self)
2731 const gchar *needle;
2733 needle = hildon_live_search_get_text (livesearch);
2734 if (needle && needle[0] != '\0') {
2735 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2737 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2740 priv->live_search_timeout = 0;
2746 on_live_search_refilter (HildonLiveSearch *livesearch,
2747 ModestHeaderView *self)
2749 ModestHeaderViewPrivate *priv;
2750 GtkTreeModel *model, *sortable, *filter;
2752 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2754 if (priv->live_search_timeout > 0) {
2755 g_source_remove (priv->live_search_timeout);
2756 priv->live_search_timeout = 0;
2760 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2761 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2762 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2763 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2764 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2768 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2769 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2771 on_live_search_timeout (self);
2778 modest_header_view_setup_live_search (ModestHeaderView *self)
2780 ModestHeaderViewPrivate *priv;
2782 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2783 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2784 priv->live_search = hildon_live_search_new ();
2786 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2788 return priv->live_search;