1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
35 #include <tny-error.h>
36 #include <tny-merge-folder.h>
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-main-window.h>
45 #include <modest-ui-actions.h>
46 #include <modest-marshal.h>
47 #include <modest-text-utils.h>
48 #include <modest-icon-names.h>
49 #include <modest-runtime.h>
50 #include "modest-platform.h"
51 #include <modest-hbox-cell-renderer.h>
52 #include <modest-vbox-cell-renderer.h>
53 #include <modest-datetime-formatter.h>
54 #include <modest-ui-constants.h>
55 #ifdef MODEST_TOOLKIT_HILDON2
56 #include <hildon/hildon.h>
59 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
60 static void modest_header_view_init (ModestHeaderView *obj);
61 static void modest_header_view_finalize (GObject *obj);
62 static void modest_header_view_dispose (GObject *obj);
64 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
65 GtkTreeViewColumn *column, gpointer userdata);
67 static gint cmp_rows (GtkTreeModel *tree_model,
72 static gint cmp_subject_rows (GtkTreeModel *tree_model,
77 static gboolean filter_row (GtkTreeModel *model,
81 static void on_account_removed (TnyAccountStore *self,
85 static void on_selection_changed (GtkTreeSelection *sel,
88 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
91 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
94 static void setup_drag_and_drop (GtkWidget *self);
96 static void enable_drag_and_drop (GtkWidget *self);
98 static void disable_drag_and_drop (GtkWidget *self);
100 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
102 #ifndef MODEST_TOOLKIT_HILDON2
103 static gboolean on_focus_in (GtkWidget *sef,
104 GdkEventFocus *event,
107 static gboolean on_focus_out (GtkWidget *self,
108 GdkEventFocus *event,
112 static void folder_monitor_update (TnyFolderObserver *self,
113 TnyFolderChange *change);
115 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
117 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
119 static void _clear_hidding_filter (ModestHeaderView *header_view);
121 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
123 const gchar *tny_folder_id);
125 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
126 GdkEventExpose *event,
129 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
130 static void update_style (ModestHeaderView *self);
133 HEADER_VIEW_NON_EMPTY,
138 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
139 struct _ModestHeaderViewPrivate {
141 ModestHeaderViewStyle style;
144 TnyFolderMonitor *monitor;
145 GMutex *observers_lock;
147 /*header-view-observer observer*/
148 GMutex *observer_list_lock;
149 GSList *observer_list;
151 /* not unref this object, its a singlenton */
152 ModestEmailClipboard *clipboard;
154 /* Filter tree model */
157 GtkTreeRowReference *autoselect_reference;
158 ModestHeaderViewFilter filter;
159 #ifdef MODEST_TOOLKIT_HILDON2
160 GtkWidget *live_search;
163 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
164 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
166 gulong selection_changed_handler;
167 gulong acc_removed_handler;
169 GList *drag_begin_cached_selected_rows;
171 HeaderViewStatus status;
172 guint status_timeout;
173 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
175 ModestDatetimeFormatter *datetime_formatter;
177 GtkCellRenderer *renderer_subject;
178 GtkCellRenderer *renderer_address;
179 GtkCellRenderer *renderer_date_status;
181 GdkColor active_color;
182 GdkColor secondary_color;
186 gchar *filter_string;
187 gchar **filter_string_splitted;
188 gboolean filter_date_range;
189 time_t date_range_start;
190 time_t date_range_end;
193 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
194 struct _HeadersCountChangedHelper {
195 ModestHeaderView *self;
196 TnyFolderChange *change;
200 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
201 MODEST_TYPE_HEADER_VIEW, \
202 ModestHeaderViewPrivate))
206 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
209 HEADER_SELECTED_SIGNAL,
210 HEADER_ACTIVATED_SIGNAL,
211 ITEM_NOT_FOUND_SIGNAL,
212 MSG_COUNT_CHANGED_SIGNAL,
213 UPDATING_MSG_LIST_SIGNAL,
218 static GObjectClass *parent_class = NULL;
220 /* uncomment the following if you have defined any signals */
221 static guint signals[LAST_SIGNAL] = {0};
224 modest_header_view_get_type (void)
226 static GType my_type = 0;
228 static const GTypeInfo my_info = {
229 sizeof(ModestHeaderViewClass),
230 NULL, /* base init */
231 NULL, /* base finalize */
232 (GClassInitFunc) modest_header_view_class_init,
233 NULL, /* class finalize */
234 NULL, /* class data */
235 sizeof(ModestHeaderView),
237 (GInstanceInitFunc) modest_header_view_init,
241 static const GInterfaceInfo tny_folder_observer_info =
243 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
244 NULL, /* interface_finalize */
245 NULL /* interface_data */
247 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
251 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
252 &tny_folder_observer_info);
260 modest_header_view_class_init (ModestHeaderViewClass *klass)
262 GObjectClass *gobject_class;
263 gobject_class = (GObjectClass*) klass;
265 parent_class = g_type_class_peek_parent (klass);
266 gobject_class->finalize = modest_header_view_finalize;
267 gobject_class->dispose = modest_header_view_dispose;
269 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
271 signals[HEADER_SELECTED_SIGNAL] =
272 g_signal_new ("header_selected",
273 G_TYPE_FROM_CLASS (gobject_class),
275 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
277 g_cclosure_marshal_VOID__POINTER,
278 G_TYPE_NONE, 1, G_TYPE_POINTER);
280 signals[HEADER_ACTIVATED_SIGNAL] =
281 g_signal_new ("header_activated",
282 G_TYPE_FROM_CLASS (gobject_class),
284 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
286 gtk_marshal_VOID__POINTER_POINTER,
287 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
290 signals[ITEM_NOT_FOUND_SIGNAL] =
291 g_signal_new ("item_not_found",
292 G_TYPE_FROM_CLASS (gobject_class),
294 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
296 g_cclosure_marshal_VOID__INT,
297 G_TYPE_NONE, 1, G_TYPE_INT);
299 signals[MSG_COUNT_CHANGED_SIGNAL] =
300 g_signal_new ("msg_count_changed",
301 G_TYPE_FROM_CLASS (gobject_class),
303 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
305 modest_marshal_VOID__POINTER_POINTER,
306 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
308 signals[UPDATING_MSG_LIST_SIGNAL] =
309 g_signal_new ("updating-msg-list",
310 G_TYPE_FROM_CLASS (gobject_class),
312 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
314 g_cclosure_marshal_VOID__BOOLEAN,
315 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
317 #ifdef MODEST_TOOLKIT_HILDON2
318 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
324 tny_folder_observer_init (TnyFolderObserverIface *klass)
326 klass->update = folder_monitor_update;
329 static GtkTreeViewColumn*
330 get_new_column (const gchar *name, GtkCellRenderer *renderer,
331 gboolean resizable, gint sort_col_id, gboolean show_as_text,
332 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
334 GtkTreeViewColumn *column;
336 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
337 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
339 gtk_tree_view_column_set_resizable (column, resizable);
341 gtk_tree_view_column_set_expand (column, TRUE);
344 gtk_tree_view_column_add_attribute (column, renderer, "text",
346 if (sort_col_id >= 0)
347 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
349 gtk_tree_view_column_set_sort_indicator (column, FALSE);
350 gtk_tree_view_column_set_reorderable (column, TRUE);
353 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
360 remove_all_columns (ModestHeaderView *obj)
362 GList *columns, *cursor;
364 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
366 for (cursor = columns; cursor; cursor = cursor->next)
367 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
368 GTK_TREE_VIEW_COLUMN(cursor->data));
369 g_list_free (columns);
373 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
375 GtkTreeModel *sortable;
376 GtkTreeViewColumn *column=NULL;
377 GtkTreeSelection *selection = NULL;
378 GtkCellRenderer *renderer_header,
379 *renderer_attach, *renderer_compact_date_or_status;
380 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
381 *renderer_subject_box, *renderer_recpt,
383 ModestHeaderViewPrivate *priv;
384 GtkTreeViewColumn *compact_column = NULL;
387 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
388 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
390 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
392 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
394 /* TODO: check whether these renderers need to be freed */
395 renderer_attach = gtk_cell_renderer_pixbuf_new ();
396 renderer_priority = gtk_cell_renderer_pixbuf_new ();
397 renderer_header = gtk_cell_renderer_text_new ();
399 renderer_compact_header = modest_vbox_cell_renderer_new ();
400 renderer_recpt_box = modest_hbox_cell_renderer_new ();
401 renderer_subject_box = modest_hbox_cell_renderer_new ();
402 renderer_recpt = gtk_cell_renderer_text_new ();
403 priv->renderer_address = renderer_recpt;
404 priv->renderer_subject = gtk_cell_renderer_text_new ();
405 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
406 priv->renderer_date_status = renderer_compact_date_or_status;
408 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
409 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
410 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
411 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
412 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
413 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
414 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
415 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
416 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
417 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
418 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
419 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
420 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
421 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
423 #ifdef MODEST_TOOLKIT_HILDON2
424 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
426 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
427 #ifndef MODEST_TOOLKIT_GTK
428 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
429 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
431 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
432 g_object_set(G_OBJECT(renderer_header),
433 "ellipsize", PANGO_ELLIPSIZE_END,
435 g_object_set (G_OBJECT (priv->renderer_subject),
436 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
438 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
439 g_object_set (G_OBJECT (renderer_recpt),
440 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
442 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
443 g_object_set(G_OBJECT(renderer_compact_date_or_status),
444 "xalign", 1.0, "yalign", 0.1,
446 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
447 #ifdef MODEST_TOOLKIT_HILDON2
448 g_object_set (G_OBJECT (renderer_priority),
450 "xalign", 0.0, NULL);
451 g_object_set (G_OBJECT (renderer_attach),
453 "xalign", 0.0, NULL);
455 g_object_set (G_OBJECT (renderer_priority),
456 "yalign", 0.5, NULL);
457 g_object_set (G_OBJECT (renderer_attach),
458 "yalign", 0.0, NULL);
461 #ifdef MODEST_TOOLKIT_HILDON1
462 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
463 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
464 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
465 #elif MODEST_TOOLKIT_HILDON2
466 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
467 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
468 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
470 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
471 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
472 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
475 remove_all_columns (self);
477 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
478 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
479 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
481 /* Add new columns */
482 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
483 ModestHeaderViewColumn col =
484 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
486 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
487 g_printerr ("modest: invalid column %d in column list\n", col);
493 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
494 column = get_new_column (_("A"), renderer_attach, FALSE,
495 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
497 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
499 gtk_tree_view_column_set_fixed_width (column, 45);
503 case MODEST_HEADER_VIEW_COLUMN_FROM:
504 column = get_new_column (_("From"), renderer_header, TRUE,
505 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
507 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
508 GINT_TO_POINTER(TRUE));
511 case MODEST_HEADER_VIEW_COLUMN_TO:
512 column = get_new_column (_("To"), renderer_header, TRUE,
513 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
515 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
516 GINT_TO_POINTER(FALSE));
519 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
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(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
525 compact_column = column;
528 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
529 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
530 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
532 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
533 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
534 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
535 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
536 compact_column = column;
540 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
541 column = get_new_column (_("Subject"), renderer_header, TRUE,
542 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
544 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
548 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
549 column = get_new_column (_("Received"), renderer_header, TRUE,
550 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
552 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
553 GINT_TO_POINTER(TRUE));
556 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
557 column = get_new_column (_("Sent"), renderer_header, TRUE,
558 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
560 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
561 GINT_TO_POINTER(FALSE));
564 case MODEST_HEADER_VIEW_COLUMN_SIZE:
565 column = get_new_column (_("Size"), renderer_header, TRUE,
566 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
568 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
571 case MODEST_HEADER_VIEW_COLUMN_STATUS:
572 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
573 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
575 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
580 g_return_val_if_reached(FALSE);
583 /* we keep the column id around */
584 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
585 GINT_TO_POINTER(col));
587 /* we need this ptr when sorting the rows */
588 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
590 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
594 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
595 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
596 (GtkTreeIterCompareFunc) cmp_rows,
597 compact_column, NULL);
598 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
599 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
600 (GtkTreeIterCompareFunc) cmp_subject_rows,
601 compact_column, NULL);
605 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
611 datetime_format_changed (ModestDatetimeFormatter *formatter,
612 ModestHeaderView *self)
614 gtk_widget_queue_draw (GTK_WIDGET (self));
618 modest_header_view_init (ModestHeaderView *obj)
620 ModestHeaderViewPrivate *priv;
623 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
625 priv->show_latest = 0;
628 priv->is_outbox = FALSE;
630 priv->monitor = NULL;
631 priv->observers_lock = g_mutex_new ();
632 priv->autoselect_reference = NULL;
634 priv->status = HEADER_VIEW_INIT;
635 priv->status_timeout = 0;
636 priv->notify_status = TRUE;
638 priv->observer_list_lock = g_mutex_new();
639 priv->observer_list = NULL;
641 priv->clipboard = modest_runtime_get_email_clipboard ();
642 priv->hidding_ids = NULL;
643 priv->n_selected = 0;
644 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
645 #ifdef MODEST_TOOLKIT_HILDON2
646 priv->live_search = NULL;
648 priv->filter_string = NULL;
649 priv->filter_string_splitted = NULL;
650 priv->filter_date_range = FALSE;
651 priv->selection_changed_handler = 0;
652 priv->acc_removed_handler = 0;
654 /* Sort parameters */
655 for (j=0; j < 2; j++) {
656 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
657 priv->sort_colid[j][i] = -1;
658 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
662 priv->datetime_formatter = modest_datetime_formatter_new ();
663 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
664 G_CALLBACK (datetime_format_changed), (gpointer) obj);
666 setup_drag_and_drop (GTK_WIDGET(obj));
670 modest_header_view_dispose (GObject *obj)
672 ModestHeaderView *self;
673 ModestHeaderViewPrivate *priv;
674 GtkTreeSelection *sel;
676 self = MODEST_HEADER_VIEW(obj);
677 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
679 if (priv->datetime_formatter) {
680 g_object_unref (priv->datetime_formatter);
681 priv->datetime_formatter = NULL;
684 /* Free in the dispose to avoid unref cycles */
686 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
687 g_object_unref (G_OBJECT (priv->folder));
691 /* We need to do this here in the dispose because the
692 selection won't exist when finalizing */
693 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
694 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
695 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
696 priv->selection_changed_handler = 0;
699 G_OBJECT_CLASS(parent_class)->dispose (obj);
703 modest_header_view_finalize (GObject *obj)
705 ModestHeaderView *self;
706 ModestHeaderViewPrivate *priv;
708 self = MODEST_HEADER_VIEW(obj);
709 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
711 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
712 priv->acc_removed_handler)) {
713 g_signal_handler_disconnect (modest_runtime_get_account_store (),
714 priv->acc_removed_handler);
717 /* There is no need to lock because there should not be any
718 * reference to self now. */
719 g_mutex_free(priv->observer_list_lock);
720 g_slist_free(priv->observer_list);
722 g_mutex_lock (priv->observers_lock);
724 tny_folder_monitor_stop (priv->monitor);
725 g_object_unref (G_OBJECT (priv->monitor));
727 g_mutex_unlock (priv->observers_lock);
728 g_mutex_free (priv->observers_lock);
730 /* Clear hidding array created by cut operation */
731 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
733 if (priv->autoselect_reference != NULL) {
734 gtk_tree_row_reference_free (priv->autoselect_reference);
735 priv->autoselect_reference = NULL;
738 if (priv->filter_string) {
739 g_free (priv->filter_string);
742 if (priv->filter_string_splitted) {
743 g_strfreev (priv->filter_string_splitted);
746 G_OBJECT_CLASS(parent_class)->finalize (obj);
751 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
754 GtkTreeSelection *sel;
755 ModestHeaderView *self;
756 ModestHeaderViewPrivate *priv;
758 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
761 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
762 self = MODEST_HEADER_VIEW(obj);
763 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
765 modest_header_view_set_style (self, style);
767 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
768 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
769 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
771 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
772 TRUE); /* alternating row colors */
774 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
775 priv->selection_changed_handler =
776 g_signal_connect_after (sel, "changed",
777 G_CALLBACK(on_selection_changed), self);
779 g_signal_connect (self, "row-activated",
780 G_CALLBACK (on_header_row_activated), NULL);
782 #ifndef MODEST_TOOLKIT_HILDON2
783 g_signal_connect (self, "focus-in-event",
784 G_CALLBACK(on_focus_in), NULL);
785 g_signal_connect (self, "focus-out-event",
786 G_CALLBACK(on_focus_out), NULL);
789 g_signal_connect (self, "button-press-event",
790 G_CALLBACK(on_button_press_event), NULL);
791 g_signal_connect (self, "button-release-event",
792 G_CALLBACK(on_button_release_event), NULL);
794 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
796 G_CALLBACK (on_account_removed),
799 g_signal_connect (self, "expose-event",
800 G_CALLBACK(modest_header_view_on_expose_event),
803 return GTK_WIDGET(self);
808 modest_header_view_count_selected_headers (ModestHeaderView *self)
810 GtkTreeSelection *sel;
813 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
815 /* Get selection object and check selected rows count */
816 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
817 selected_rows = gtk_tree_selection_count_selected_rows (sel);
819 return selected_rows;
823 modest_header_view_has_selected_headers (ModestHeaderView *self)
825 GtkTreeSelection *sel;
828 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
830 /* Get selection object and check selected rows count */
831 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
832 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
839 modest_header_view_get_selected_headers (ModestHeaderView *self)
841 GtkTreeSelection *sel;
842 TnyList *header_list = NULL;
844 GList *list, *tmp = NULL;
845 GtkTreeModel *tree_model = NULL;
848 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
851 /* Get selected rows */
852 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
853 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
856 header_list = tny_simple_list_new();
858 list = g_list_reverse (list);
861 /* get header from selection */
862 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
863 gtk_tree_model_get (tree_model, &iter,
864 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
866 /* Prepend to list */
867 tny_list_prepend (header_list, G_OBJECT (header));
868 g_object_unref (G_OBJECT (header));
870 tmp = g_list_next (tmp);
873 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
880 /* scroll our list view so the selected item is visible */
882 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
884 #ifdef MODEST_TOOLKIT_GTK
886 GtkTreePath *selected_path;
887 GtkTreePath *start, *end;
891 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
892 selected_path = gtk_tree_model_get_path (model, iter);
894 start = gtk_tree_path_new ();
895 end = gtk_tree_path_new ();
897 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
899 if (gtk_tree_path_compare (selected_path, start) < 0 ||
900 gtk_tree_path_compare (end, selected_path) < 0)
901 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
902 selected_path, NULL, TRUE,
905 gtk_tree_path_free (selected_path);
906 gtk_tree_path_free (start);
907 gtk_tree_path_free (end);
909 #endif /* MODEST_TOOLKIT_GTK */
914 modest_header_view_select_next (ModestHeaderView *self)
916 GtkTreeSelection *sel;
921 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
923 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
924 path = get_selected_row (GTK_TREE_VIEW(self), &model);
925 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
926 /* Unselect previous path */
927 gtk_tree_selection_unselect_path (sel, path);
929 /* Move path down and selects new one */
930 if (gtk_tree_model_iter_next (model, &iter)) {
931 gtk_tree_selection_select_iter (sel, &iter);
932 scroll_to_selected (self, &iter, FALSE);
934 gtk_tree_path_free(path);
940 modest_header_view_select_prev (ModestHeaderView *self)
942 GtkTreeSelection *sel;
947 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
949 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
950 path = get_selected_row (GTK_TREE_VIEW(self), &model);
951 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
952 /* Unselect previous path */
953 gtk_tree_selection_unselect_path (sel, path);
956 if (gtk_tree_path_prev (path)) {
957 gtk_tree_model_get_iter (model, &iter, path);
959 /* Select the new one */
960 gtk_tree_selection_select_iter (sel, &iter);
961 scroll_to_selected (self, &iter, TRUE);
964 gtk_tree_path_free (path);
969 modest_header_view_get_columns (ModestHeaderView *self)
971 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
973 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
979 modest_header_view_set_style (ModestHeaderView *self,
980 ModestHeaderViewStyle style)
982 ModestHeaderViewPrivate *priv;
983 gboolean show_col_headers = FALSE;
984 ModestHeaderViewStyle old_style;
986 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
987 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
990 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
991 if (priv->style == style)
992 return TRUE; /* nothing to do */
995 case MODEST_HEADER_VIEW_STYLE_DETAILS:
996 show_col_headers = TRUE;
998 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1001 g_return_val_if_reached (FALSE);
1003 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1004 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1006 old_style = priv->style;
1007 priv->style = style;
1013 ModestHeaderViewStyle
1014 modest_header_view_get_style (ModestHeaderView *self)
1016 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1018 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1021 /* This is used to automatically select the first header if the user
1022 * has not selected any header yet.
1025 modest_header_view_on_expose_event(GtkTreeView *header_view,
1026 GdkEventExpose *event,
1029 GtkTreeSelection *sel;
1030 GtkTreeModel *model;
1031 GtkTreeIter tree_iter;
1032 ModestHeaderViewPrivate *priv;
1034 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1035 model = gtk_tree_view_get_model(header_view);
1040 #ifdef MODEST_TOOLKIT_HILDON2
1043 sel = gtk_tree_view_get_selection(header_view);
1044 if(!gtk_tree_selection_count_selected_rows(sel)) {
1045 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1046 GtkTreePath *tree_iter_path;
1047 /* Prevent the widget from getting the focus
1048 when selecting the first item */
1049 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1050 g_object_set(header_view, "can-focus", FALSE, NULL);
1051 gtk_tree_selection_select_iter(sel, &tree_iter);
1052 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1053 g_object_set(header_view, "can-focus", TRUE, NULL);
1054 if (priv->autoselect_reference) {
1055 gtk_tree_row_reference_free (priv->autoselect_reference);
1057 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1058 gtk_tree_path_free (tree_iter_path);
1061 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1062 gboolean moved_selection = FALSE;
1063 GtkTreePath * last_path;
1064 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1065 moved_selection = TRUE;
1069 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1070 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1071 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1072 moved_selection = TRUE;
1073 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1075 gtk_tree_path_free (last_path);
1077 if (moved_selection) {
1078 gtk_tree_row_reference_free (priv->autoselect_reference);
1079 priv->autoselect_reference = NULL;
1082 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1083 GtkTreePath *current_path;
1084 current_path = gtk_tree_model_get_path (model, &tree_iter);
1085 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1086 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1087 g_object_set(header_view, "can-focus", FALSE, NULL);
1088 gtk_tree_selection_unselect_all (sel);
1089 gtk_tree_selection_select_iter(sel, &tree_iter);
1090 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1091 g_object_set(header_view, "can-focus", TRUE, NULL);
1092 gtk_tree_row_reference_free (priv->autoselect_reference);
1093 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1095 gtk_tree_path_free (current_path);
1096 gtk_tree_path_free (last_path);
1106 modest_header_view_get_folder (ModestHeaderView *self)
1108 ModestHeaderViewPrivate *priv;
1110 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1112 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1115 g_object_ref (priv->folder);
1117 return priv->folder;
1121 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1127 ModestHeaderView *self;
1128 ModestHeaderViewPrivate *priv;
1130 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1132 self = MODEST_HEADER_VIEW (user_data);
1133 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1135 if (cancelled || err)
1138 /* Add IDLE observer (monitor) and another folder observer for
1139 new messages (self) */
1140 g_mutex_lock (priv->observers_lock);
1141 if (priv->monitor) {
1142 tny_folder_monitor_stop (priv->monitor);
1143 g_object_unref (G_OBJECT (priv->monitor));
1145 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1146 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1147 tny_folder_monitor_start (priv->monitor);
1148 g_mutex_unlock (priv->observers_lock);
1152 modest_header_view_set_folder_intern (ModestHeaderView *self,
1158 ModestHeaderViewPrivate *priv;
1159 GList *cols, *cursor;
1160 GtkTreeModel *filter_model, *sortable;
1162 GtkSortType sort_type;
1164 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1166 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1167 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (headers), priv->show_latest);
1169 /* Start the monitor in the callback of the
1170 tny_gtk_header_list_model_set_folder call. It's crucial to
1171 do it there and not just after the call because we want the
1172 monitor to observe only the headers returned by the
1173 tny_folder_get_headers_async call that it's inside the
1174 tny_gtk_header_list_model_set_folder call. This way the
1175 monitor infrastructure could successfully cope with
1176 duplicates. For example if a tny_folder_add_msg_async is
1177 happening while tny_gtk_header_list_model_set_folder is
1178 invoked, then the first call could add a header that will
1179 be added again by tny_gtk_header_list_model_set_folder, so
1180 we'd end up with duplicate headers. sergio */
1181 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1183 set_folder_intern_get_headers_async_cb,
1186 /* Create a tree model filter to hide and show rows for cut operations */
1187 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (headers), NULL);
1188 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1189 filter_row, self, NULL);
1190 g_object_unref (headers);
1192 /* Init filter_row function to examine empty status */
1193 priv->status = HEADER_VIEW_INIT;
1195 /* Create sortable model */
1196 sortable = gtk_tree_model_sort_new_with_model (filter_model);
1197 g_object_unref (filter_model);
1199 /* install our special sorting functions */
1200 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1202 /* Restore sort column id */
1204 type = modest_tny_folder_guess_folder_type (folder);
1205 if (type == TNY_FOLDER_TYPE_INVALID)
1206 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1208 sort_colid = modest_header_view_get_sort_column_id (self, type);
1209 sort_type = modest_header_view_get_sort_type (self, type);
1210 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1213 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1214 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1215 (GtkTreeIterCompareFunc) cmp_rows,
1217 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1218 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1219 (GtkTreeIterCompareFunc) cmp_subject_rows,
1224 gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable);
1225 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1226 g_object_unref (sortable);
1233 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1235 GtkSortType sort_type)
1237 ModestHeaderViewPrivate *priv = NULL;
1238 GtkTreeModel *sortable = NULL;
1241 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1242 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1244 /* Get model and private data */
1245 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1246 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1248 /* Sort tree model */
1249 type = modest_tny_folder_guess_folder_type (priv->folder);
1250 if (type == TNY_FOLDER_TYPE_INVALID)
1251 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1253 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1256 /* Store new sort parameters */
1257 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1262 modest_header_view_set_sort_params (ModestHeaderView *self,
1264 GtkSortType sort_type,
1267 ModestHeaderViewPrivate *priv;
1268 ModestHeaderViewStyle style;
1270 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1271 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1272 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1274 style = modest_header_view_get_style (self);
1275 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1277 priv->sort_colid[style][type] = sort_colid;
1278 priv->sort_type[style][type] = sort_type;
1282 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1285 ModestHeaderViewPrivate *priv;
1286 ModestHeaderViewStyle style;
1288 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1289 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1291 style = modest_header_view_get_style (self);
1292 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1294 return priv->sort_colid[style][type];
1298 modest_header_view_get_sort_type (ModestHeaderView *self,
1301 ModestHeaderViewPrivate *priv;
1302 ModestHeaderViewStyle style;
1304 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1305 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1307 style = modest_header_view_get_style (self);
1308 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1310 return priv->sort_type[style][type];
1314 ModestHeaderView *header_view;
1315 RefreshAsyncUserCallback cb;
1320 folder_refreshed_cb (ModestMailOperation *mail_op,
1324 ModestHeaderViewPrivate *priv;
1325 SetFolderHelper *info;
1327 info = (SetFolderHelper*) user_data;
1329 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1333 info->cb (mail_op, folder, info->user_data);
1335 /* Start the folder count changes observer. We do not need it
1336 before the refresh. Note that the monitor could still be
1337 called for this refresh but now we know that the callback
1338 was previously called */
1339 g_mutex_lock (priv->observers_lock);
1340 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1341 g_mutex_unlock (priv->observers_lock);
1343 /* Notify the observers that the update is over */
1344 g_signal_emit (G_OBJECT (info->header_view),
1345 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1347 /* Allow filtering notifications from now on if the current
1348 folder is still the same (if not then the user has selected
1349 another one to refresh, we should wait until that refresh
1351 if (priv->folder == folder)
1352 priv->notify_status = TRUE;
1355 g_object_unref (info->header_view);
1360 refresh_folder_error_handler (ModestMailOperation *mail_op,
1363 const GError *error = modest_mail_operation_get_error (mail_op);
1365 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1366 error->code == TNY_IO_ERROR_WRITE ||
1367 error->code == TNY_IO_ERROR_READ) {
1368 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1369 /* If the mail op has been cancelled then it's not an error: don't show any message */
1370 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1371 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1372 modest_platform_information_banner (NULL, NULL, msg);
1379 modest_header_view_set_folder (ModestHeaderView *self,
1382 ModestWindow *progress_window,
1383 RefreshAsyncUserCallback callback,
1386 ModestHeaderViewPrivate *priv;
1388 g_return_if_fail (self);
1390 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1393 if (priv->status_timeout) {
1394 g_source_remove (priv->status_timeout);
1395 priv->status_timeout = 0;
1398 g_mutex_lock (priv->observers_lock);
1399 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1400 g_object_unref (priv->folder);
1401 priv->folder = NULL;
1402 g_mutex_unlock (priv->observers_lock);
1406 GtkTreeSelection *selection;
1407 SetFolderHelper *info;
1408 ModestMailOperation *mail_op = NULL;
1410 /* Set folder in the model */
1411 modest_header_view_set_folder_intern (self, folder, refresh);
1413 /* Pick my reference. Nothing to do with the mail operation */
1414 priv->folder = g_object_ref (folder);
1416 /* Do not notify about filterings until the refresh finishes */
1417 priv->notify_status = FALSE;
1419 /* Clear the selection if exists */
1420 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1421 gtk_tree_selection_unselect_all(selection);
1422 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1424 /* Notify the observers that the update begins */
1425 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1428 /* create the helper */
1429 info = g_malloc0 (sizeof (SetFolderHelper));
1430 info->header_view = g_object_ref (self);
1431 info->cb = callback;
1432 info->user_data = user_data;
1434 /* Create the mail operation (source will be the parent widget) */
1435 if (progress_window)
1436 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1437 refresh_folder_error_handler,
1440 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1443 /* Refresh the folder asynchronously */
1444 modest_mail_operation_refresh_folder (mail_op,
1446 folder_refreshed_cb,
1449 folder_refreshed_cb (mail_op, folder, info);
1453 g_object_unref (mail_op);
1455 g_mutex_lock (priv->observers_lock);
1457 if (priv->monitor) {
1458 tny_folder_monitor_stop (priv->monitor);
1459 g_object_unref (G_OBJECT (priv->monitor));
1460 priv->monitor = NULL;
1463 if (priv->autoselect_reference) {
1464 gtk_tree_row_reference_free (priv->autoselect_reference);
1465 priv->autoselect_reference = NULL;
1468 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1470 modest_header_view_notify_observers(self, NULL, NULL);
1472 g_mutex_unlock (priv->observers_lock);
1474 /* Notify the observers that the update is over */
1475 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1481 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1482 GtkTreeViewColumn *column, gpointer userdata)
1484 ModestHeaderView *self = NULL;
1486 GtkTreeModel *model = NULL;
1487 TnyHeader *header = NULL;
1488 TnyHeaderFlags flags;
1490 self = MODEST_HEADER_VIEW (treeview);
1492 model = gtk_tree_view_get_model (treeview);
1493 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1496 /* get the first selected item */
1497 gtk_tree_model_get (model, &iter,
1498 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1499 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1502 /* Dont open DELETED messages */
1503 if (flags & TNY_HEADER_FLAG_DELETED) {
1506 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1507 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1508 modest_platform_information_banner (NULL, NULL, msg);
1514 g_signal_emit (G_OBJECT(self),
1515 signals[HEADER_ACTIVATED_SIGNAL],
1521 g_object_unref (G_OBJECT (header));
1526 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1528 GtkTreeModel *model;
1529 TnyHeader *header = NULL;
1530 GtkTreePath *path = NULL;
1532 ModestHeaderView *self;
1533 GList *selected = NULL;
1535 g_return_if_fail (sel);
1536 g_return_if_fail (user_data);
1538 self = MODEST_HEADER_VIEW (user_data);
1540 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1541 if (selected != NULL)
1542 path = (GtkTreePath *) selected->data;
1543 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1544 return; /* msg was _un_selected */
1546 gtk_tree_model_get (model, &iter,
1547 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1551 g_signal_emit (G_OBJECT(self),
1552 signals[HEADER_SELECTED_SIGNAL],
1555 g_object_unref (G_OBJECT (header));
1557 /* free all items in 'selected' */
1558 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1559 g_list_free (selected);
1563 /* PROTECTED method. It's useful when we want to force a given
1564 selection to reload a msg. For example if we have selected a header
1565 in offline mode, when Modest become online, we want to reload the
1566 message automatically without an user click over the header */
1568 _modest_header_view_change_selection (GtkTreeSelection *selection,
1571 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1572 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1574 on_selection_changed (selection, user_data);
1578 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1585 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1589 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1593 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1601 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1609 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1610 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1614 case TNY_HEADER_FLAG_ATTACHMENTS:
1616 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1617 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1618 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1619 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1621 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1622 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1624 return cmp ? cmp : t1 - t2;
1626 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1627 TnyHeader *header1 = NULL, *header2 = NULL;
1629 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1630 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1631 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1632 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1634 /* This is for making priority values respect the intuitive sort relationship
1635 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1637 if (header1 && header2) {
1638 cmp = compare_priorities (tny_header_get_priority (header1),
1639 tny_header_get_priority (header2));
1640 g_object_unref (header1);
1641 g_object_unref (header2);
1643 return cmp ? cmp : t1 - t2;
1649 return &iter1 - &iter2; /* oughhhh */
1654 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1661 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1663 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1664 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1665 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1666 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1668 /* Do not use the prefixes for sorting. Consume all the blank
1669 spaces for sorting */
1670 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1671 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1674 /* If they're equal based on subject without prefix then just
1675 sort them by length. This will show messages like this.
1682 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1689 /* Drag and drop stuff */
1691 drag_data_get_cb (GtkWidget *widget,
1692 GdkDragContext *context,
1693 GtkSelectionData *selection_data,
1698 ModestHeaderView *self = NULL;
1699 ModestHeaderViewPrivate *priv = NULL;
1701 self = MODEST_HEADER_VIEW (widget);
1702 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1704 /* Set the data. Do not use the current selection because it
1705 could be different than the selection at the beginning of
1707 modest_dnd_selection_data_set_paths (selection_data,
1708 priv->drag_begin_cached_selected_rows);
1712 * We're caching the selected rows at the beginning because the
1713 * selection could change between drag-begin and drag-data-get, for
1714 * example if we have a set of rows already selected, and then we
1715 * click in one of them (without SHIFT key pressed) and begin a drag,
1716 * the selection at that moment contains all the selected lines, but
1717 * after dropping the selection, the release event provokes that only
1718 * the row used to begin the drag is selected, so at the end the
1719 * drag&drop affects only one rows instead of all the selected ones.
1723 drag_begin_cb (GtkWidget *widget,
1724 GdkDragContext *context,
1727 ModestHeaderView *self = NULL;
1728 ModestHeaderViewPrivate *priv = NULL;
1729 GtkTreeSelection *selection;
1731 self = MODEST_HEADER_VIEW (widget);
1732 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1734 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1735 priv->drag_begin_cached_selected_rows =
1736 gtk_tree_selection_get_selected_rows (selection, NULL);
1740 * We use the drag-end signal to clear the cached selection, we use
1741 * this because this allways happens, whether or not the d&d was a
1745 drag_end_cb (GtkWidget *widget,
1749 ModestHeaderView *self = NULL;
1750 ModestHeaderViewPrivate *priv = NULL;
1752 self = MODEST_HEADER_VIEW (widget);
1753 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1755 /* Free cached data */
1756 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1757 g_list_free (priv->drag_begin_cached_selected_rows);
1758 priv->drag_begin_cached_selected_rows = NULL;
1761 /* Header view drag types */
1762 const GtkTargetEntry header_view_drag_types[] = {
1763 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1767 enable_drag_and_drop (GtkWidget *self)
1769 #ifdef MODEST_TOOLKIT_HILDON2
1772 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1773 header_view_drag_types,
1774 G_N_ELEMENTS (header_view_drag_types),
1775 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1779 disable_drag_and_drop (GtkWidget *self)
1781 #ifdef MODEST_TOOLKIT_HILDON2
1784 gtk_drag_source_unset (self);
1788 setup_drag_and_drop (GtkWidget *self)
1790 #ifdef MODEST_TOOLKIT_HILDON2
1793 enable_drag_and_drop(self);
1794 g_signal_connect(G_OBJECT (self), "drag_data_get",
1795 G_CALLBACK(drag_data_get_cb), NULL);
1797 g_signal_connect(G_OBJECT (self), "drag_begin",
1798 G_CALLBACK(drag_begin_cb), NULL);
1800 g_signal_connect(G_OBJECT (self), "drag_end",
1801 G_CALLBACK(drag_end_cb), NULL);
1804 static GtkTreePath *
1805 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1807 GtkTreePath *path = NULL;
1808 GtkTreeSelection *sel = NULL;
1811 sel = gtk_tree_view_get_selection(self);
1812 rows = gtk_tree_selection_get_selected_rows (sel, model);
1814 if ((rows == NULL) || (g_list_length(rows) != 1))
1817 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1822 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1828 #ifndef MODEST_TOOLKIT_HILDON2
1830 * This function moves the tree view scroll to the current selected
1831 * row when the widget grabs the focus
1834 on_focus_in (GtkWidget *self,
1835 GdkEventFocus *event,
1838 GtkTreeSelection *selection;
1839 GtkTreeModel *model;
1840 GList *selected = NULL;
1841 GtkTreePath *selected_path = NULL;
1843 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1847 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1848 /* If none selected yet, pick the first one */
1849 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1853 /* Return if the model is empty */
1854 if (!gtk_tree_model_get_iter_first (model, &iter))
1857 path = gtk_tree_model_get_path (model, &iter);
1858 gtk_tree_selection_select_path (selection, path);
1859 gtk_tree_path_free (path);
1862 /* Need to get the all the rows because is selection multiple */
1863 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1864 if (selected == NULL) return FALSE;
1865 selected_path = (GtkTreePath *) selected->data;
1868 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1869 g_list_free (selected);
1875 on_focus_out (GtkWidget *self,
1876 GdkEventFocus *event,
1880 if (!gtk_widget_is_focus (self)) {
1881 GtkTreeSelection *selection = NULL;
1882 GList *selected_rows = NULL;
1883 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1884 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1885 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1886 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1887 gtk_tree_selection_unselect_all (selection);
1888 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1889 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1890 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1891 g_list_free (selected_rows);
1899 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1901 enable_drag_and_drop(self);
1906 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1908 GtkTreeSelection *selection = NULL;
1909 GtkTreePath *path = NULL;
1910 gboolean already_selected = FALSE, already_opened = FALSE;
1911 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1913 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1915 GtkTreeModel *model;
1917 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1918 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1920 /* Get header from model */
1921 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1922 if (gtk_tree_model_get_iter (model, &iter, path)) {
1923 GValue value = {0,};
1926 gtk_tree_model_get_value (model, &iter,
1927 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1929 header = (TnyHeader *) g_value_get_object (&value);
1930 if (TNY_IS_HEADER (header)) {
1931 status = modest_tny_all_send_queues_get_msg_status (header);
1932 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1935 g_value_unset (&value);
1939 /* Enable drag and drop only if the user clicks on a row that
1940 it's already selected. If not, let him select items using
1941 the pointer. If the message is in an OUTBOX and in sending
1942 status disable drag and drop as well */
1943 if (!already_selected ||
1944 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1946 disable_drag_and_drop(self);
1949 gtk_tree_path_free(path);
1951 /* If it's already opened then do not let the button-press
1952 event go on because it'll perform a message open because
1953 we're clicking on to an already selected header */
1958 folder_monitor_update (TnyFolderObserver *self,
1959 TnyFolderChange *change)
1961 ModestHeaderViewPrivate *priv = NULL;
1962 TnyFolderChangeChanged changed;
1963 TnyFolder *folder = NULL;
1965 changed = tny_folder_change_get_changed (change);
1967 /* Do not notify the observers if the folder of the header
1968 view has changed before this call to the observer
1970 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1971 folder = tny_folder_change_get_folder (change);
1972 if (folder != priv->folder)
1975 MODEST_DEBUG_BLOCK (
1976 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1977 g_print ("ADDED %d/%d (r/t) \n",
1978 tny_folder_change_get_new_unread_count (change),
1979 tny_folder_change_get_new_all_count (change));
1980 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1981 g_print ("ALL COUNT %d\n",
1982 tny_folder_change_get_new_all_count (change));
1983 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1984 g_print ("UNREAD COUNT %d\n",
1985 tny_folder_change_get_new_unread_count (change));
1986 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1987 g_print ("EXPUNGED %d/%d (r/t) \n",
1988 tny_folder_change_get_new_unread_count (change),
1989 tny_folder_change_get_new_all_count (change));
1990 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1991 g_print ("FOLDER RENAME\n");
1992 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
1993 g_print ("MSG RECEIVED %d/%d (r/t) \n",
1994 tny_folder_change_get_new_unread_count (change),
1995 tny_folder_change_get_new_all_count (change));
1996 g_print ("---------------------------------------------------\n");
1999 /* Check folder count */
2000 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2001 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2003 g_mutex_lock (priv->observers_lock);
2005 /* Emit signal to evaluate how headers changes affects
2006 to the window view */
2007 g_signal_emit (G_OBJECT(self),
2008 signals[MSG_COUNT_CHANGED_SIGNAL],
2011 /* Added or removed headers, so data stored on cliboard are invalid */
2012 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2013 modest_email_clipboard_clear (priv->clipboard);
2015 g_mutex_unlock (priv->observers_lock);
2021 g_object_unref (folder);
2025 modest_header_view_is_empty (ModestHeaderView *self)
2027 ModestHeaderViewPrivate *priv;
2029 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2031 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2033 return priv->status == HEADER_VIEW_EMPTY;
2037 modest_header_view_clear (ModestHeaderView *self)
2039 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2041 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2045 modest_header_view_copy_selection (ModestHeaderView *header_view)
2047 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2049 /* Copy selection */
2050 _clipboard_set_selected_data (header_view, FALSE);
2054 modest_header_view_cut_selection (ModestHeaderView *header_view)
2056 ModestHeaderViewPrivate *priv = NULL;
2057 const gchar **hidding = NULL;
2058 guint i, n_selected;
2060 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2062 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2064 /* Copy selection */
2065 _clipboard_set_selected_data (header_view, TRUE);
2067 /* Get hidding ids */
2068 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2070 /* Clear hidding array created by previous cut operation */
2071 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2073 /* Copy hidding array */
2074 priv->n_selected = n_selected;
2075 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2076 for (i=0; i < n_selected; i++)
2077 priv->hidding_ids[i] = g_strdup(hidding[i]);
2079 /* Hide cut headers */
2080 modest_header_view_refilter (header_view);
2087 _clipboard_set_selected_data (ModestHeaderView *header_view,
2090 ModestHeaderViewPrivate *priv = NULL;
2091 TnyList *headers = NULL;
2093 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2094 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2096 /* Set selected data on clipboard */
2097 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2098 headers = modest_header_view_get_selected_headers (header_view);
2099 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2102 g_object_unref (headers);
2106 ModestHeaderView *self;
2111 notify_filter_change (gpointer data)
2113 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2115 g_signal_emit (info->self,
2116 signals[MSG_COUNT_CHANGED_SIGNAL],
2117 0, info->folder, NULL);
2123 notify_filter_change_destroy (gpointer data)
2125 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2126 ModestHeaderViewPrivate *priv;
2128 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2129 priv->status_timeout = 0;
2131 g_object_unref (info->self);
2132 g_object_unref (info->folder);
2133 g_slice_free (NotifyFilterInfo, info);
2137 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2139 /* For the moment we only need to filter outbox */
2140 return priv->is_outbox;
2144 header_match_string (TnyHeader *header, gchar **words)
2151 gchar *subject_fold;
2157 gchar **current_word;
2160 subject = tny_header_dup_subject (header);
2161 cc = tny_header_dup_cc (header);
2162 bcc = tny_header_dup_bcc (header);
2163 to = tny_header_dup_to (header);
2164 from = tny_header_dup_from (header);
2166 subject_fold = g_utf8_casefold (subject, -1);
2168 bcc_fold = g_utf8_casefold (bcc, -1);
2170 cc_fold = g_utf8_casefold (cc, -1);
2172 to_fold = g_utf8_casefold (to, -1);
2174 from_fold = g_utf8_casefold (from, -1);
2179 for (current_word = words; *current_word != NULL; current_word++) {
2181 if ((subject && g_strstr_len (subject_fold, -1, *current_word))
2182 || (cc && g_strstr_len (cc_fold, -1, *current_word))
2183 || (bcc && g_strstr_len (bcc_fold, -1, *current_word))
2184 || (to && g_strstr_len (to_fold, -1, *current_word))
2185 || (from && g_strstr_len (from_fold, -1, *current_word))) {
2193 g_free (subject_fold);
2203 filter_row (GtkTreeModel *model,
2207 ModestHeaderViewPrivate *priv = NULL;
2208 TnyHeaderFlags flags;
2209 TnyHeader *header = NULL;
2212 gboolean visible = TRUE;
2213 gboolean found = FALSE;
2214 GValue value = {0,};
2215 HeaderViewStatus old_status;
2217 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2218 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2220 /* Get header from model */
2221 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2222 flags = (TnyHeaderFlags) g_value_get_int (&value);
2223 g_value_unset (&value);
2224 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2225 header = (TnyHeader *) g_value_get_object (&value);
2226 g_value_unset (&value);
2228 /* Get message id from header (ensure is a valid id) */
2234 /* Hide deleted and mark as deleted heders */
2235 if (flags & TNY_HEADER_FLAG_DELETED ||
2236 flags & TNY_HEADER_FLAG_EXPUNGED) {
2241 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2242 if (current_folder_needs_filtering (priv) &&
2243 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2249 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2250 if (current_folder_needs_filtering (priv) &&
2251 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2257 if (visible && priv->filter_string) {
2258 if (!header_match_string (header, priv->filter_string_splitted)) {
2262 if (priv->filter_date_range) {
2263 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2264 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2271 /* If no data on clipboard, return always TRUE */
2272 if (modest_email_clipboard_cleared(priv->clipboard)) {
2278 if (priv->hidding_ids != NULL) {
2279 id = tny_header_dup_message_id (header);
2280 for (i=0; i < priv->n_selected && !found; i++)
2281 if (priv->hidding_ids[i] != NULL && id != NULL)
2282 found = (!strcmp (priv->hidding_ids[i], id));
2289 old_status = priv->status;
2290 priv->status = ((gboolean) priv->status) && !visible;
2291 if ((priv->notify_status) && (priv->status != old_status)) {
2292 if (priv->status_timeout)
2293 g_source_remove (priv->status_timeout);
2296 NotifyFilterInfo *info;
2298 info = g_slice_new0 (NotifyFilterInfo);
2299 info->self = g_object_ref (G_OBJECT (user_data));
2301 info->folder = tny_header_get_folder (header);
2302 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2303 notify_filter_change,
2305 notify_filter_change_destroy);
2313 _clear_hidding_filter (ModestHeaderView *header_view)
2315 ModestHeaderViewPrivate *priv = NULL;
2318 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2319 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2321 if (priv->hidding_ids != NULL) {
2322 for (i=0; i < priv->n_selected; i++)
2323 g_free (priv->hidding_ids[i]);
2324 g_free(priv->hidding_ids);
2329 modest_header_view_refilter (ModestHeaderView *header_view)
2331 GtkTreeModel *model, *sortable = NULL;
2332 ModestHeaderViewPrivate *priv = NULL;
2334 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2335 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2337 /* Hide cut headers */
2338 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2339 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2340 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2341 if (GTK_IS_TREE_MODEL_FILTER (model)) {
2342 priv->status = HEADER_VIEW_INIT;
2343 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
2349 * Called when an account is removed. If I'm showing a folder of the
2350 * account that has been removed then clear the view
2353 on_account_removed (TnyAccountStore *self,
2354 TnyAccount *account,
2357 ModestHeaderViewPrivate *priv = NULL;
2359 /* Ignore changes in transport accounts */
2360 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2363 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2366 TnyAccount *my_account;
2368 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2369 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2370 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2371 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2373 my_account = tny_folder_get_account (priv->folder);
2377 if (my_account == account)
2378 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2379 g_object_unref (my_account);
2385 modest_header_view_add_observer(ModestHeaderView *header_view,
2386 ModestHeaderViewObserver *observer)
2388 ModestHeaderViewPrivate *priv;
2390 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2391 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2393 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2395 g_mutex_lock(priv->observer_list_lock);
2396 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2397 g_mutex_unlock(priv->observer_list_lock);
2401 modest_header_view_remove_observer(ModestHeaderView *header_view,
2402 ModestHeaderViewObserver *observer)
2404 ModestHeaderViewPrivate *priv;
2406 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2407 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2409 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2411 g_mutex_lock(priv->observer_list_lock);
2412 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2413 g_mutex_unlock(priv->observer_list_lock);
2417 modest_header_view_notify_observers(ModestHeaderView *header_view,
2418 GtkTreeModel *model,
2419 const gchar *tny_folder_id)
2421 ModestHeaderViewPrivate *priv = NULL;
2423 ModestHeaderViewObserver *observer;
2426 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2428 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2430 g_mutex_lock(priv->observer_list_lock);
2431 iter = priv->observer_list;
2432 while(iter != NULL){
2433 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2434 modest_header_view_observer_update(observer, model,
2436 iter = g_slist_next(iter);
2438 g_mutex_unlock(priv->observer_list_lock);
2442 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2444 ModestHeaderViewPrivate *priv = NULL;
2446 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2447 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2451 modest_header_view_set_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 modest_header_view_unset_filter (ModestHeaderView *self,
2467 ModestHeaderViewFilter filter)
2469 ModestHeaderViewPrivate *priv;
2471 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2472 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2474 priv->filter &= ~filter;
2476 if (current_folder_needs_filtering (priv))
2477 modest_header_view_refilter (self);
2481 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2483 if (strcmp ("style", spec->name) == 0) {
2484 update_style (MODEST_HEADER_VIEW (obj));
2485 gtk_widget_queue_draw (GTK_WIDGET (obj));
2490 update_style (ModestHeaderView *self)
2492 ModestHeaderViewPrivate *priv;
2493 GdkColor style_color;
2494 GdkColor style_active_color;
2495 PangoAttrList *attr_list;
2497 PangoAttribute *attr;
2499 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2500 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2504 attr_list = pango_attr_list_new ();
2505 if (!gtk_style_lookup_color (GTK_WIDGET (self)->style, "SecondaryTextColor", &style_color)) {
2506 gdk_color_parse ("grey", &style_color);
2508 priv->secondary_color = style_color;
2509 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2510 pango_attr_list_insert (attr_list, attr);
2513 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2515 "SmallSystemFont", NULL,
2518 attr = pango_attr_font_desc_new (pango_font_description_copy
2519 (style->font_desc));
2520 pango_attr_list_insert (attr_list, attr);
2522 g_object_set (G_OBJECT (priv->renderer_address),
2523 "foreground-gdk", &(priv->secondary_color),
2524 "foreground-set", TRUE,
2525 "attributes", attr_list,
2527 g_object_set (G_OBJECT (priv->renderer_date_status),
2528 "foreground-gdk", &(priv->secondary_color),
2529 "foreground-set", TRUE,
2530 "attributes", attr_list,
2532 pango_attr_list_unref (attr_list);
2534 g_object_set (G_OBJECT (priv->renderer_address),
2535 "foreground-gdk", &(priv->secondary_color),
2536 "foreground-set", TRUE,
2537 "scale", PANGO_SCALE_SMALL,
2540 g_object_set (G_OBJECT (priv->renderer_date_status),
2541 "foreground-gdk", &(priv->secondary_color),
2542 "foreground-set", TRUE,
2543 "scale", PANGO_SCALE_SMALL,
2548 if (gtk_style_lookup_color (GTK_WIDGET (self)->style, "ActiveTextColor", &style_active_color)) {
2549 priv->active_color = style_active_color;
2550 #ifdef MODEST_TOOLKIT_HILDON2
2551 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2552 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2555 #ifdef MODEST_TOOLKIT_HILDON2
2556 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2562 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2567 GtkTreeModel *tree_model;
2572 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2580 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2581 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2585 gtk_tree_model_get (tree_model, &iter,
2586 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2593 parse_date_side (const gchar *string, time_t *date_side)
2599 gboolean result = FALSE;
2601 if (string && string[0] == '\0') {
2606 casefold = g_utf8_casefold (string, -1);
2607 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2608 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2609 date = g_date_new ();
2611 if (g_utf8_collate (casefold, today) == 0) {
2612 *date_side = time (NULL);
2617 if (g_utf8_collate (casefold, yesterday) == 0) {
2618 *date_side = time (NULL) - 24*60*60;
2623 g_date_set_parse (date, string);
2624 if (g_date_valid (date)) {
2626 g_date_to_struct_tm (date, &tm);
2627 *date_side = mktime (&tm);
2642 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2647 parts = g_strsplit (string, "..", 2);
2650 if (g_strv_length (parts) != 2) {
2657 if (!parse_date_side (parts[0], date_range_start)) {
2662 if (parse_date_side (parts[1], date_range_end)) {
2663 if (*date_range_end == 0) {
2664 *date_range_end = (time_t) -1;
2666 *date_range_end += (24*60*60 - 1);
2679 modest_header_view_set_show_latest (ModestHeaderView *header_view,
2682 ModestHeaderViewPrivate *priv;
2683 GtkTreeModel *sortable, *filter, *model;
2685 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2686 priv->show_latest = show_latest;
2688 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2689 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2690 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2691 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2692 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2694 tny_gtk_header_list_model_set_show_latest (TNY_GTK_HEADER_LIST_MODEL (model), priv->show_latest);
2701 modest_header_view_get_show_latest (ModestHeaderView *header_view)
2703 ModestHeaderViewPrivate *priv;
2704 GtkTreeModel *sortable, *filter, *model;
2707 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2709 result = priv->show_latest;
2710 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2711 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2712 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2713 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2714 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2716 result = tny_gtk_header_list_model_get_show_latest (TNY_GTK_HEADER_LIST_MODEL (model));
2725 modest_header_view_get_not_latest (ModestHeaderView *header_view)
2727 ModestHeaderViewPrivate *priv;
2728 gint not_latest = 0;
2729 GtkTreeModel *sortable, *filter, *model;
2731 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2733 if (priv->show_latest == 0)
2736 sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2737 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2738 filter = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2739 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2740 model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2742 not_latest = MAX (0, tny_list_get_length (TNY_LIST (model)) - priv->show_latest);
2751 modest_header_view_set_filter_string (ModestHeaderView *self,
2752 const gchar *filter_string)
2754 ModestHeaderViewPrivate *priv;
2756 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2757 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2759 if (priv->filter_string)
2760 g_free (priv->filter_string);
2762 priv->filter_string = g_strdup (filter_string);
2763 priv->filter_date_range = FALSE;
2765 if (priv->filter_string_splitted) {
2766 g_strfreev (priv->filter_string_splitted);
2767 priv->filter_string_splitted = NULL;
2770 if (priv->filter_string) {
2771 gchar **split, **current, **current_target;
2773 split = g_strsplit (priv->filter_string, " ", 0);
2775 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2776 current_target = priv->filter_string_splitted;
2777 for (current = split; *current != 0; current ++) {
2778 gboolean has_date_range = FALSE;;
2779 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2780 time_t range_start, range_end;
2781 /* It contains .. but it's not ".." so it may be a date range */
2782 if (parse_date_range (*current, &range_start, &range_end)) {
2783 priv->filter_date_range = TRUE;
2784 has_date_range = TRUE;
2785 priv->date_range_start = range_start;
2786 priv->date_range_end = range_end;
2789 if (!has_date_range) {
2790 *current_target = g_utf8_casefold (*current, -1);
2794 *current_target = '\0';
2797 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2800 #ifdef MODEST_TOOLKIT_HILDON2
2802 on_live_search_refilter (HildonLiveSearch *livesearch,
2803 ModestHeaderView *self)
2805 const gchar *needle;
2807 needle = hildon_live_search_get_text (livesearch);
2808 if (needle && needle[0] != '\0') {
2809 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2811 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2818 modest_header_view_setup_live_search (ModestHeaderView *self)
2820 ModestHeaderViewPrivate *priv;
2822 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2823 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2824 priv->live_search = hildon_live_search_new ();
2826 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2828 return priv->live_search;