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>
55 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
56 static void modest_header_view_init (ModestHeaderView *obj);
57 static void modest_header_view_finalize (GObject *obj);
58 static void modest_header_view_dispose (GObject *obj);
60 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
61 GtkTreeViewColumn *column, gpointer userdata);
63 static gint cmp_rows (GtkTreeModel *tree_model,
68 static gint cmp_subject_rows (GtkTreeModel *tree_model,
73 static gboolean filter_row (GtkTreeModel *model,
77 static void on_account_removed (TnyAccountStore *self,
81 static void on_selection_changed (GtkTreeSelection *sel,
84 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
87 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
90 static void setup_drag_and_drop (GtkWidget *self);
92 static void enable_drag_and_drop (GtkWidget *self);
94 static void disable_drag_and_drop (GtkWidget *self);
96 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
98 #ifndef MODEST_TOOLKIT_HILDON2
99 static gboolean on_focus_in (GtkWidget *sef,
100 GdkEventFocus *event,
103 static gboolean on_focus_out (GtkWidget *self,
104 GdkEventFocus *event,
108 static void folder_monitor_update (TnyFolderObserver *self,
109 TnyFolderChange *change);
111 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
113 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
115 static void _clear_hidding_filter (ModestHeaderView *header_view);
117 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
119 const gchar *tny_folder_id);
121 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
122 GdkEventExpose *event,
125 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
126 static void update_style (ModestHeaderView *self);
129 HEADER_VIEW_NON_EMPTY,
134 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
135 struct _ModestHeaderViewPrivate {
137 ModestHeaderViewStyle style;
140 TnyFolderMonitor *monitor;
141 GMutex *observers_lock;
143 /*header-view-observer observer*/
144 GMutex *observer_list_lock;
145 GSList *observer_list;
147 /* not unref this object, its a singlenton */
148 ModestEmailClipboard *clipboard;
150 /* Filter tree model */
153 GtkTreeRowReference *autoselect_reference;
154 ModestHeaderViewFilter filter;
156 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
157 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
159 gulong selection_changed_handler;
160 gulong acc_removed_handler;
162 GList *drag_begin_cached_selected_rows;
164 HeaderViewStatus status;
165 guint status_timeout;
166 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
168 ModestDatetimeFormatter *datetime_formatter;
170 GtkCellRenderer *renderer_subject;
171 GtkCellRenderer *renderer_address;
172 GtkCellRenderer *renderer_date_status;
174 GdkColor active_color;
175 GdkColor secondary_color;
177 gchar *filter_string;
178 gchar **filter_string_splitted;
179 gboolean filter_date_range;
180 time_t date_range_start;
181 time_t date_range_end;
184 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
185 struct _HeadersCountChangedHelper {
186 ModestHeaderView *self;
187 TnyFolderChange *change;
191 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
192 MODEST_TYPE_HEADER_VIEW, \
193 ModestHeaderViewPrivate))
197 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
200 HEADER_SELECTED_SIGNAL,
201 HEADER_ACTIVATED_SIGNAL,
202 ITEM_NOT_FOUND_SIGNAL,
203 MSG_COUNT_CHANGED_SIGNAL,
204 UPDATING_MSG_LIST_SIGNAL,
209 static GObjectClass *parent_class = NULL;
211 /* uncomment the following if you have defined any signals */
212 static guint signals[LAST_SIGNAL] = {0};
215 modest_header_view_get_type (void)
217 static GType my_type = 0;
219 static const GTypeInfo my_info = {
220 sizeof(ModestHeaderViewClass),
221 NULL, /* base init */
222 NULL, /* base finalize */
223 (GClassInitFunc) modest_header_view_class_init,
224 NULL, /* class finalize */
225 NULL, /* class data */
226 sizeof(ModestHeaderView),
228 (GInstanceInitFunc) modest_header_view_init,
232 static const GInterfaceInfo tny_folder_observer_info =
234 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
235 NULL, /* interface_finalize */
236 NULL /* interface_data */
238 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
242 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
243 &tny_folder_observer_info);
251 modest_header_view_class_init (ModestHeaderViewClass *klass)
253 GObjectClass *gobject_class;
254 gobject_class = (GObjectClass*) klass;
256 parent_class = g_type_class_peek_parent (klass);
257 gobject_class->finalize = modest_header_view_finalize;
258 gobject_class->dispose = modest_header_view_dispose;
260 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
262 signals[HEADER_SELECTED_SIGNAL] =
263 g_signal_new ("header_selected",
264 G_TYPE_FROM_CLASS (gobject_class),
266 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
268 g_cclosure_marshal_VOID__POINTER,
269 G_TYPE_NONE, 1, G_TYPE_POINTER);
271 signals[HEADER_ACTIVATED_SIGNAL] =
272 g_signal_new ("header_activated",
273 G_TYPE_FROM_CLASS (gobject_class),
275 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
277 gtk_marshal_VOID__POINTER_POINTER,
278 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
281 signals[ITEM_NOT_FOUND_SIGNAL] =
282 g_signal_new ("item_not_found",
283 G_TYPE_FROM_CLASS (gobject_class),
285 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
287 g_cclosure_marshal_VOID__INT,
288 G_TYPE_NONE, 1, G_TYPE_INT);
290 signals[MSG_COUNT_CHANGED_SIGNAL] =
291 g_signal_new ("msg_count_changed",
292 G_TYPE_FROM_CLASS (gobject_class),
294 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
296 modest_marshal_VOID__POINTER_POINTER,
297 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
299 signals[UPDATING_MSG_LIST_SIGNAL] =
300 g_signal_new ("updating-msg-list",
301 G_TYPE_FROM_CLASS (gobject_class),
303 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
305 g_cclosure_marshal_VOID__BOOLEAN,
306 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
308 #ifdef MODEST_TOOLKIT_HILDON2
309 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
315 tny_folder_observer_init (TnyFolderObserverIface *klass)
317 klass->update = folder_monitor_update;
320 static GtkTreeViewColumn*
321 get_new_column (const gchar *name, GtkCellRenderer *renderer,
322 gboolean resizable, gint sort_col_id, gboolean show_as_text,
323 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
325 GtkTreeViewColumn *column;
327 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
328 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
330 gtk_tree_view_column_set_resizable (column, resizable);
332 gtk_tree_view_column_set_expand (column, TRUE);
335 gtk_tree_view_column_add_attribute (column, renderer, "text",
337 if (sort_col_id >= 0)
338 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
340 gtk_tree_view_column_set_sort_indicator (column, FALSE);
341 gtk_tree_view_column_set_reorderable (column, TRUE);
344 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
351 remove_all_columns (ModestHeaderView *obj)
353 GList *columns, *cursor;
355 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
357 for (cursor = columns; cursor; cursor = cursor->next)
358 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
359 GTK_TREE_VIEW_COLUMN(cursor->data));
360 g_list_free (columns);
364 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
366 GtkTreeModel *sortable;
367 GtkTreeViewColumn *column=NULL;
368 GtkTreeSelection *selection = NULL;
369 GtkCellRenderer *renderer_header,
370 *renderer_attach, *renderer_compact_date_or_status;
371 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
372 *renderer_subject_box, *renderer_recpt,
374 ModestHeaderViewPrivate *priv;
375 GtkTreeViewColumn *compact_column = NULL;
378 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
379 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
381 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
383 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
385 /* TODO: check whether these renderers need to be freed */
386 renderer_attach = gtk_cell_renderer_pixbuf_new ();
387 renderer_priority = gtk_cell_renderer_pixbuf_new ();
388 renderer_header = gtk_cell_renderer_text_new ();
390 renderer_compact_header = modest_vbox_cell_renderer_new ();
391 renderer_recpt_box = modest_hbox_cell_renderer_new ();
392 renderer_subject_box = modest_hbox_cell_renderer_new ();
393 renderer_recpt = gtk_cell_renderer_text_new ();
394 priv->renderer_address = renderer_recpt;
395 priv->renderer_subject = gtk_cell_renderer_text_new ();
396 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
397 priv->renderer_date_status = renderer_compact_date_or_status;
399 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
400 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
401 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
402 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
403 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
404 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
405 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
406 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
407 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
408 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
409 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
410 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
411 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
412 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
414 #ifdef MODEST_TOOLKIT_HILDON2
415 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
417 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
418 #ifndef MODEST_TOOLKIT_GTK
419 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
420 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
422 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
423 g_object_set(G_OBJECT(renderer_header),
424 "ellipsize", PANGO_ELLIPSIZE_END,
426 g_object_set (G_OBJECT (priv->renderer_subject),
427 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
429 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
430 g_object_set (G_OBJECT (renderer_recpt),
431 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
433 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
434 g_object_set(G_OBJECT(renderer_compact_date_or_status),
435 "xalign", 1.0, "yalign", 0.1,
437 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
438 #ifdef MODEST_TOOLKIT_HILDON2
439 g_object_set (G_OBJECT (renderer_priority),
441 "xalign", 0.0, NULL);
442 g_object_set (G_OBJECT (renderer_attach),
444 "xalign", 0.0, NULL);
446 g_object_set (G_OBJECT (renderer_priority),
447 "yalign", 0.5, NULL);
448 g_object_set (G_OBJECT (renderer_attach),
449 "yalign", 0.0, NULL);
452 #ifdef MODEST_TOOLKIT_HILDON1
453 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
454 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
455 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
456 #elif MODEST_TOOLKIT_HILDON2
457 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
458 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
459 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
461 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
462 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
463 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
466 remove_all_columns (self);
468 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
469 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
470 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
472 /* Add new columns */
473 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
474 ModestHeaderViewColumn col =
475 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
477 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
478 g_printerr ("modest: invalid column %d in column list\n", col);
484 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
485 column = get_new_column (_("A"), renderer_attach, FALSE,
486 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
488 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
490 gtk_tree_view_column_set_fixed_width (column, 45);
494 case MODEST_HEADER_VIEW_COLUMN_FROM:
495 column = get_new_column (_("From"), renderer_header, TRUE,
496 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
498 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
499 GINT_TO_POINTER(TRUE));
502 case MODEST_HEADER_VIEW_COLUMN_TO:
503 column = get_new_column (_("To"), renderer_header, TRUE,
504 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
506 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
507 GINT_TO_POINTER(FALSE));
510 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
511 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
512 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
514 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
515 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
516 compact_column = column;
519 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
520 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
521 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
523 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
524 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
525 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
526 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
527 compact_column = column;
531 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
532 column = get_new_column (_("Subject"), renderer_header, TRUE,
533 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
535 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
539 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
540 column = get_new_column (_("Received"), renderer_header, TRUE,
541 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
543 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
544 GINT_TO_POINTER(TRUE));
547 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
548 column = get_new_column (_("Sent"), renderer_header, TRUE,
549 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
551 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
552 GINT_TO_POINTER(FALSE));
555 case MODEST_HEADER_VIEW_COLUMN_SIZE:
556 column = get_new_column (_("Size"), renderer_header, TRUE,
557 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
559 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
562 case MODEST_HEADER_VIEW_COLUMN_STATUS:
563 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
564 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
566 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
571 g_return_val_if_reached(FALSE);
574 /* we keep the column id around */
575 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
576 GINT_TO_POINTER(col));
578 /* we need this ptr when sorting the rows */
579 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
581 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
585 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
586 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
587 (GtkTreeIterCompareFunc) cmp_rows,
588 compact_column, NULL);
589 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
590 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
591 (GtkTreeIterCompareFunc) cmp_subject_rows,
592 compact_column, NULL);
596 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
602 datetime_format_changed (ModestDatetimeFormatter *formatter,
603 ModestHeaderView *self)
605 gtk_widget_queue_draw (GTK_WIDGET (self));
609 modest_header_view_init (ModestHeaderView *obj)
611 ModestHeaderViewPrivate *priv;
614 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
617 priv->is_outbox = FALSE;
619 priv->monitor = NULL;
620 priv->observers_lock = g_mutex_new ();
621 priv->autoselect_reference = NULL;
623 priv->status = HEADER_VIEW_INIT;
624 priv->status_timeout = 0;
625 priv->notify_status = TRUE;
627 priv->observer_list_lock = g_mutex_new();
628 priv->observer_list = NULL;
630 priv->clipboard = modest_runtime_get_email_clipboard ();
631 priv->hidding_ids = NULL;
632 priv->n_selected = 0;
633 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
634 priv->filter_string = NULL;
635 priv->filter_string_splitted = NULL;
636 priv->filter_date_range = FALSE;
637 priv->selection_changed_handler = 0;
638 priv->acc_removed_handler = 0;
640 /* Sort parameters */
641 for (j=0; j < 2; j++) {
642 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
643 priv->sort_colid[j][i] = -1;
644 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
648 priv->datetime_formatter = modest_datetime_formatter_new ();
649 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
650 G_CALLBACK (datetime_format_changed), (gpointer) obj);
652 setup_drag_and_drop (GTK_WIDGET(obj));
656 modest_header_view_dispose (GObject *obj)
658 ModestHeaderView *self;
659 ModestHeaderViewPrivate *priv;
660 GtkTreeSelection *sel;
662 self = MODEST_HEADER_VIEW(obj);
663 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
665 if (priv->datetime_formatter) {
666 g_object_unref (priv->datetime_formatter);
667 priv->datetime_formatter = NULL;
670 /* Free in the dispose to avoid unref cycles */
672 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
673 g_object_unref (G_OBJECT (priv->folder));
677 /* We need to do this here in the dispose because the
678 selection won't exist when finalizing */
679 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
680 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
681 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
682 priv->selection_changed_handler = 0;
685 G_OBJECT_CLASS(parent_class)->dispose (obj);
689 modest_header_view_finalize (GObject *obj)
691 ModestHeaderView *self;
692 ModestHeaderViewPrivate *priv;
694 self = MODEST_HEADER_VIEW(obj);
695 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
697 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
698 priv->acc_removed_handler)) {
699 g_signal_handler_disconnect (modest_runtime_get_account_store (),
700 priv->acc_removed_handler);
703 /* There is no need to lock because there should not be any
704 * reference to self now. */
705 g_mutex_free(priv->observer_list_lock);
706 g_slist_free(priv->observer_list);
708 g_mutex_lock (priv->observers_lock);
710 tny_folder_monitor_stop (priv->monitor);
711 g_object_unref (G_OBJECT (priv->monitor));
713 g_mutex_unlock (priv->observers_lock);
714 g_mutex_free (priv->observers_lock);
716 /* Clear hidding array created by cut operation */
717 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
719 if (priv->autoselect_reference != NULL) {
720 gtk_tree_row_reference_free (priv->autoselect_reference);
721 priv->autoselect_reference = NULL;
724 if (priv->filter_string) {
725 g_free (priv->filter_string);
728 if (priv->filter_string_splitted) {
729 g_strfreev (priv->filter_string_splitted);
732 G_OBJECT_CLASS(parent_class)->finalize (obj);
737 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
740 GtkTreeSelection *sel;
741 ModestHeaderView *self;
742 ModestHeaderViewPrivate *priv;
744 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
747 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
748 self = MODEST_HEADER_VIEW(obj);
749 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
751 modest_header_view_set_style (self, style);
753 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
754 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
755 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
757 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
758 TRUE); /* alternating row colors */
760 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
761 priv->selection_changed_handler =
762 g_signal_connect_after (sel, "changed",
763 G_CALLBACK(on_selection_changed), self);
765 g_signal_connect (self, "row-activated",
766 G_CALLBACK (on_header_row_activated), NULL);
768 #ifndef MODEST_TOOLKIT_HILDON2
769 g_signal_connect (self, "focus-in-event",
770 G_CALLBACK(on_focus_in), NULL);
771 g_signal_connect (self, "focus-out-event",
772 G_CALLBACK(on_focus_out), NULL);
775 g_signal_connect (self, "button-press-event",
776 G_CALLBACK(on_button_press_event), NULL);
777 g_signal_connect (self, "button-release-event",
778 G_CALLBACK(on_button_release_event), NULL);
780 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
782 G_CALLBACK (on_account_removed),
785 g_signal_connect (self, "expose-event",
786 G_CALLBACK(modest_header_view_on_expose_event),
789 return GTK_WIDGET(self);
794 modest_header_view_count_selected_headers (ModestHeaderView *self)
796 GtkTreeSelection *sel;
799 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
801 /* Get selection object and check selected rows count */
802 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
803 selected_rows = gtk_tree_selection_count_selected_rows (sel);
805 return selected_rows;
809 modest_header_view_has_selected_headers (ModestHeaderView *self)
811 GtkTreeSelection *sel;
814 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
816 /* Get selection object and check selected rows count */
817 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
818 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
825 modest_header_view_get_selected_headers (ModestHeaderView *self)
827 GtkTreeSelection *sel;
828 TnyList *header_list = NULL;
830 GList *list, *tmp = NULL;
831 GtkTreeModel *tree_model = NULL;
834 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
837 /* Get selected rows */
838 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
839 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
842 header_list = tny_simple_list_new();
844 list = g_list_reverse (list);
847 /* get header from selection */
848 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
849 gtk_tree_model_get (tree_model, &iter,
850 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
852 /* Prepend to list */
853 tny_list_prepend (header_list, G_OBJECT (header));
854 g_object_unref (G_OBJECT (header));
856 tmp = g_list_next (tmp);
859 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
866 /* scroll our list view so the selected item is visible */
868 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
870 #ifdef MODEST_TOOLKIT_GTK
872 GtkTreePath *selected_path;
873 GtkTreePath *start, *end;
877 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
878 selected_path = gtk_tree_model_get_path (model, iter);
880 start = gtk_tree_path_new ();
881 end = gtk_tree_path_new ();
883 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
885 if (gtk_tree_path_compare (selected_path, start) < 0 ||
886 gtk_tree_path_compare (end, selected_path) < 0)
887 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
888 selected_path, NULL, TRUE,
891 gtk_tree_path_free (selected_path);
892 gtk_tree_path_free (start);
893 gtk_tree_path_free (end);
895 #endif /* MODEST_TOOLKIT_GTK */
900 modest_header_view_select_next (ModestHeaderView *self)
902 GtkTreeSelection *sel;
907 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
909 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
910 path = get_selected_row (GTK_TREE_VIEW(self), &model);
911 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
912 /* Unselect previous path */
913 gtk_tree_selection_unselect_path (sel, path);
915 /* Move path down and selects new one */
916 if (gtk_tree_model_iter_next (model, &iter)) {
917 gtk_tree_selection_select_iter (sel, &iter);
918 scroll_to_selected (self, &iter, FALSE);
920 gtk_tree_path_free(path);
926 modest_header_view_select_prev (ModestHeaderView *self)
928 GtkTreeSelection *sel;
933 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
935 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
936 path = get_selected_row (GTK_TREE_VIEW(self), &model);
937 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
938 /* Unselect previous path */
939 gtk_tree_selection_unselect_path (sel, path);
942 if (gtk_tree_path_prev (path)) {
943 gtk_tree_model_get_iter (model, &iter, path);
945 /* Select the new one */
946 gtk_tree_selection_select_iter (sel, &iter);
947 scroll_to_selected (self, &iter, TRUE);
950 gtk_tree_path_free (path);
955 modest_header_view_get_columns (ModestHeaderView *self)
957 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
959 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
965 modest_header_view_set_style (ModestHeaderView *self,
966 ModestHeaderViewStyle style)
968 ModestHeaderViewPrivate *priv;
969 gboolean show_col_headers = FALSE;
970 ModestHeaderViewStyle old_style;
972 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
973 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
976 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
977 if (priv->style == style)
978 return TRUE; /* nothing to do */
981 case MODEST_HEADER_VIEW_STYLE_DETAILS:
982 show_col_headers = TRUE;
984 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
987 g_return_val_if_reached (FALSE);
989 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
990 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
992 old_style = priv->style;
999 ModestHeaderViewStyle
1000 modest_header_view_get_style (ModestHeaderView *self)
1002 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1004 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1007 /* This is used to automatically select the first header if the user
1008 * has not selected any header yet.
1011 modest_header_view_on_expose_event(GtkTreeView *header_view,
1012 GdkEventExpose *event,
1015 GtkTreeSelection *sel;
1016 GtkTreeModel *model;
1017 GtkTreeIter tree_iter;
1018 ModestHeaderViewPrivate *priv;
1020 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1021 model = gtk_tree_view_get_model(header_view);
1026 #ifdef MODEST_TOOLKIT_HILDON2
1029 sel = gtk_tree_view_get_selection(header_view);
1030 if(!gtk_tree_selection_count_selected_rows(sel)) {
1031 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1032 GtkTreePath *tree_iter_path;
1033 /* Prevent the widget from getting the focus
1034 when selecting the first item */
1035 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1036 g_object_set(header_view, "can-focus", FALSE, NULL);
1037 gtk_tree_selection_select_iter(sel, &tree_iter);
1038 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1039 g_object_set(header_view, "can-focus", TRUE, NULL);
1040 if (priv->autoselect_reference) {
1041 gtk_tree_row_reference_free (priv->autoselect_reference);
1043 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1044 gtk_tree_path_free (tree_iter_path);
1047 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1048 gboolean moved_selection = FALSE;
1049 GtkTreePath * last_path;
1050 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1051 moved_selection = TRUE;
1055 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1056 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1057 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1058 moved_selection = TRUE;
1059 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1061 gtk_tree_path_free (last_path);
1063 if (moved_selection) {
1064 gtk_tree_row_reference_free (priv->autoselect_reference);
1065 priv->autoselect_reference = NULL;
1068 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1069 GtkTreePath *current_path;
1070 current_path = gtk_tree_model_get_path (model, &tree_iter);
1071 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1072 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1073 g_object_set(header_view, "can-focus", FALSE, NULL);
1074 gtk_tree_selection_unselect_all (sel);
1075 gtk_tree_selection_select_iter(sel, &tree_iter);
1076 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1077 g_object_set(header_view, "can-focus", TRUE, NULL);
1078 gtk_tree_row_reference_free (priv->autoselect_reference);
1079 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1081 gtk_tree_path_free (current_path);
1082 gtk_tree_path_free (last_path);
1092 modest_header_view_get_folder (ModestHeaderView *self)
1094 ModestHeaderViewPrivate *priv;
1096 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1098 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1101 g_object_ref (priv->folder);
1103 return priv->folder;
1107 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1113 ModestHeaderView *self;
1114 ModestHeaderViewPrivate *priv;
1116 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1118 self = MODEST_HEADER_VIEW (user_data);
1119 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1121 if (cancelled || err)
1124 /* Add IDLE observer (monitor) and another folder observer for
1125 new messages (self) */
1126 g_mutex_lock (priv->observers_lock);
1127 if (priv->monitor) {
1128 tny_folder_monitor_stop (priv->monitor);
1129 g_object_unref (G_OBJECT (priv->monitor));
1131 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1132 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1133 tny_folder_monitor_start (priv->monitor);
1134 g_mutex_unlock (priv->observers_lock);
1138 modest_header_view_set_folder_intern (ModestHeaderView *self,
1144 ModestHeaderViewPrivate *priv;
1145 GList *cols, *cursor;
1146 GtkTreeModel *filter_model, *sortable;
1148 GtkSortType sort_type;
1150 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1152 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1154 /* Start the monitor in the callback of the
1155 tny_gtk_header_list_model_set_folder call. It's crucial to
1156 do it there and not just after the call because we want the
1157 monitor to observe only the headers returned by the
1158 tny_folder_get_headers_async call that it's inside the
1159 tny_gtk_header_list_model_set_folder call. This way the
1160 monitor infrastructure could successfully cope with
1161 duplicates. For example if a tny_folder_add_msg_async is
1162 happening while tny_gtk_header_list_model_set_folder is
1163 invoked, then the first call could add a header that will
1164 be added again by tny_gtk_header_list_model_set_folder, so
1165 we'd end up with duplicate headers. sergio */
1166 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1168 set_folder_intern_get_headers_async_cb,
1171 /* Create a tree model filter to hide and show rows for cut operations */
1172 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (headers), NULL);
1173 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1174 filter_row, self, NULL);
1175 g_object_unref (headers);
1177 /* Init filter_row function to examine empty status */
1178 priv->status = HEADER_VIEW_INIT;
1180 /* Create sortable model */
1181 sortable = gtk_tree_model_sort_new_with_model (filter_model);
1182 g_object_unref (filter_model);
1184 /* install our special sorting functions */
1185 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1187 /* Restore sort column id */
1189 type = modest_tny_folder_guess_folder_type (folder);
1190 if (type == TNY_FOLDER_TYPE_INVALID)
1191 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1193 sort_colid = modest_header_view_get_sort_column_id (self, type);
1194 sort_type = modest_header_view_get_sort_type (self, type);
1195 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1198 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1199 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1200 (GtkTreeIterCompareFunc) cmp_rows,
1202 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1203 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1204 (GtkTreeIterCompareFunc) cmp_subject_rows,
1209 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
1210 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1211 g_object_unref (sortable);
1218 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1220 GtkSortType sort_type)
1222 ModestHeaderViewPrivate *priv = NULL;
1223 GtkTreeModel *sortable = NULL;
1226 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1227 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1229 /* Get model and private data */
1230 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1231 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1233 /* Sort tree model */
1234 type = modest_tny_folder_guess_folder_type (priv->folder);
1235 if (type == TNY_FOLDER_TYPE_INVALID)
1236 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1238 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1241 /* Store new sort parameters */
1242 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1247 modest_header_view_set_sort_params (ModestHeaderView *self,
1249 GtkSortType sort_type,
1252 ModestHeaderViewPrivate *priv;
1253 ModestHeaderViewStyle style;
1255 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1256 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1257 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1259 style = modest_header_view_get_style (self);
1260 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1262 priv->sort_colid[style][type] = sort_colid;
1263 priv->sort_type[style][type] = sort_type;
1267 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1270 ModestHeaderViewPrivate *priv;
1271 ModestHeaderViewStyle style;
1273 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1274 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1276 style = modest_header_view_get_style (self);
1277 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1279 return priv->sort_colid[style][type];
1283 modest_header_view_get_sort_type (ModestHeaderView *self,
1286 ModestHeaderViewPrivate *priv;
1287 ModestHeaderViewStyle style;
1289 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1290 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1292 style = modest_header_view_get_style (self);
1293 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1295 return priv->sort_type[style][type];
1299 ModestHeaderView *header_view;
1300 RefreshAsyncUserCallback cb;
1305 folder_refreshed_cb (ModestMailOperation *mail_op,
1309 ModestHeaderViewPrivate *priv;
1310 SetFolderHelper *info;
1312 info = (SetFolderHelper*) user_data;
1314 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1318 info->cb (mail_op, folder, info->user_data);
1320 /* Start the folder count changes observer. We do not need it
1321 before the refresh. Note that the monitor could still be
1322 called for this refresh but now we know that the callback
1323 was previously called */
1324 g_mutex_lock (priv->observers_lock);
1325 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1326 g_mutex_unlock (priv->observers_lock);
1328 /* Notify the observers that the update is over */
1329 g_signal_emit (G_OBJECT (info->header_view),
1330 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1332 /* Allow filtering notifications from now on if the current
1333 folder is still the same (if not then the user has selected
1334 another one to refresh, we should wait until that refresh
1336 if (priv->folder == folder)
1337 priv->notify_status = TRUE;
1340 g_object_unref (info->header_view);
1345 refresh_folder_error_handler (ModestMailOperation *mail_op,
1348 const GError *error = modest_mail_operation_get_error (mail_op);
1350 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1351 error->code == TNY_IO_ERROR_WRITE ||
1352 error->code == TNY_IO_ERROR_READ) {
1353 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1354 /* If the mail op has been cancelled then it's not an error: don't show any message */
1355 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1356 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1357 modest_platform_information_banner (NULL, NULL, msg);
1364 modest_header_view_set_folder (ModestHeaderView *self,
1367 ModestWindow *progress_window,
1368 RefreshAsyncUserCallback callback,
1371 ModestHeaderViewPrivate *priv;
1373 g_return_if_fail (self);
1375 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1378 if (priv->status_timeout) {
1379 g_source_remove (priv->status_timeout);
1380 priv->status_timeout = 0;
1383 g_mutex_lock (priv->observers_lock);
1384 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1385 g_object_unref (priv->folder);
1386 priv->folder = NULL;
1387 g_mutex_unlock (priv->observers_lock);
1391 GtkTreeSelection *selection;
1392 SetFolderHelper *info;
1393 ModestMailOperation *mail_op = NULL;
1395 /* Set folder in the model */
1396 modest_header_view_set_folder_intern (self, folder, refresh);
1398 /* Pick my reference. Nothing to do with the mail operation */
1399 priv->folder = g_object_ref (folder);
1401 /* Do not notify about filterings until the refresh finishes */
1402 priv->notify_status = FALSE;
1404 /* Clear the selection if exists */
1405 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1406 gtk_tree_selection_unselect_all(selection);
1407 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1409 /* Notify the observers that the update begins */
1410 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1413 /* create the helper */
1414 info = g_malloc0 (sizeof (SetFolderHelper));
1415 info->header_view = g_object_ref (self);
1416 info->cb = callback;
1417 info->user_data = user_data;
1419 /* Create the mail operation (source will be the parent widget) */
1420 if (progress_window)
1421 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1422 refresh_folder_error_handler,
1425 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1428 /* Refresh the folder asynchronously */
1429 modest_mail_operation_refresh_folder (mail_op,
1431 folder_refreshed_cb,
1434 folder_refreshed_cb (mail_op, folder, info);
1438 g_object_unref (mail_op);
1440 g_mutex_lock (priv->observers_lock);
1442 if (priv->monitor) {
1443 tny_folder_monitor_stop (priv->monitor);
1444 g_object_unref (G_OBJECT (priv->monitor));
1445 priv->monitor = NULL;
1448 if (priv->autoselect_reference) {
1449 gtk_tree_row_reference_free (priv->autoselect_reference);
1450 priv->autoselect_reference = NULL;
1453 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1455 modest_header_view_notify_observers(self, NULL, NULL);
1457 g_mutex_unlock (priv->observers_lock);
1459 /* Notify the observers that the update is over */
1460 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1466 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1467 GtkTreeViewColumn *column, gpointer userdata)
1469 ModestHeaderView *self = NULL;
1471 GtkTreeModel *model = NULL;
1472 TnyHeader *header = NULL;
1473 TnyHeaderFlags flags;
1475 self = MODEST_HEADER_VIEW (treeview);
1477 model = gtk_tree_view_get_model (treeview);
1478 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1481 /* get the first selected item */
1482 gtk_tree_model_get (model, &iter,
1483 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1484 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1487 /* Dont open DELETED messages */
1488 if (flags & TNY_HEADER_FLAG_DELETED) {
1491 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1492 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1493 modest_platform_information_banner (NULL, NULL, msg);
1499 g_signal_emit (G_OBJECT(self),
1500 signals[HEADER_ACTIVATED_SIGNAL],
1506 g_object_unref (G_OBJECT (header));
1511 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1513 GtkTreeModel *model;
1514 TnyHeader *header = NULL;
1515 GtkTreePath *path = NULL;
1517 ModestHeaderView *self;
1518 GList *selected = NULL;
1520 g_return_if_fail (sel);
1521 g_return_if_fail (user_data);
1523 self = MODEST_HEADER_VIEW (user_data);
1525 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1526 if (selected != NULL)
1527 path = (GtkTreePath *) selected->data;
1528 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1529 return; /* msg was _un_selected */
1531 gtk_tree_model_get (model, &iter,
1532 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1536 g_signal_emit (G_OBJECT(self),
1537 signals[HEADER_SELECTED_SIGNAL],
1540 g_object_unref (G_OBJECT (header));
1542 /* free all items in 'selected' */
1543 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1544 g_list_free (selected);
1548 /* PROTECTED method. It's useful when we want to force a given
1549 selection to reload a msg. For example if we have selected a header
1550 in offline mode, when Modest become online, we want to reload the
1551 message automatically without an user click over the header */
1553 _modest_header_view_change_selection (GtkTreeSelection *selection,
1556 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1557 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1559 on_selection_changed (selection, user_data);
1563 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1570 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1574 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1578 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1586 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1594 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1595 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1599 case TNY_HEADER_FLAG_ATTACHMENTS:
1601 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1602 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1603 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1604 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1606 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1607 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1609 return cmp ? cmp : t1 - t2;
1611 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1612 TnyHeader *header1 = NULL, *header2 = NULL;
1614 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1615 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1616 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1617 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1619 /* This is for making priority values respect the intuitive sort relationship
1620 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1622 if (header1 && header2) {
1623 cmp = compare_priorities (tny_header_get_priority (header1),
1624 tny_header_get_priority (header2));
1625 g_object_unref (header1);
1626 g_object_unref (header2);
1628 return cmp ? cmp : t1 - t2;
1634 return &iter1 - &iter2; /* oughhhh */
1639 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1646 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1648 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1649 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1650 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1651 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1653 /* Do not use the prefixes for sorting. Consume all the blank
1654 spaces for sorting */
1655 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1656 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1659 /* If they're equal based on subject without prefix then just
1660 sort them by length. This will show messages like this.
1667 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1674 /* Drag and drop stuff */
1676 drag_data_get_cb (GtkWidget *widget,
1677 GdkDragContext *context,
1678 GtkSelectionData *selection_data,
1683 ModestHeaderView *self = NULL;
1684 ModestHeaderViewPrivate *priv = NULL;
1686 self = MODEST_HEADER_VIEW (widget);
1687 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1689 /* Set the data. Do not use the current selection because it
1690 could be different than the selection at the beginning of
1692 modest_dnd_selection_data_set_paths (selection_data,
1693 priv->drag_begin_cached_selected_rows);
1697 * We're caching the selected rows at the beginning because the
1698 * selection could change between drag-begin and drag-data-get, for
1699 * example if we have a set of rows already selected, and then we
1700 * click in one of them (without SHIFT key pressed) and begin a drag,
1701 * the selection at that moment contains all the selected lines, but
1702 * after dropping the selection, the release event provokes that only
1703 * the row used to begin the drag is selected, so at the end the
1704 * drag&drop affects only one rows instead of all the selected ones.
1708 drag_begin_cb (GtkWidget *widget,
1709 GdkDragContext *context,
1712 ModestHeaderView *self = NULL;
1713 ModestHeaderViewPrivate *priv = NULL;
1714 GtkTreeSelection *selection;
1716 self = MODEST_HEADER_VIEW (widget);
1717 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1719 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1720 priv->drag_begin_cached_selected_rows =
1721 gtk_tree_selection_get_selected_rows (selection, NULL);
1725 * We use the drag-end signal to clear the cached selection, we use
1726 * this because this allways happens, whether or not the d&d was a
1730 drag_end_cb (GtkWidget *widget,
1734 ModestHeaderView *self = NULL;
1735 ModestHeaderViewPrivate *priv = NULL;
1737 self = MODEST_HEADER_VIEW (widget);
1738 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1740 /* Free cached data */
1741 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1742 g_list_free (priv->drag_begin_cached_selected_rows);
1743 priv->drag_begin_cached_selected_rows = NULL;
1746 /* Header view drag types */
1747 const GtkTargetEntry header_view_drag_types[] = {
1748 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1752 enable_drag_and_drop (GtkWidget *self)
1754 #ifdef MODEST_TOOLKIT_HILDON2
1757 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1758 header_view_drag_types,
1759 G_N_ELEMENTS (header_view_drag_types),
1760 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1764 disable_drag_and_drop (GtkWidget *self)
1766 #ifdef MODEST_TOOLKIT_HILDON2
1769 gtk_drag_source_unset (self);
1773 setup_drag_and_drop (GtkWidget *self)
1775 #ifdef MODEST_TOOLKIT_HILDON2
1778 enable_drag_and_drop(self);
1779 g_signal_connect(G_OBJECT (self), "drag_data_get",
1780 G_CALLBACK(drag_data_get_cb), NULL);
1782 g_signal_connect(G_OBJECT (self), "drag_begin",
1783 G_CALLBACK(drag_begin_cb), NULL);
1785 g_signal_connect(G_OBJECT (self), "drag_end",
1786 G_CALLBACK(drag_end_cb), NULL);
1789 static GtkTreePath *
1790 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1792 GtkTreePath *path = NULL;
1793 GtkTreeSelection *sel = NULL;
1796 sel = gtk_tree_view_get_selection(self);
1797 rows = gtk_tree_selection_get_selected_rows (sel, model);
1799 if ((rows == NULL) || (g_list_length(rows) != 1))
1802 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1807 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1813 #ifndef MODEST_TOOLKIT_HILDON2
1815 * This function moves the tree view scroll to the current selected
1816 * row when the widget grabs the focus
1819 on_focus_in (GtkWidget *self,
1820 GdkEventFocus *event,
1823 GtkTreeSelection *selection;
1824 GtkTreeModel *model;
1825 GList *selected = NULL;
1826 GtkTreePath *selected_path = NULL;
1828 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1832 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1833 /* If none selected yet, pick the first one */
1834 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1838 /* Return if the model is empty */
1839 if (!gtk_tree_model_get_iter_first (model, &iter))
1842 path = gtk_tree_model_get_path (model, &iter);
1843 gtk_tree_selection_select_path (selection, path);
1844 gtk_tree_path_free (path);
1847 /* Need to get the all the rows because is selection multiple */
1848 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1849 if (selected == NULL) return FALSE;
1850 selected_path = (GtkTreePath *) selected->data;
1853 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1854 g_list_free (selected);
1860 on_focus_out (GtkWidget *self,
1861 GdkEventFocus *event,
1865 if (!gtk_widget_is_focus (self)) {
1866 GtkTreeSelection *selection = NULL;
1867 GList *selected_rows = NULL;
1868 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1869 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1870 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1871 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1872 gtk_tree_selection_unselect_all (selection);
1873 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1874 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1875 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1876 g_list_free (selected_rows);
1884 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1886 enable_drag_and_drop(self);
1891 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1893 GtkTreeSelection *selection = NULL;
1894 GtkTreePath *path = NULL;
1895 gboolean already_selected = FALSE, already_opened = FALSE;
1896 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1898 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1900 GtkTreeModel *model;
1902 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1903 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1905 /* Get header from model */
1906 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1907 if (gtk_tree_model_get_iter (model, &iter, path)) {
1908 GValue value = {0,};
1911 gtk_tree_model_get_value (model, &iter,
1912 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1914 header = (TnyHeader *) g_value_get_object (&value);
1915 if (TNY_IS_HEADER (header)) {
1916 status = modest_tny_all_send_queues_get_msg_status (header);
1917 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1920 g_value_unset (&value);
1924 /* Enable drag and drop only if the user clicks on a row that
1925 it's already selected. If not, let him select items using
1926 the pointer. If the message is in an OUTBOX and in sending
1927 status disable drag and drop as well */
1928 if (!already_selected ||
1929 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1931 disable_drag_and_drop(self);
1934 gtk_tree_path_free(path);
1936 /* If it's already opened then do not let the button-press
1937 event go on because it'll perform a message open because
1938 we're clicking on to an already selected header */
1943 folder_monitor_update (TnyFolderObserver *self,
1944 TnyFolderChange *change)
1946 ModestHeaderViewPrivate *priv = NULL;
1947 TnyFolderChangeChanged changed;
1948 TnyFolder *folder = NULL;
1950 changed = tny_folder_change_get_changed (change);
1952 /* Do not notify the observers if the folder of the header
1953 view has changed before this call to the observer
1955 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1956 folder = tny_folder_change_get_folder (change);
1957 if (folder != priv->folder)
1960 MODEST_DEBUG_BLOCK (
1961 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1962 g_print ("ADDED %d/%d (r/t) \n",
1963 tny_folder_change_get_new_unread_count (change),
1964 tny_folder_change_get_new_all_count (change));
1965 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1966 g_print ("ALL COUNT %d\n",
1967 tny_folder_change_get_new_all_count (change));
1968 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1969 g_print ("UNREAD COUNT %d\n",
1970 tny_folder_change_get_new_unread_count (change));
1971 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1972 g_print ("EXPUNGED %d/%d (r/t) \n",
1973 tny_folder_change_get_new_unread_count (change),
1974 tny_folder_change_get_new_all_count (change));
1975 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1976 g_print ("FOLDER RENAME\n");
1977 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1978 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1979 tny_folder_change_get_new_unread_count (change),
1980 tny_folder_change_get_new_all_count (change));
1981 g_print ("---------------------------------------------------\n");
1984 /* Check folder count */
1985 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1986 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1988 g_mutex_lock (priv->observers_lock);
1990 /* Emit signal to evaluate how headers changes affects
1991 to the window view */
1992 g_signal_emit (G_OBJECT(self),
1993 signals[MSG_COUNT_CHANGED_SIGNAL],
1996 /* Added or removed headers, so data stored on cliboard are invalid */
1997 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
1998 modest_email_clipboard_clear (priv->clipboard);
2000 g_mutex_unlock (priv->observers_lock);
2006 g_object_unref (folder);
2010 modest_header_view_is_empty (ModestHeaderView *self)
2012 ModestHeaderViewPrivate *priv;
2014 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2016 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2018 return priv->status == HEADER_VIEW_EMPTY;
2022 modest_header_view_clear (ModestHeaderView *self)
2024 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2026 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2030 modest_header_view_copy_selection (ModestHeaderView *header_view)
2032 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2034 /* Copy selection */
2035 _clipboard_set_selected_data (header_view, FALSE);
2039 modest_header_view_cut_selection (ModestHeaderView *header_view)
2041 ModestHeaderViewPrivate *priv = NULL;
2042 const gchar **hidding = NULL;
2043 guint i, n_selected;
2045 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2047 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2049 /* Copy selection */
2050 _clipboard_set_selected_data (header_view, TRUE);
2052 /* Get hidding ids */
2053 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2055 /* Clear hidding array created by previous cut operation */
2056 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2058 /* Copy hidding array */
2059 priv->n_selected = n_selected;
2060 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2061 for (i=0; i < n_selected; i++)
2062 priv->hidding_ids[i] = g_strdup(hidding[i]);
2064 /* Hide cut headers */
2065 modest_header_view_refilter (header_view);
2072 _clipboard_set_selected_data (ModestHeaderView *header_view,
2075 ModestHeaderViewPrivate *priv = NULL;
2076 TnyList *headers = NULL;
2078 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2079 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2081 /* Set selected data on clipboard */
2082 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2083 headers = modest_header_view_get_selected_headers (header_view);
2084 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2087 g_object_unref (headers);
2091 ModestHeaderView *self;
2096 notify_filter_change (gpointer data)
2098 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2100 g_signal_emit (info->self,
2101 signals[MSG_COUNT_CHANGED_SIGNAL],
2102 0, info->folder, NULL);
2108 notify_filter_change_destroy (gpointer data)
2110 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2111 ModestHeaderViewPrivate *priv;
2113 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2114 priv->status_timeout = 0;
2116 g_object_unref (info->self);
2117 g_object_unref (info->folder);
2118 g_slice_free (NotifyFilterInfo, info);
2122 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2124 /* For the moment we only need to filter outbox */
2125 return priv->is_outbox;
2129 header_match_string (TnyHeader *header, gchar **words)
2136 gchar *subject_fold;
2142 gchar **current_word;
2145 subject = tny_header_dup_subject (header);
2146 cc = tny_header_dup_cc (header);
2147 bcc = tny_header_dup_bcc (header);
2148 to = tny_header_dup_to (header);
2149 from = tny_header_dup_from (header);
2151 subject_fold = g_utf8_casefold (subject, -1);
2153 bcc_fold = g_utf8_casefold (bcc, -1);
2155 cc_fold = g_utf8_casefold (cc, -1);
2157 to_fold = g_utf8_casefold (to, -1);
2159 from_fold = g_utf8_casefold (from, -1);
2164 for (current_word = words; *current_word != NULL; current_word++) {
2166 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2167 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2168 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2169 || (to && g_strstr_len (to_fold, -1, *current_word))
2170 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2178 g_free (subject_fold);
2188 filter_row (GtkTreeModel *model,
2192 ModestHeaderViewPrivate *priv = NULL;
2193 TnyHeaderFlags flags;
2194 TnyHeader *header = NULL;
2197 gboolean visible = TRUE;
2198 gboolean found = FALSE;
2199 GValue value = {0,};
2200 HeaderViewStatus old_status;
2202 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2203 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2205 /* Get header from model */
2206 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2207 flags = (TnyHeaderFlags) g_value_get_int (&value);
2208 g_value_unset (&value);
2209 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2210 header = (TnyHeader *) g_value_get_object (&value);
2211 g_value_unset (&value);
2213 /* Get message id from header (ensure is a valid id) */
2219 /* Hide deleted and mark as deleted heders */
2220 if (flags & TNY_HEADER_FLAG_DELETED ||
2221 flags & TNY_HEADER_FLAG_EXPUNGED) {
2226 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2227 if (current_folder_needs_filtering (priv) &&
2228 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2234 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2235 if (current_folder_needs_filtering (priv) &&
2236 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2242 if (visible && priv->filter_string) {
2243 if (!header_match_string (header, priv->filter_string_splitted)) {
2247 if (priv->filter_date_range) {
2248 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2249 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2256 /* If no data on clipboard, return always TRUE */
2257 if (modest_email_clipboard_cleared(priv->clipboard)) {
2263 if (priv->hidding_ids != NULL) {
2264 id = tny_header_dup_message_id (header);
2265 for (i=0; i < priv->n_selected && !found; i++)
2266 if (priv->hidding_ids[i] != NULL && id != NULL)
2267 found = (!strcmp (priv->hidding_ids[i], id));
2274 old_status = priv->status;
2275 priv->status = ((gboolean) priv->status) && !visible;
2276 if ((priv->notify_status) && (priv->status != old_status)) {
2277 if (priv->status_timeout)
2278 g_source_remove (priv->status_timeout);
2281 NotifyFilterInfo *info;
2283 info = g_slice_new0 (NotifyFilterInfo);
2284 info->self = g_object_ref (G_OBJECT (user_data));
2286 info->folder = tny_header_get_folder (header);
2287 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2288 notify_filter_change,
2290 notify_filter_change_destroy);
2298 _clear_hidding_filter (ModestHeaderView *header_view)
2300 ModestHeaderViewPrivate *priv = NULL;
2303 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2304 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2306 if (priv->hidding_ids != NULL) {
2307 for (i=0; i < priv->n_selected; i++)
2308 g_free (priv->hidding_ids[i]);
2309 g_free(priv->hidding_ids);
2314 modest_header_view_refilter (ModestHeaderView *header_view)
2316 GtkTreeModel *model, *sortable = NULL;
2317 ModestHeaderViewPrivate *priv = NULL;
2319 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2320 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2322 /* Hide cut headers */
2323 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2324 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2325 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2326 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2327 priv->status = HEADER_VIEW_INIT;
2328 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2334 * Called when an account is removed. If I'm showing a folder of the
2335 * account that has been removed then clear the view
2338 on_account_removed (TnyAccountStore *self,
2339 TnyAccount *account,
2342 ModestHeaderViewPrivate *priv = NULL;
2344 /* Ignore changes in transport accounts */
2345 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2348 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2351 TnyAccount *my_account;
2353 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2354 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2355 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2356 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2358 my_account = tny_folder_get_account (priv->folder);
2362 if (my_account == account)
2363 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2364 g_object_unref (my_account);
2370 modest_header_view_add_observer(ModestHeaderView *header_view,
2371 ModestHeaderViewObserver *observer)
2373 ModestHeaderViewPrivate *priv;
2375 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2376 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2378 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2380 g_mutex_lock(priv->observer_list_lock);
2381 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2382 g_mutex_unlock(priv->observer_list_lock);
2386 modest_header_view_remove_observer(ModestHeaderView *header_view,
2387 ModestHeaderViewObserver *observer)
2389 ModestHeaderViewPrivate *priv;
2391 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2392 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2394 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2396 g_mutex_lock(priv->observer_list_lock);
2397 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2398 g_mutex_unlock(priv->observer_list_lock);
2402 modest_header_view_notify_observers(ModestHeaderView *header_view,
2403 GtkTreeModel *model,
2404 const gchar *tny_folder_id)
2406 ModestHeaderViewPrivate *priv = NULL;
2408 ModestHeaderViewObserver *observer;
2411 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2413 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2415 g_mutex_lock(priv->observer_list_lock);
2416 iter = priv->observer_list;
2417 while(iter != NULL){
2418 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2419 modest_header_view_observer_update(observer, model,
2421 iter = g_slist_next(iter);
2423 g_mutex_unlock(priv->observer_list_lock);
2427 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2429 ModestHeaderViewPrivate *priv = NULL;
2431 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2432 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2436 modest_header_view_set_filter (ModestHeaderView *self,
2437 ModestHeaderViewFilter filter)
2439 ModestHeaderViewPrivate *priv;
2441 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2442 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2444 priv->filter |= filter;
2446 if (current_folder_needs_filtering (priv))
2447 modest_header_view_refilter (self);
2451 modest_header_view_unset_filter (ModestHeaderView *self,
2452 ModestHeaderViewFilter filter)
2454 ModestHeaderViewPrivate *priv;
2456 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2457 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2459 priv->filter &= ~filter;
2461 if (current_folder_needs_filtering (priv))
2462 modest_header_view_refilter (self);
2466 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2468 if (strcmp ("style", spec->name) == 0) {
2469 update_style (MODEST_HEADER_VIEW (obj));
2470 gtk_widget_queue_draw (GTK_WIDGET (obj));
2475 update_style (ModestHeaderView *self)
2477 ModestHeaderViewPrivate *priv;
2478 GdkColor style_color;
2479 GdkColor style_active_color;
2480 PangoAttrList *attr_list;
2482 PangoAttribute *attr;
2484 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2485 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2489 attr_list = pango_attr_list_new ();
2490 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2491 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2493 priv->secondary_color = style_color;
2494 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2495 pango_attr_list_insert (attr_list, attr);
2498 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2500 "SmallSystemFont", NULL,
2503 attr = pango_attr_font_desc_new (pango_font_description_copy
2504 (style->font_desc));
2505 pango_attr_list_insert (attr_list, attr);
2507 g_object_set (G_OBJECT (priv->renderer_address),
2508 "foreground-gdk", &(priv->secondary_color),
2509 "foreground-set", TRUE,
2510 "attributes", attr_list,
2512 g_object_set (G_OBJECT (priv->renderer_date_status),
2513 "foreground-gdk", &(priv->secondary_color),
2514 "foreground-set", TRUE,
2515 "attributes", attr_list,
2517 pango_attr_list_unref (attr_list);
2519 g_object_set (G_OBJECT (priv->renderer_address),
2520 "foreground-gdk", &(priv->secondary_color),
2521 "foreground-set", TRUE,
2522 "scale", PANGO_SCALE_SMALL,
2525 g_object_set (G_OBJECT (priv->renderer_date_status),
2526 "foreground-gdk", &(priv->secondary_color),
2527 "foreground-set", TRUE,
2528 "scale", PANGO_SCALE_SMALL,
2533 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2534 priv->active_color = style_active_color;
2535 #ifdef MODEST_TOOLKIT_HILDON2
2536 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2537 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2540 #ifdef MODEST_TOOLKIT_HILDON2
2541 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2547 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2552 GtkTreeModel *tree_model;
2557 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2565 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2566 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2570 gtk_tree_model_get (tree_model, &iter,
2571 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2578 parse_date_side (const gchar *string, time_t *date_side)
2584 gboolean result = FALSE;
2586 if (string && string[0] == '\0') {
2591 casefold = g_utf8_casefold (string, -1);
2592 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2593 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2594 date = g_date_new ();
2596 if (g_utf8_collate (casefold, today) == 0) {
2597 *date_side = time (NULL);
2602 if (g_utf8_collate (casefold, yesterday) == 0) {
2603 *date_side = time (NULL) - 24*60*60;
2608 g_date_set_parse (date, string);
2609 if (g_date_valid (date)) {
2611 g_date_to_struct_tm (date, &tm);
2612 *date_side = mktime (&tm);
2627 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2632 parts = g_strsplit (string, "..", 2);
2635 if (g_strv_length (parts) != 2) {
2642 if (!parse_date_side (parts[0], date_range_start)) {
2647 if (parse_date_side (parts[1], date_range_end)) {
2648 if (*date_range_end == 0) {
2649 *date_range_end = (time_t) -1;
2651 *date_range_end += (24*60*60 - 1);
2664 modest_header_view_set_filter_string (ModestHeaderView *self,
2665 const gchar *filter_string)
2667 ModestHeaderViewPrivate *priv;
2669 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2670 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2672 if (priv->filter_string)
2673 g_free (priv->filter_string);
2675 priv->filter_string = g_strdup (filter_string);
2676 priv->filter_date_range = FALSE;
2678 if (priv->filter_string_splitted) {
2679 g_strfreev (priv->filter_string_splitted);
2680 priv->filter_string_splitted = NULL;
2683 if (priv->filter_string) {
2684 gchar **split, **current, **current_target;
2686 split = g_strsplit (priv->filter_string, " ", 0);
2688 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2689 current_target = priv->filter_string_splitted;
2690 for (current = split; *current != 0; current ++) {
2691 gboolean has_date_range = FALSE;;
2692 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2693 time_t range_start, range_end;
2694 /* It contains .. but it's not ".." so it may be a date range */
2695 if (parse_date_range (*current, &range_start, &range_end)) {
2696 priv->filter_date_range = TRUE;
2697 has_date_range = TRUE;
2698 priv->date_range_start = range_start;
2699 priv->date_range_end = range_end;
2702 if (!has_date_range) {
2703 *current_target = g_utf8_casefold (*current, -1);
2707 *current_target = '\0';
2710 modest_header_view_refilter (MODEST_HEADER_VIEW (self));