1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
35 #include <tny-error.h>
36 #include <tny-merge-folder.h>
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-main-window.h>
45 #include <modest-ui-actions.h>
46 #include <modest-marshal.h>
47 #include <modest-text-utils.h>
48 #include <modest-icon-names.h>
49 #include <modest-runtime.h>
50 #include "modest-platform.h"
51 #include <modest-hbox-cell-renderer.h>
52 #include <modest-vbox-cell-renderer.h>
53 #include <modest-datetime-formatter.h>
54 #include <modest-ui-constants.h>
56 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
57 static void modest_header_view_init (ModestHeaderView *obj);
58 static void modest_header_view_finalize (GObject *obj);
59 static void modest_header_view_dispose (GObject *obj);
61 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
62 GtkTreeViewColumn *column, gpointer userdata);
64 static gint cmp_rows (GtkTreeModel *tree_model,
69 static gint cmp_subject_rows (GtkTreeModel *tree_model,
74 static gboolean filter_row (GtkTreeModel *model,
78 static void on_account_removed (TnyAccountStore *self,
82 static void on_selection_changed (GtkTreeSelection *sel,
85 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
88 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
91 static void setup_drag_and_drop (GtkWidget *self);
93 static void enable_drag_and_drop (GtkWidget *self);
95 static void disable_drag_and_drop (GtkWidget *self);
97 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
99 #ifndef MODEST_TOOLKIT_HILDON2
100 static gboolean on_focus_in (GtkWidget *sef,
101 GdkEventFocus *event,
104 static gboolean on_focus_out (GtkWidget *self,
105 GdkEventFocus *event,
109 static void folder_monitor_update (TnyFolderObserver *self,
110 TnyFolderChange *change);
112 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
114 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
116 static void _clear_hidding_filter (ModestHeaderView *header_view);
118 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
120 const gchar *tny_folder_id);
122 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
123 GdkEventExpose *event,
126 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
127 static void update_style (ModestHeaderView *self);
130 HEADER_VIEW_NON_EMPTY,
135 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
136 struct _ModestHeaderViewPrivate {
138 ModestHeaderViewStyle style;
141 TnyFolderMonitor *monitor;
142 GMutex *observers_lock;
144 /*header-view-observer observer*/
145 GMutex *observer_list_lock;
146 GSList *observer_list;
148 /* not unref this object, its a singlenton */
149 ModestEmailClipboard *clipboard;
151 /* Filter tree model */
154 GtkTreeRowReference *autoselect_reference;
155 ModestHeaderViewFilter filter;
157 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
158 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
160 gulong selection_changed_handler;
161 gulong acc_removed_handler;
163 GList *drag_begin_cached_selected_rows;
165 HeaderViewStatus status;
166 guint status_timeout;
167 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
169 ModestDatetimeFormatter *datetime_formatter;
171 GtkCellRenderer *renderer_subject;
172 GtkCellRenderer *renderer_address;
173 GtkCellRenderer *renderer_date_status;
175 GdkColor active_color;
176 GdkColor secondary_color;
180 gchar *filter_string;
181 gchar **filter_string_splitted;
182 gboolean filter_date_range;
183 time_t date_range_start;
184 time_t date_range_end;
187 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
188 struct _HeadersCountChangedHelper {
189 ModestHeaderView *self;
190 TnyFolderChange *change;
194 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
195 MODEST_TYPE_HEADER_VIEW, \
196 ModestHeaderViewPrivate))
200 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
203 HEADER_SELECTED_SIGNAL,
204 HEADER_ACTIVATED_SIGNAL,
205 ITEM_NOT_FOUND_SIGNAL,
206 MSG_COUNT_CHANGED_SIGNAL,
207 UPDATING_MSG_LIST_SIGNAL,
212 static GObjectClass *parent_class = NULL;
214 /* uncomment the following if you have defined any signals */
215 static guint signals[LAST_SIGNAL] = {0};
218 modest_header_view_get_type (void)
220 static GType my_type = 0;
222 static const GTypeInfo my_info = {
223 sizeof(ModestHeaderViewClass),
224 NULL, /* base init */
225 NULL, /* base finalize */
226 (GClassInitFunc) modest_header_view_class_init,
227 NULL, /* class finalize */
228 NULL, /* class data */
229 sizeof(ModestHeaderView),
231 (GInstanceInitFunc) modest_header_view_init,
235 static const GInterfaceInfo tny_folder_observer_info =
237 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
238 NULL, /* interface_finalize */
239 NULL /* interface_data */
241 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
245 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
246 &tny_folder_observer_info);
254 modest_header_view_class_init (ModestHeaderViewClass *klass)
256 GObjectClass *gobject_class;
257 gobject_class = (GObjectClass*) klass;
259 parent_class = g_type_class_peek_parent (klass);
260 gobject_class->finalize = modest_header_view_finalize;
261 gobject_class->dispose = modest_header_view_dispose;
263 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
265 signals[HEADER_SELECTED_SIGNAL] =
266 g_signal_new ("header_selected",
267 G_TYPE_FROM_CLASS (gobject_class),
269 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
271 g_cclosure_marshal_VOID__POINTER,
272 G_TYPE_NONE, 1, G_TYPE_POINTER);
274 signals[HEADER_ACTIVATED_SIGNAL] =
275 g_signal_new ("header_activated",
276 G_TYPE_FROM_CLASS (gobject_class),
278 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
280 gtk_marshal_VOID__POINTER_POINTER,
281 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
284 signals[ITEM_NOT_FOUND_SIGNAL] =
285 g_signal_new ("item_not_found",
286 G_TYPE_FROM_CLASS (gobject_class),
288 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
290 g_cclosure_marshal_VOID__INT,
291 G_TYPE_NONE, 1, G_TYPE_INT);
293 signals[MSG_COUNT_CHANGED_SIGNAL] =
294 g_signal_new ("msg_count_changed",
295 G_TYPE_FROM_CLASS (gobject_class),
297 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
299 modest_marshal_VOID__POINTER_POINTER,
300 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
302 signals[UPDATING_MSG_LIST_SIGNAL] =
303 g_signal_new ("updating-msg-list",
304 G_TYPE_FROM_CLASS (gobject_class),
306 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
308 g_cclosure_marshal_VOID__BOOLEAN,
309 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
311 #ifdef MODEST_TOOLKIT_HILDON2
312 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
318 tny_folder_observer_init (TnyFolderObserverIface *klass)
320 klass->update = folder_monitor_update;
323 static GtkTreeViewColumn*
324 get_new_column (const gchar *name, GtkCellRenderer *renderer,
325 gboolean resizable, gint sort_col_id, gboolean show_as_text,
326 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
328 GtkTreeViewColumn *column;
330 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
331 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
333 gtk_tree_view_column_set_resizable (column, resizable);
335 gtk_tree_view_column_set_expand (column, TRUE);
338 gtk_tree_view_column_add_attribute (column, renderer, "text",
340 if (sort_col_id >= 0)
341 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
343 gtk_tree_view_column_set_sort_indicator (column, FALSE);
344 gtk_tree_view_column_set_reorderable (column, TRUE);
347 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
354 remove_all_columns (ModestHeaderView *obj)
356 GList *columns, *cursor;
358 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
360 for (cursor = columns; cursor; cursor = cursor->next)
361 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
362 GTK_TREE_VIEW_COLUMN(cursor->data));
363 g_list_free (columns);
367 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
369 GtkTreeModel *sortable;
370 GtkTreeViewColumn *column=NULL;
371 GtkTreeSelection *selection = NULL;
372 GtkCellRenderer *renderer_header,
373 *renderer_attach, *renderer_compact_date_or_status;
374 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
375 *renderer_subject_box, *renderer_recpt,
377 ModestHeaderViewPrivate *priv;
378 GtkTreeViewColumn *compact_column = NULL;
381 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
382 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
384 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
386 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
388 /* TODO: check whether these renderers need to be freed */
389 renderer_attach = gtk_cell_renderer_pixbuf_new ();
390 renderer_priority = gtk_cell_renderer_pixbuf_new ();
391 renderer_header = gtk_cell_renderer_text_new ();
393 renderer_compact_header = modest_vbox_cell_renderer_new ();
394 renderer_recpt_box = modest_hbox_cell_renderer_new ();
395 renderer_subject_box = modest_hbox_cell_renderer_new ();
396 renderer_recpt = gtk_cell_renderer_text_new ();
397 priv->renderer_address = renderer_recpt;
398 priv->renderer_subject = gtk_cell_renderer_text_new ();
399 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
400 priv->renderer_date_status = renderer_compact_date_or_status;
402 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
403 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
404 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
405 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
406 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
407 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
408 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
409 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
410 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
411 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
412 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
413 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
414 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
415 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
417 #ifdef MODEST_TOOLKIT_HILDON2
418 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
420 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
421 #ifndef MODEST_TOOLKIT_GTK
422 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
423 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
425 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
426 g_object_set(G_OBJECT(renderer_header),
427 "ellipsize", PANGO_ELLIPSIZE_END,
429 g_object_set (G_OBJECT (priv->renderer_subject),
430 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
432 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
433 g_object_set (G_OBJECT (renderer_recpt),
434 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
436 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
437 g_object_set(G_OBJECT(renderer_compact_date_or_status),
438 "xalign", 1.0, "yalign", 0.1,
440 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
441 #ifdef MODEST_TOOLKIT_HILDON2
442 g_object_set (G_OBJECT (renderer_priority),
444 "xalign", 0.0, NULL);
445 g_object_set (G_OBJECT (renderer_attach),
447 "xalign", 0.0, NULL);
449 g_object_set (G_OBJECT (renderer_priority),
450 "yalign", 0.5, NULL);
451 g_object_set (G_OBJECT (renderer_attach),
452 "yalign", 0.0, NULL);
455 #ifdef MODEST_TOOLKIT_HILDON1
456 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
457 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
458 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
459 #elif MODEST_TOOLKIT_HILDON2
460 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
461 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
462 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
464 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
465 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
466 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
469 remove_all_columns (self);
471 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
472 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
473 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
475 /* Add new columns */
476 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
477 ModestHeaderViewColumn col =
478 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
480 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
481 g_printerr ("modest: invalid column %d in column list\n", col);
487 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
488 column = get_new_column (_("A"), renderer_attach, FALSE,
489 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
491 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
493 gtk_tree_view_column_set_fixed_width (column, 45);
497 case MODEST_HEADER_VIEW_COLUMN_FROM:
498 column = get_new_column (_("From"), renderer_header, TRUE,
499 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
501 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
502 GINT_TO_POINTER(TRUE));
505 case MODEST_HEADER_VIEW_COLUMN_TO:
506 column = get_new_column (_("To"), renderer_header, TRUE,
507 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
509 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
510 GINT_TO_POINTER(FALSE));
513 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
514 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
515 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
517 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
518 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
519 compact_column = column;
522 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
523 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
524 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
526 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
527 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
528 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
529 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
530 compact_column = column;
534 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
535 column = get_new_column (_("Subject"), renderer_header, TRUE,
536 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
538 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
542 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
543 column = get_new_column (_("Received"), renderer_header, TRUE,
544 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
546 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
547 GINT_TO_POINTER(TRUE));
550 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
551 column = get_new_column (_("Sent"), renderer_header, TRUE,
552 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
554 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
555 GINT_TO_POINTER(FALSE));
558 case MODEST_HEADER_VIEW_COLUMN_SIZE:
559 column = get_new_column (_("Size"), renderer_header, TRUE,
560 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
562 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
565 case MODEST_HEADER_VIEW_COLUMN_STATUS:
566 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
567 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
569 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
574 g_return_val_if_reached(FALSE);
577 /* we keep the column id around */
578 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
579 GINT_TO_POINTER(col));
581 /* we need this ptr when sorting the rows */
582 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
584 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
588 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
589 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
590 (GtkTreeIterCompareFunc) cmp_rows,
591 compact_column, NULL);
592 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
593 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
594 (GtkTreeIterCompareFunc) cmp_subject_rows,
595 compact_column, NULL);
599 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
605 datetime_format_changed (ModestDatetimeFormatter *formatter,
606 ModestHeaderView *self)
608 gtk_widget_queue_draw (GTK_WIDGET (self));
612 modest_header_view_init (ModestHeaderView *obj)
614 ModestHeaderViewPrivate *priv;
617 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
619 priv->show_latest = 0;
622 priv->is_outbox = FALSE;
624 priv->monitor = NULL;
625 priv->observers_lock = g_mutex_new ();
626 priv->autoselect_reference = NULL;
628 priv->status = HEADER_VIEW_INIT;
629 priv->status_timeout = 0;
630 priv->notify_status = TRUE;
632 priv->observer_list_lock = g_mutex_new();
633 priv->observer_list = NULL;
635 priv->clipboard = modest_runtime_get_email_clipboard ();
636 priv->hidding_ids = NULL;
637 priv->n_selected = 0;
638 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
639 priv->filter_string = NULL;
640 priv->filter_string_splitted = NULL;
641 priv->filter_date_range = FALSE;
642 priv->selection_changed_handler = 0;
643 priv->acc_removed_handler = 0;
645 /* Sort parameters */
646 for (j=0; j < 2; j++) {
647 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
648 priv->sort_colid[j][i] = -1;
649 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
653 priv->datetime_formatter = modest_datetime_formatter_new ();
654 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
655 G_CALLBACK (datetime_format_changed), (gpointer) obj);
657 setup_drag_and_drop (GTK_WIDGET(obj));
661 modest_header_view_dispose (GObject *obj)
663 ModestHeaderView *self;
664 ModestHeaderViewPrivate *priv;
665 GtkTreeSelection *sel;
667 self = MODEST_HEADER_VIEW(obj);
668 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
670 if (priv->datetime_formatter) {
671 g_object_unref (priv->datetime_formatter);
672 priv->datetime_formatter = NULL;
675 /* Free in the dispose to avoid unref cycles */
677 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
678 g_object_unref (G_OBJECT (priv->folder));
682 /* We need to do this here in the dispose because the
683 selection won't exist when finalizing */
684 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
685 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
686 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
687 priv->selection_changed_handler = 0;
690 G_OBJECT_CLASS(parent_class)->dispose (obj);
694 modest_header_view_finalize (GObject *obj)
696 ModestHeaderView *self;
697 ModestHeaderViewPrivate *priv;
699 self = MODEST_HEADER_VIEW(obj);
700 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
702 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
703 priv->acc_removed_handler)) {
704 g_signal_handler_disconnect (modest_runtime_get_account_store (),
705 priv->acc_removed_handler);
708 /* There is no need to lock because there should not be any
709 * reference to self now. */
710 g_mutex_free(priv->observer_list_lock);
711 g_slist_free(priv->observer_list);
713 g_mutex_lock (priv->observers_lock);
715 tny_folder_monitor_stop (priv->monitor);
716 g_object_unref (G_OBJECT (priv->monitor));
718 g_mutex_unlock (priv->observers_lock);
719 g_mutex_free (priv->observers_lock);
721 /* Clear hidding array created by cut operation */
722 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
724 if (priv->autoselect_reference != NULL) {
725 gtk_tree_row_reference_free (priv->autoselect_reference);
726 priv->autoselect_reference = NULL;
729 if (priv->filter_string) {
730 g_free (priv->filter_string);
733 if (priv->filter_string_splitted) {
734 g_strfreev (priv->filter_string_splitted);
737 G_OBJECT_CLASS(parent_class)->finalize (obj);
742 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
745 GtkTreeSelection *sel;
746 ModestHeaderView *self;
747 ModestHeaderViewPrivate *priv;
749 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
752 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
753 self = MODEST_HEADER_VIEW(obj);
754 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
756 modest_header_view_set_style (self, style);
758 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
759 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
760 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
762 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
763 TRUE); /* alternating row colors */
765 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
766 priv->selection_changed_handler =
767 g_signal_connect_after (sel, "changed",
768 G_CALLBACK(on_selection_changed), self);
770 g_signal_connect (self, "row-activated",
771 G_CALLBACK (on_header_row_activated), NULL);
773 #ifndef MODEST_TOOLKIT_HILDON2
774 g_signal_connect (self, "focus-in-event",
775 G_CALLBACK(on_focus_in), NULL);
776 g_signal_connect (self, "focus-out-event",
777 G_CALLBACK(on_focus_out), NULL);
780 g_signal_connect (self, "button-press-event",
781 G_CALLBACK(on_button_press_event), NULL);
782 g_signal_connect (self, "button-release-event",
783 G_CALLBACK(on_button_release_event), NULL);
785 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
787 G_CALLBACK (on_account_removed),
790 g_signal_connect (self, "expose-event",
791 G_CALLBACK(modest_header_view_on_expose_event),
794 return GTK_WIDGET(self);
799 modest_header_view_count_selected_headers (ModestHeaderView *self)
801 GtkTreeSelection *sel;
804 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
806 /* Get selection object and check selected rows count */
807 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
808 selected_rows = gtk_tree_selection_count_selected_rows (sel);
810 return selected_rows;
814 modest_header_view_has_selected_headers (ModestHeaderView *self)
816 GtkTreeSelection *sel;
819 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
821 /* Get selection object and check selected rows count */
822 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
823 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
830 modest_header_view_get_selected_headers (ModestHeaderView *self)
832 GtkTreeSelection *sel;
833 TnyList *header_list = NULL;
835 GList *list, *tmp = NULL;
836 GtkTreeModel *tree_model = NULL;
839 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
842 /* Get selected rows */
843 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
844 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
847 header_list = tny_simple_list_new();
849 list = g_list_reverse (list);
852 /* get header from selection */
853 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
854 gtk_tree_model_get (tree_model, &iter,
855 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
857 /* Prepend to list */
858 tny_list_prepend (header_list, G_OBJECT (header));
859 g_object_unref (G_OBJECT (header));
861 tmp = g_list_next (tmp);
864 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
871 /* scroll our list view so the selected item is visible */
873 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
875 #ifdef MODEST_TOOLKIT_GTK
877 GtkTreePath *selected_path;
878 GtkTreePath *start, *end;
882 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
883 selected_path = gtk_tree_model_get_path (model, iter);
885 start = gtk_tree_path_new ();
886 end = gtk_tree_path_new ();
888 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
890 if (gtk_tree_path_compare (selected_path, start) < 0 ||
891 gtk_tree_path_compare (end, selected_path) < 0)
892 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
893 selected_path, NULL, TRUE,
896 gtk_tree_path_free (selected_path);
897 gtk_tree_path_free (start);
898 gtk_tree_path_free (end);
900 #endif /* MODEST_TOOLKIT_GTK */
905 modest_header_view_select_next (ModestHeaderView *self)
907 GtkTreeSelection *sel;
912 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
914 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
915 path = get_selected_row (GTK_TREE_VIEW(self), &model);
916 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
917 /* Unselect previous path */
918 gtk_tree_selection_unselect_path (sel, path);
920 /* Move path down and selects new one */
921 if (gtk_tree_model_iter_next (model, &iter)) {
922 gtk_tree_selection_select_iter (sel, &iter);
923 scroll_to_selected (self, &iter, FALSE);
925 gtk_tree_path_free(path);
931 modest_header_view_select_prev (ModestHeaderView *self)
933 GtkTreeSelection *sel;
938 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
940 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
941 path = get_selected_row (GTK_TREE_VIEW(self), &model);
942 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
943 /* Unselect previous path */
944 gtk_tree_selection_unselect_path (sel, path);
947 if (gtk_tree_path_prev (path)) {
948 gtk_tree_model_get_iter (model, &iter, path);
950 /* Select the new one */
951 gtk_tree_selection_select_iter (sel, &iter);
952 scroll_to_selected (self, &iter, TRUE);
955 gtk_tree_path_free (path);
960 modest_header_view_get_columns (ModestHeaderView *self)
962 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
964 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
970 modest_header_view_set_style (ModestHeaderView *self,
971 ModestHeaderViewStyle style)
973 ModestHeaderViewPrivate *priv;
974 gboolean show_col_headers = FALSE;
975 ModestHeaderViewStyle old_style;
977 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
978 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
981 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
982 if (priv->style == style)
983 return TRUE; /* nothing to do */
986 case MODEST_HEADER_VIEW_STYLE_DETAILS:
987 show_col_headers = TRUE;
989 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
992 g_return_val_if_reached (FALSE);
994 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
995 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
997 old_style = priv->style;
1004 ModestHeaderViewStyle
1005 modest_header_view_get_style (ModestHeaderView *self)
1007 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1009 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1012 /* This is used to automatically select the first header if the user
1013 * has not selected any header yet.
1016 modest_header_view_on_expose_event(GtkTreeView *header_view,
1017 GdkEventExpose *event,
1020 GtkTreeSelection *sel;
1021 GtkTreeModel *model;
1022 GtkTreeIter tree_iter;
1023 ModestHeaderViewPrivate *priv;
1025 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1026 model = gtk_tree_view_get_model(header_view);
1031 #ifdef MODEST_TOOLKIT_HILDON2
1034 sel = gtk_tree_view_get_selection(header_view);
1035 if(!gtk_tree_selection_count_selected_rows(sel)) {
1036 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1037 GtkTreePath *tree_iter_path;
1038 /* Prevent the widget from getting the focus
1039 when selecting the first item */
1040 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1041 g_object_set(header_view, "can-focus", FALSE, NULL);
1042 gtk_tree_selection_select_iter(sel, &tree_iter);
1043 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1044 g_object_set(header_view, "can-focus", TRUE, NULL);
1045 if (priv->autoselect_reference) {
1046 gtk_tree_row_reference_free (priv->autoselect_reference);
1048 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1049 gtk_tree_path_free (tree_iter_path);
1052 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1053 gboolean moved_selection = FALSE;
1054 GtkTreePath * last_path;
1055 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1056 moved_selection = TRUE;
1060 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1061 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1062 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1063 moved_selection = TRUE;
1064 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1066 gtk_tree_path_free (last_path);
1068 if (moved_selection) {
1069 gtk_tree_row_reference_free (priv->autoselect_reference);
1070 priv->autoselect_reference = NULL;
1073 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1074 GtkTreePath *current_path;
1075 current_path = gtk_tree_model_get_path (model, &tree_iter);
1076 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1077 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1078 g_object_set(header_view, "can-focus", FALSE, NULL);
1079 gtk_tree_selection_unselect_all (sel);
1080 gtk_tree_selection_select_iter(sel, &tree_iter);
1081 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1082 g_object_set(header_view, "can-focus", TRUE, NULL);
1083 gtk_tree_row_reference_free (priv->autoselect_reference);
1084 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1086 gtk_tree_path_free (current_path);
1087 gtk_tree_path_free (last_path);
1097 modest_header_view_get_folder (ModestHeaderView *self)
1099 ModestHeaderViewPrivate *priv;
1101 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1103 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1106 g_object_ref (priv->folder);
1108 return priv->folder;
1112 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1118 ModestHeaderView *self;
1119 ModestHeaderViewPrivate *priv;
1121 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1123 self = MODEST_HEADER_VIEW (user_data);
1124 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1126 if (cancelled || err)
1129 /* Add IDLE observer (monitor) and another folder observer for
1130 new messages (self) */
1131 g_mutex_lock (priv->observers_lock);
1132 if (priv->monitor) {
1133 tny_folder_monitor_stop (priv->monitor);
1134 g_object_unref (G_OBJECT (priv->monitor));
1136 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1137 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1138 tny_folder_monitor_start (priv->monitor);
1139 g_mutex_unlock (priv->observers_lock);
1143 modest_header_view_set_folder_intern (ModestHeaderView *self,
1149 ModestHeaderViewPrivate *priv;
1150 GList *cols, *cursor;
1151 GtkTreeModel *filter_model, *sortable;
1153 GtkSortType sort_type;
1155 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1157 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1158 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (headers), priv->show_latest);
1160 /* Start the monitor in the callback of the
1161 tny_gtk_header_list_model_set_folder call. It's crucial to
1162 do it there and not just after the call because we want the
1163 monitor to observe only the headers returned by the
1164 tny_folder_get_headers_async call that it's inside the
1165 tny_gtk_header_list_model_set_folder call. This way the
1166 monitor infrastructure could successfully cope with
1167 duplicates. For example if a tny_folder_add_msg_async is
1168 happening while tny_gtk_header_list_model_set_folder is
1169 invoked, then the first call could add a header that will
1170 be added again by tny_gtk_header_list_model_set_folder, so
1171 we'd end up with duplicate headers. sergio */
1172 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1174 set_folder_intern_get_headers_async_cb,
1177 /* Create a tree model filter to hide and show rows for cut operations */
1178 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (headers), NULL);
1179 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1180 filter_row, self, NULL);
1181 g_object_unref (headers);
1183 /* Init filter_row function to examine empty status */
1184 priv->status = HEADER_VIEW_INIT;
1186 /* Create sortable model */
1187 sortable = gtk_tree_model_sort_new_with_model (filter_model);
1188 g_object_unref (filter_model);
1190 /* install our special sorting functions */
1191 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1193 /* Restore sort column id */
1195 type = modest_tny_folder_guess_folder_type (folder);
1196 if (type == TNY_FOLDER_TYPE_INVALID)
1197 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1199 sort_colid = modest_header_view_get_sort_column_id (self, type);
1200 sort_type = modest_header_view_get_sort_type (self, type);
1201 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1204 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1205 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1206 (GtkTreeIterCompareFunc) cmp_rows,
1208 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1209 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1210 (GtkTreeIterCompareFunc) cmp_subject_rows,
1215 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
1216 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1217 g_object_unref (sortable);
1224 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1226 GtkSortType sort_type)
1228 ModestHeaderViewPrivate *priv = NULL;
1229 GtkTreeModel *sortable = NULL;
1232 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1233 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1235 /* Get model and private data */
1236 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1237 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1239 /* Sort tree model */
1240 type = modest_tny_folder_guess_folder_type (priv->folder);
1241 if (type == TNY_FOLDER_TYPE_INVALID)
1242 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1244 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1247 /* Store new sort parameters */
1248 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1253 modest_header_view_set_sort_params (ModestHeaderView *self,
1255 GtkSortType sort_type,
1258 ModestHeaderViewPrivate *priv;
1259 ModestHeaderViewStyle style;
1261 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1262 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1263 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1265 style = modest_header_view_get_style (self);
1266 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1268 priv->sort_colid[style][type] = sort_colid;
1269 priv->sort_type[style][type] = sort_type;
1273 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1276 ModestHeaderViewPrivate *priv;
1277 ModestHeaderViewStyle style;
1279 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1280 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1282 style = modest_header_view_get_style (self);
1283 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1285 return priv->sort_colid[style][type];
1289 modest_header_view_get_sort_type (ModestHeaderView *self,
1292 ModestHeaderViewPrivate *priv;
1293 ModestHeaderViewStyle style;
1295 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1296 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1298 style = modest_header_view_get_style (self);
1299 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1301 return priv->sort_type[style][type];
1305 ModestHeaderView *header_view;
1306 RefreshAsyncUserCallback cb;
1311 folder_refreshed_cb (ModestMailOperation *mail_op,
1315 ModestHeaderViewPrivate *priv;
1316 SetFolderHelper *info;
1318 info = (SetFolderHelper*) user_data;
1320 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1324 info->cb (mail_op, folder, info->user_data);
1326 /* Start the folder count changes observer. We do not need it
1327 before the refresh. Note that the monitor could still be
1328 called for this refresh but now we know that the callback
1329 was previously called */
1330 g_mutex_lock (priv->observers_lock);
1331 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1332 g_mutex_unlock (priv->observers_lock);
1334 /* Notify the observers that the update is over */
1335 g_signal_emit (G_OBJECT (info->header_view),
1336 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1338 /* Allow filtering notifications from now on if the current
1339 folder is still the same (if not then the user has selected
1340 another one to refresh, we should wait until that refresh
1342 if (priv->folder == folder)
1343 priv->notify_status = TRUE;
1346 g_object_unref (info->header_view);
1351 refresh_folder_error_handler (ModestMailOperation *mail_op,
1354 const GError *error = modest_mail_operation_get_error (mail_op);
1356 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1357 error->code == TNY_IO_ERROR_WRITE ||
1358 error->code == TNY_IO_ERROR_READ) {
1359 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1360 /* If the mail op has been cancelled then it's not an error: don't show any message */
1361 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1362 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1363 modest_platform_information_banner (NULL, NULL, msg);
1370 modest_header_view_set_folder (ModestHeaderView *self,
1373 ModestWindow *progress_window,
1374 RefreshAsyncUserCallback callback,
1377 ModestHeaderViewPrivate *priv;
1379 g_return_if_fail (self);
1381 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1384 if (priv->status_timeout) {
1385 g_source_remove (priv->status_timeout);
1386 priv->status_timeout = 0;
1389 g_mutex_lock (priv->observers_lock);
1390 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1391 g_object_unref (priv->folder);
1392 priv->folder = NULL;
1393 g_mutex_unlock (priv->observers_lock);
1397 GtkTreeSelection *selection;
1398 SetFolderHelper *info;
1399 ModestMailOperation *mail_op = NULL;
1401 /* Set folder in the model */
1402 modest_header_view_set_folder_intern (self, folder, refresh);
1404 /* Pick my reference. Nothing to do with the mail operation */
1405 priv->folder = g_object_ref (folder);
1407 /* Do not notify about filterings until the refresh finishes */
1408 priv->notify_status = FALSE;
1410 /* Clear the selection if exists */
1411 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1412 gtk_tree_selection_unselect_all(selection);
1413 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1415 /* Notify the observers that the update begins */
1416 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1419 /* create the helper */
1420 info = g_malloc0 (sizeof (SetFolderHelper));
1421 info->header_view = g_object_ref (self);
1422 info->cb = callback;
1423 info->user_data = user_data;
1425 /* Create the mail operation (source will be the parent widget) */
1426 if (progress_window)
1427 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1428 refresh_folder_error_handler,
1431 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1434 /* Refresh the folder asynchronously */
1435 modest_mail_operation_refresh_folder (mail_op,
1437 folder_refreshed_cb,
1440 folder_refreshed_cb (mail_op, folder, info);
1444 g_object_unref (mail_op);
1446 g_mutex_lock (priv->observers_lock);
1448 if (priv->monitor) {
1449 tny_folder_monitor_stop (priv->monitor);
1450 g_object_unref (G_OBJECT (priv->monitor));
1451 priv->monitor = NULL;
1454 if (priv->autoselect_reference) {
1455 gtk_tree_row_reference_free (priv->autoselect_reference);
1456 priv->autoselect_reference = NULL;
1459 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1461 modest_header_view_notify_observers(self, NULL, NULL);
1463 g_mutex_unlock (priv->observers_lock);
1465 /* Notify the observers that the update is over */
1466 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1472 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1473 GtkTreeViewColumn *column, gpointer userdata)
1475 ModestHeaderView *self = NULL;
1477 GtkTreeModel *model = NULL;
1478 TnyHeader *header = NULL;
1479 TnyHeaderFlags flags;
1481 self = MODEST_HEADER_VIEW (treeview);
1483 model = gtk_tree_view_get_model (treeview);
1484 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1487 /* get the first selected item */
1488 gtk_tree_model_get (model, &iter,
1489 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1490 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1493 /* Dont open DELETED messages */
1494 if (flags & TNY_HEADER_FLAG_DELETED) {
1497 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1498 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1499 modest_platform_information_banner (NULL, NULL, msg);
1505 g_signal_emit (G_OBJECT(self),
1506 signals[HEADER_ACTIVATED_SIGNAL],
1512 g_object_unref (G_OBJECT (header));
1517 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1519 GtkTreeModel *model;
1520 TnyHeader *header = NULL;
1521 GtkTreePath *path = NULL;
1523 ModestHeaderView *self;
1524 GList *selected = NULL;
1526 g_return_if_fail (sel);
1527 g_return_if_fail (user_data);
1529 self = MODEST_HEADER_VIEW (user_data);
1531 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1532 if (selected != NULL)
1533 path = (GtkTreePath *) selected->data;
1534 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1535 return; /* msg was _un_selected */
1537 gtk_tree_model_get (model, &iter,
1538 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1542 g_signal_emit (G_OBJECT(self),
1543 signals[HEADER_SELECTED_SIGNAL],
1546 g_object_unref (G_OBJECT (header));
1548 /* free all items in 'selected' */
1549 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1550 g_list_free (selected);
1554 /* PROTECTED method. It's useful when we want to force a given
1555 selection to reload a msg. For example if we have selected a header
1556 in offline mode, when Modest become online, we want to reload the
1557 message automatically without an user click over the header */
1559 _modest_header_view_change_selection (GtkTreeSelection *selection,
1562 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1563 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1565 on_selection_changed (selection, user_data);
1569 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1576 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1580 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1584 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1592 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1600 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1601 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1605 case TNY_HEADER_FLAG_ATTACHMENTS:
1607 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1608 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1609 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1610 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1612 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1613 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1615 return cmp ? cmp : t1 - t2;
1617 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1618 TnyHeader *header1 = NULL, *header2 = NULL;
1620 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1621 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1622 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1623 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1625 /* This is for making priority values respect the intuitive sort relationship
1626 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1628 if (header1 && header2) {
1629 cmp = compare_priorities (tny_header_get_priority (header1),
1630 tny_header_get_priority (header2));
1631 g_object_unref (header1);
1632 g_object_unref (header2);
1634 return cmp ? cmp : t1 - t2;
1640 return &iter1 - &iter2; /* oughhhh */
1645 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1652 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1654 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1655 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1656 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1657 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1659 /* Do not use the prefixes for sorting. Consume all the blank
1660 spaces for sorting */
1661 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1662 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1665 /* If they're equal based on subject without prefix then just
1666 sort them by length. This will show messages like this.
1673 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1680 /* Drag and drop stuff */
1682 drag_data_get_cb (GtkWidget *widget,
1683 GdkDragContext *context,
1684 GtkSelectionData *selection_data,
1689 ModestHeaderView *self = NULL;
1690 ModestHeaderViewPrivate *priv = NULL;
1692 self = MODEST_HEADER_VIEW (widget);
1693 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1695 /* Set the data. Do not use the current selection because it
1696 could be different than the selection at the beginning of
1698 modest_dnd_selection_data_set_paths (selection_data,
1699 priv->drag_begin_cached_selected_rows);
1703 * We're caching the selected rows at the beginning because the
1704 * selection could change between drag-begin and drag-data-get, for
1705 * example if we have a set of rows already selected, and then we
1706 * click in one of them (without SHIFT key pressed) and begin a drag,
1707 * the selection at that moment contains all the selected lines, but
1708 * after dropping the selection, the release event provokes that only
1709 * the row used to begin the drag is selected, so at the end the
1710 * drag&drop affects only one rows instead of all the selected ones.
1714 drag_begin_cb (GtkWidget *widget,
1715 GdkDragContext *context,
1718 ModestHeaderView *self = NULL;
1719 ModestHeaderViewPrivate *priv = NULL;
1720 GtkTreeSelection *selection;
1722 self = MODEST_HEADER_VIEW (widget);
1723 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1725 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1726 priv->drag_begin_cached_selected_rows =
1727 gtk_tree_selection_get_selected_rows (selection, NULL);
1731 * We use the drag-end signal to clear the cached selection, we use
1732 * this because this allways happens, whether or not the d&d was a
1736 drag_end_cb (GtkWidget *widget,
1740 ModestHeaderView *self = NULL;
1741 ModestHeaderViewPrivate *priv = NULL;
1743 self = MODEST_HEADER_VIEW (widget);
1744 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1746 /* Free cached data */
1747 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1748 g_list_free (priv->drag_begin_cached_selected_rows);
1749 priv->drag_begin_cached_selected_rows = NULL;
1752 /* Header view drag types */
1753 const GtkTargetEntry header_view_drag_types[] = {
1754 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1758 enable_drag_and_drop (GtkWidget *self)
1760 #ifdef MODEST_TOOLKIT_HILDON2
1763 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1764 header_view_drag_types,
1765 G_N_ELEMENTS (header_view_drag_types),
1766 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1770 disable_drag_and_drop (GtkWidget *self)
1772 #ifdef MODEST_TOOLKIT_HILDON2
1775 gtk_drag_source_unset (self);
1779 setup_drag_and_drop (GtkWidget *self)
1781 #ifdef MODEST_TOOLKIT_HILDON2
1784 enable_drag_and_drop(self);
1785 g_signal_connect(G_OBJECT (self), "drag_data_get",
1786 G_CALLBACK(drag_data_get_cb), NULL);
1788 g_signal_connect(G_OBJECT (self), "drag_begin",
1789 G_CALLBACK(drag_begin_cb), NULL);
1791 g_signal_connect(G_OBJECT (self), "drag_end",
1792 G_CALLBACK(drag_end_cb), NULL);
1795 static GtkTreePath *
1796 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1798 GtkTreePath *path = NULL;
1799 GtkTreeSelection *sel = NULL;
1802 sel = gtk_tree_view_get_selection(self);
1803 rows = gtk_tree_selection_get_selected_rows (sel, model);
1805 if ((rows == NULL) || (g_list_length(rows) != 1))
1808 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1813 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1819 #ifndef MODEST_TOOLKIT_HILDON2
1821 * This function moves the tree view scroll to the current selected
1822 * row when the widget grabs the focus
1825 on_focus_in (GtkWidget *self,
1826 GdkEventFocus *event,
1829 GtkTreeSelection *selection;
1830 GtkTreeModel *model;
1831 GList *selected = NULL;
1832 GtkTreePath *selected_path = NULL;
1834 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1838 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1839 /* If none selected yet, pick the first one */
1840 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1844 /* Return if the model is empty */
1845 if (!gtk_tree_model_get_iter_first (model, &iter))
1848 path = gtk_tree_model_get_path (model, &iter);
1849 gtk_tree_selection_select_path (selection, path);
1850 gtk_tree_path_free (path);
1853 /* Need to get the all the rows because is selection multiple */
1854 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1855 if (selected == NULL) return FALSE;
1856 selected_path = (GtkTreePath *) selected->data;
1859 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1860 g_list_free (selected);
1866 on_focus_out (GtkWidget *self,
1867 GdkEventFocus *event,
1871 if (!gtk_widget_is_focus (self)) {
1872 GtkTreeSelection *selection = NULL;
1873 GList *selected_rows = NULL;
1874 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1875 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1876 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1877 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1878 gtk_tree_selection_unselect_all (selection);
1879 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1880 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1881 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1882 g_list_free (selected_rows);
1890 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1892 enable_drag_and_drop(self);
1897 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1899 GtkTreeSelection *selection = NULL;
1900 GtkTreePath *path = NULL;
1901 gboolean already_selected = FALSE, already_opened = FALSE;
1902 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1904 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1906 GtkTreeModel *model;
1908 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1909 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1911 /* Get header from model */
1912 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1913 if (gtk_tree_model_get_iter (model, &iter, path)) {
1914 GValue value = {0,};
1917 gtk_tree_model_get_value (model, &iter,
1918 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1920 header = (TnyHeader *) g_value_get_object (&value);
1921 if (TNY_IS_HEADER (header)) {
1922 status = modest_tny_all_send_queues_get_msg_status (header);
1923 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1926 g_value_unset (&value);
1930 /* Enable drag and drop only if the user clicks on a row that
1931 it's already selected. If not, let him select items using
1932 the pointer. If the message is in an OUTBOX and in sending
1933 status disable drag and drop as well */
1934 if (!already_selected ||
1935 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1937 disable_drag_and_drop(self);
1940 gtk_tree_path_free(path);
1942 /* If it's already opened then do not let the button-press
1943 event go on because it'll perform a message open because
1944 we're clicking on to an already selected header */
1949 folder_monitor_update (TnyFolderObserver *self,
1950 TnyFolderChange *change)
1952 ModestHeaderViewPrivate *priv = NULL;
1953 TnyFolderChangeChanged changed;
1954 TnyFolder *folder = NULL;
1956 changed = tny_folder_change_get_changed (change);
1958 /* Do not notify the observers if the folder of the header
1959 view has changed before this call to the observer
1961 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1962 folder = tny_folder_change_get_folder (change);
1963 if (folder != priv->folder)
1966 MODEST_DEBUG_BLOCK (
1967 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1968 g_print ("ADDED %d/%d (r/t) \n",
1969 tny_folder_change_get_new_unread_count (change),
1970 tny_folder_change_get_new_all_count (change));
1971 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1972 g_print ("ALL COUNT %d\n",
1973 tny_folder_change_get_new_all_count (change));
1974 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1975 g_print ("UNREAD COUNT %d\n",
1976 tny_folder_change_get_new_unread_count (change));
1977 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1978 g_print ("EXPUNGED %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_FOLDER_RENAME)
1982 g_print ("FOLDER RENAME\n");
1983 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1984 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1985 tny_folder_change_get_new_unread_count (change),
1986 tny_folder_change_get_new_all_count (change));
1987 g_print ("---------------------------------------------------\n");
1990 /* Check folder count */
1991 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
1992 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
1994 g_mutex_lock (priv->observers_lock);
1996 /* Emit signal to evaluate how headers changes affects
1997 to the window view */
1998 g_signal_emit (G_OBJECT(self),
1999 signals[MSG_COUNT_CHANGED_SIGNAL],
2002 /* Added or removed headers, so data stored on cliboard are invalid */
2003 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2004 modest_email_clipboard_clear (priv->clipboard);
2006 g_mutex_unlock (priv->observers_lock);
2012 g_object_unref (folder);
2016 modest_header_view_is_empty (ModestHeaderView *self)
2018 ModestHeaderViewPrivate *priv;
2020 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2022 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2024 return priv->status == HEADER_VIEW_EMPTY;
2028 modest_header_view_clear (ModestHeaderView *self)
2030 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2032 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2036 modest_header_view_copy_selection (ModestHeaderView *header_view)
2038 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2040 /* Copy selection */
2041 _clipboard_set_selected_data (header_view, FALSE);
2045 modest_header_view_cut_selection (ModestHeaderView *header_view)
2047 ModestHeaderViewPrivate *priv = NULL;
2048 const gchar **hidding = NULL;
2049 guint i, n_selected;
2051 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2053 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2055 /* Copy selection */
2056 _clipboard_set_selected_data (header_view, TRUE);
2058 /* Get hidding ids */
2059 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2061 /* Clear hidding array created by previous cut operation */
2062 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2064 /* Copy hidding array */
2065 priv->n_selected = n_selected;
2066 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2067 for (i=0; i < n_selected; i++)
2068 priv->hidding_ids[i] = g_strdup(hidding[i]);
2070 /* Hide cut headers */
2071 modest_header_view_refilter (header_view);
2078 _clipboard_set_selected_data (ModestHeaderView *header_view,
2081 ModestHeaderViewPrivate *priv = NULL;
2082 TnyList *headers = NULL;
2084 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2085 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2087 /* Set selected data on clipboard */
2088 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2089 headers = modest_header_view_get_selected_headers (header_view);
2090 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2093 g_object_unref (headers);
2097 ModestHeaderView *self;
2102 notify_filter_change (gpointer data)
2104 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2106 g_signal_emit (info->self,
2107 signals[MSG_COUNT_CHANGED_SIGNAL],
2108 0, info->folder, NULL);
2114 notify_filter_change_destroy (gpointer data)
2116 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2117 ModestHeaderViewPrivate *priv;
2119 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2120 priv->status_timeout = 0;
2122 g_object_unref (info->self);
2123 g_object_unref (info->folder);
2124 g_slice_free (NotifyFilterInfo, info);
2128 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2130 /* For the moment we only need to filter outbox */
2131 return priv->is_outbox;
2135 header_match_string (TnyHeader *header, gchar **words)
2142 gchar *subject_fold;
2148 gchar **current_word;
2151 subject = tny_header_dup_subject (header);
2152 cc = tny_header_dup_cc (header);
2153 bcc = tny_header_dup_bcc (header);
2154 to = tny_header_dup_to (header);
2155 from = tny_header_dup_from (header);
2157 subject_fold = g_utf8_casefold (subject, -1);
2159 bcc_fold = g_utf8_casefold (bcc, -1);
2161 cc_fold = g_utf8_casefold (cc, -1);
2163 to_fold = g_utf8_casefold (to, -1);
2165 from_fold = g_utf8_casefold (from, -1);
2170 for (current_word = words; *current_word != NULL; current_word++) {
2172 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2173 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2174 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2175 || (to && g_strstr_len (to_fold, -1, *current_word))
2176 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2184 g_free (subject_fold);
2194 filter_row (GtkTreeModel *model,
2198 ModestHeaderViewPrivate *priv = NULL;
2199 TnyHeaderFlags flags;
2200 TnyHeader *header = NULL;
2203 gboolean visible = TRUE;
2204 gboolean found = FALSE;
2205 GValue value = {0,};
2206 HeaderViewStatus old_status;
2208 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2209 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2211 /* Get header from model */
2212 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2213 flags = (TnyHeaderFlags) g_value_get_int (&value);
2214 g_value_unset (&value);
2215 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2216 header = (TnyHeader *) g_value_get_object (&value);
2217 g_value_unset (&value);
2219 /* Get message id from header (ensure is a valid id) */
2225 /* Hide deleted and mark as deleted heders */
2226 if (flags & TNY_HEADER_FLAG_DELETED ||
2227 flags & TNY_HEADER_FLAG_EXPUNGED) {
2232 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2233 if (current_folder_needs_filtering (priv) &&
2234 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2240 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2241 if (current_folder_needs_filtering (priv) &&
2242 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2248 if (visible && priv->filter_string) {
2249 if (!header_match_string (header, priv->filter_string_splitted)) {
2253 if (priv->filter_date_range) {
2254 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2255 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2262 /* If no data on clipboard, return always TRUE */
2263 if (modest_email_clipboard_cleared(priv->clipboard)) {
2269 if (priv->hidding_ids != NULL) {
2270 id = tny_header_dup_message_id (header);
2271 for (i=0; i < priv->n_selected && !found; i++)
2272 if (priv->hidding_ids[i] != NULL && id != NULL)
2273 found = (!strcmp (priv->hidding_ids[i], id));
2280 old_status = priv->status;
2281 priv->status = ((gboolean) priv->status) && !visible;
2282 if ((priv->notify_status) && (priv->status != old_status)) {
2283 if (priv->status_timeout)
2284 g_source_remove (priv->status_timeout);
2287 NotifyFilterInfo *info;
2289 info = g_slice_new0 (NotifyFilterInfo);
2290 info->self = g_object_ref (G_OBJECT (user_data));
2292 info->folder = tny_header_get_folder (header);
2293 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2294 notify_filter_change,
2296 notify_filter_change_destroy);
2304 _clear_hidding_filter (ModestHeaderView *header_view)
2306 ModestHeaderViewPrivate *priv = NULL;
2309 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2310 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2312 if (priv->hidding_ids != NULL) {
2313 for (i=0; i < priv->n_selected; i++)
2314 g_free (priv->hidding_ids[i]);
2315 g_free(priv->hidding_ids);
2320 modest_header_view_refilter (ModestHeaderView *header_view)
2322 GtkTreeModel *model, *sortable = NULL;
2323 ModestHeaderViewPrivate *priv = NULL;
2325 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2326 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2328 /* Hide cut headers */
2329 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2330 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2331 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2332 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2333 priv->status = HEADER_VIEW_INIT;
2334 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2340 * Called when an account is removed. If I'm showing a folder of the
2341 * account that has been removed then clear the view
2344 on_account_removed (TnyAccountStore *self,
2345 TnyAccount *account,
2348 ModestHeaderViewPrivate *priv = NULL;
2350 /* Ignore changes in transport accounts */
2351 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2354 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2357 TnyAccount *my_account;
2359 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2360 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2361 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2362 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2364 my_account = tny_folder_get_account (priv->folder);
2368 if (my_account == account)
2369 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2370 g_object_unref (my_account);
2376 modest_header_view_add_observer(ModestHeaderView *header_view,
2377 ModestHeaderViewObserver *observer)
2379 ModestHeaderViewPrivate *priv;
2381 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2382 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2384 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2386 g_mutex_lock(priv->observer_list_lock);
2387 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2388 g_mutex_unlock(priv->observer_list_lock);
2392 modest_header_view_remove_observer(ModestHeaderView *header_view,
2393 ModestHeaderViewObserver *observer)
2395 ModestHeaderViewPrivate *priv;
2397 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2398 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2400 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2402 g_mutex_lock(priv->observer_list_lock);
2403 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2404 g_mutex_unlock(priv->observer_list_lock);
2408 modest_header_view_notify_observers(ModestHeaderView *header_view,
2409 GtkTreeModel *model,
2410 const gchar *tny_folder_id)
2412 ModestHeaderViewPrivate *priv = NULL;
2414 ModestHeaderViewObserver *observer;
2417 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2419 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2421 g_mutex_lock(priv->observer_list_lock);
2422 iter = priv->observer_list;
2423 while(iter != NULL){
2424 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2425 modest_header_view_observer_update(observer, model,
2427 iter = g_slist_next(iter);
2429 g_mutex_unlock(priv->observer_list_lock);
2433 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2435 ModestHeaderViewPrivate *priv = NULL;
2437 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2438 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2442 modest_header_view_set_filter (ModestHeaderView *self,
2443 ModestHeaderViewFilter filter)
2445 ModestHeaderViewPrivate *priv;
2447 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2448 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2450 priv->filter |= filter;
2452 if (current_folder_needs_filtering (priv))
2453 modest_header_view_refilter (self);
2457 modest_header_view_unset_filter (ModestHeaderView *self,
2458 ModestHeaderViewFilter filter)
2460 ModestHeaderViewPrivate *priv;
2462 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2463 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2465 priv->filter &= ~filter;
2467 if (current_folder_needs_filtering (priv))
2468 modest_header_view_refilter (self);
2472 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2474 if (strcmp ("style", spec->name) == 0) {
2475 update_style (MODEST_HEADER_VIEW (obj));
2476 gtk_widget_queue_draw (GTK_WIDGET (obj));
2481 update_style (ModestHeaderView *self)
2483 ModestHeaderViewPrivate *priv;
2484 GdkColor style_color;
2485 GdkColor style_active_color;
2486 PangoAttrList *attr_list;
2488 PangoAttribute *attr;
2490 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2491 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2495 attr_list = pango_attr_list_new ();
2496 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
2497 gdk_color_parse ("grey", &style_color);
2499 priv->secondary_color = style_color;
2500 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2501 pango_attr_list_insert (attr_list, attr);
2504 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2506 "SmallSystemFont", NULL,
2509 attr = pango_attr_font_desc_new (pango_font_description_copy
2510 (style->font_desc));
2511 pango_attr_list_insert (attr_list, attr);
2513 g_object_set (G_OBJECT (priv->renderer_address),
2514 "foreground-gdk", &(priv->secondary_color),
2515 "foreground-set", TRUE,
2516 "attributes", attr_list,
2518 g_object_set (G_OBJECT (priv->renderer_date_status),
2519 "foreground-gdk", &(priv->secondary_color),
2520 "foreground-set", TRUE,
2521 "attributes", attr_list,
2523 pango_attr_list_unref (attr_list);
2525 g_object_set (G_OBJECT (priv->renderer_address),
2526 "foreground-gdk", &(priv->secondary_color),
2527 "foreground-set", TRUE,
2528 "scale", PANGO_SCALE_SMALL,
2531 g_object_set (G_OBJECT (priv->renderer_date_status),
2532 "foreground-gdk", &(priv->secondary_color),
2533 "foreground-set", TRUE,
2534 "scale", PANGO_SCALE_SMALL,
2539 if (gtk_style_lookup_color (GTK_WIDGET (self)->style, "ActiveTextColor", &style_active_color)) {
2540 priv->active_color = style_active_color;
2541 #ifdef MODEST_TOOLKIT_HILDON2
2542 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2543 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2546 #ifdef MODEST_TOOLKIT_HILDON2
2547 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2553 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2558 GtkTreeModel *tree_model;
2563 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2571 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2572 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2576 gtk_tree_model_get (tree_model, &iter,
2577 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2584 parse_date_side (const gchar *string, time_t *date_side)
2590 gboolean result = FALSE;
2592 if (string && string[0] == '\0') {
2597 casefold = g_utf8_casefold (string, -1);
2598 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2599 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2600 date = g_date_new ();
2602 if (g_utf8_collate (casefold, today) == 0) {
2603 *date_side = time (NULL);
2608 if (g_utf8_collate (casefold, yesterday) == 0) {
2609 *date_side = time (NULL) - 24*60*60;
2614 g_date_set_parse (date, string);
2615 if (g_date_valid (date)) {
2617 g_date_to_struct_tm (date, &tm);
2618 *date_side = mktime (&tm);
2633 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2638 parts = g_strsplit (string, "..", 2);
2641 if (g_strv_length (parts) != 2) {
2648 if (!parse_date_side (parts[0], date_range_start)) {
2653 if (parse_date_side (parts[1], date_range_end)) {
2654 if (*date_range_end == 0) {
2655 *date_range_end = (time_t) -1;
2657 *date_range_end += (24*60*60 - 1);
2670 modest_header_view_set_show_latest (ModestHeaderView *header_view,
2673 ModestHeaderViewPrivate *priv;
2674 GtkTreeModel *sortable, *filter, *model;
2676 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2677 priv->show_latest = show_latest;
2679 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2680 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2681 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2682 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2683 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2685 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (model), priv->show_latest);
2692 modest_header_view_get_show_latest (ModestHeaderView *header_view)
2694 ModestHeaderViewPrivate *priv;
2695 GtkTreeModel *sortable, *filter, *model;
2698 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2700 result = priv->show_latest;
2701 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2702 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2703 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2704 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2705 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2707 result = tny_gtk_header_list_model_get_show_latest (TNY_GTK_HEADER_LIST_MODEL (model));
2716 modest_header_view_get_not_latest (ModestHeaderView *header_view)
2718 ModestHeaderViewPrivate *priv;
2719 gint not_latest = 0;
2720 GtkTreeModel *sortable, *filter, *model;
2722 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2724 if (priv->show_latest == 0)
2727 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2728 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2729 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2730 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2731 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2733 not_latest = MAX (0, tny_list_get_length (TNY_LIST (model)) - priv->show_latest);
2742 modest_header_view_set_filter_string (ModestHeaderView *self,
2743 const gchar *filter_string)
2745 ModestHeaderViewPrivate *priv;
2747 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2748 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2750 if (priv->filter_string)
2751 g_free (priv->filter_string);
2753 priv->filter_string = g_strdup (filter_string);
2754 priv->filter_date_range = FALSE;
2756 if (priv->filter_string_splitted) {
2757 g_strfreev (priv->filter_string_splitted);
2758 priv->filter_string_splitted = NULL;
2761 if (priv->filter_string) {
2762 gchar **split, **current, **current_target;
2764 split = g_strsplit (priv->filter_string, " ", 0);
2766 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2767 current_target = priv->filter_string_splitted;
2768 for (current = split; *current != 0; current ++) {
2769 gboolean has_date_range = FALSE;;
2770 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2771 time_t range_start, range_end;
2772 /* It contains .. but it's not ".." so it may be a date range */
2773 if (parse_date_range (*current, &range_start, &range_end)) {
2774 priv->filter_date_range = TRUE;
2775 has_date_range = TRUE;
2776 priv->date_range_start = range_start;
2777 priv->date_range_end = range_end;
2780 if (!has_date_range) {
2781 *current_target = g_utf8_casefold (*current, -1);
2785 *current_target = '\0';
2788 modest_header_view_refilter (MODEST_HEADER_VIEW (self));