1 /* Copyright (c) 2006, Nokia Corporation
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of the Nokia Corporation nor the names of its
14 * contributors may be used to endorse or promote products derived from
15 * this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
18 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
20 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
21 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <glib/gi18n.h>
32 #include <tny-simple-list.h>
33 #include <tny-folder-monitor.h>
34 #include <tny-folder-change.h>
35 #include <tny-error.h>
36 #include <tny-merge-folder.h>
39 #include <modest-header-view.h>
40 #include <modest-header-view-priv.h>
41 #include <modest-dnd.h>
42 #include <modest-tny-folder.h>
43 #include <modest-debug.h>
44 #include <modest-ui-actions.h>
45 #include <modest-marshal.h>
46 #include <modest-text-utils.h>
47 #include <modest-icon-names.h>
48 #include <modest-runtime.h>
49 #include "modest-platform.h"
50 #include <modest-hbox-cell-renderer.h>
51 #include <modest-vbox-cell-renderer.h>
52 #include <modest-datetime-formatter.h>
53 #include <modest-ui-constants.h>
54 #ifdef MODEST_TOOLKIT_HILDON2
55 #include <hildon/hildon.h>
58 static void modest_header_view_class_init (ModestHeaderViewClass *klass);
59 static void modest_header_view_init (ModestHeaderView *obj);
60 static void modest_header_view_finalize (GObject *obj);
61 static void modest_header_view_dispose (GObject *obj);
63 static void on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
64 GtkTreeViewColumn *column, gpointer userdata);
66 static gint cmp_rows (GtkTreeModel *tree_model,
71 static gint cmp_subject_rows (GtkTreeModel *tree_model,
76 static gboolean filter_row (GtkTreeModel *model,
80 static void on_account_removed (TnyAccountStore *self,
84 static void on_selection_changed (GtkTreeSelection *sel,
87 static gboolean on_button_press_event (GtkWidget * self, GdkEventButton * event,
90 static gboolean on_button_release_event(GtkWidget * self, GdkEventButton * event,
93 static void setup_drag_and_drop (GtkWidget *self);
95 static void enable_drag_and_drop (GtkWidget *self);
97 static void disable_drag_and_drop (GtkWidget *self);
99 static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model);
101 #ifndef MODEST_TOOLKIT_HILDON2
102 static gboolean on_focus_in (GtkWidget *sef,
103 GdkEventFocus *event,
106 static gboolean on_focus_out (GtkWidget *self,
107 GdkEventFocus *event,
111 static void folder_monitor_update (TnyFolderObserver *self,
112 TnyFolderChange *change);
114 static void tny_folder_observer_init (TnyFolderObserverIface *klass);
116 static void _clipboard_set_selected_data (ModestHeaderView *header_view, gboolean delete);
118 static void _clear_hidding_filter (ModestHeaderView *header_view);
120 static void modest_header_view_notify_observers(ModestHeaderView *header_view,
122 const gchar *tny_folder_id);
124 static gboolean modest_header_view_on_expose_event (GtkTreeView *header_view,
125 GdkEventExpose *event,
128 static void on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata);
129 static void update_style (ModestHeaderView *self);
132 HEADER_VIEW_NON_EMPTY,
137 typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate;
138 struct _ModestHeaderViewPrivate {
140 ModestHeaderViewStyle style;
143 TnyFolderMonitor *monitor;
144 GMutex *observers_lock;
146 /*header-view-observer observer*/
147 GMutex *observer_list_lock;
148 GSList *observer_list;
150 /* not unref this object, its a singlenton */
151 ModestEmailClipboard *clipboard;
153 /* Filter tree model */
156 GtkTreeRowReference *autoselect_reference;
157 ModestHeaderViewFilter filter;
158 #ifdef MODEST_TOOLKIT_HILDON2
159 GtkWidget *live_search;
162 gint sort_colid[2][TNY_FOLDER_TYPE_NUM];
163 gint sort_type[2][TNY_FOLDER_TYPE_NUM];
165 gulong selection_changed_handler;
166 gulong acc_removed_handler;
168 GList *drag_begin_cached_selected_rows;
170 HeaderViewStatus status;
171 guint status_timeout;
172 gboolean notify_status; /* whether or not the filter_row should notify about changes in the filtering */
174 ModestDatetimeFormatter *datetime_formatter;
176 GtkCellRenderer *renderer_subject;
177 GtkCellRenderer *renderer_address;
178 GtkCellRenderer *renderer_date_status;
180 GdkColor active_color;
181 GdkColor secondary_color;
183 gchar *filter_string;
184 gchar **filter_string_splitted;
185 gboolean filter_date_range;
186 time_t date_range_start;
187 time_t date_range_end;
190 typedef struct _HeadersCountChangedHelper HeadersCountChangedHelper;
191 struct _HeadersCountChangedHelper {
192 ModestHeaderView *self;
193 TnyFolderChange *change;
197 #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \
198 MODEST_TYPE_HEADER_VIEW, \
199 ModestHeaderViewPrivate))
203 #define MODEST_HEADER_VIEW_PTR "modest-header-view"
205 #define _HEADER_VIEW_SUBJECT_FOLD "_subject_modest_header_view"
206 #define _HEADER_VIEW_FROM_FOLD "_from_modest_header_view"
207 #define _HEADER_VIEW_TO_FOLD "_to_modest_header_view"
208 #define _HEADER_VIEW_CC_FOLD "_cc_modest_header_view"
209 #define _HEADER_VIEW_BCC_FOLD "_bcc_modest_header_view"
212 HEADER_SELECTED_SIGNAL,
213 HEADER_ACTIVATED_SIGNAL,
214 ITEM_NOT_FOUND_SIGNAL,
215 MSG_COUNT_CHANGED_SIGNAL,
216 UPDATING_MSG_LIST_SIGNAL,
221 static GObjectClass *parent_class = NULL;
223 /* uncomment the following if you have defined any signals */
224 static guint signals[LAST_SIGNAL] = {0};
227 modest_header_view_get_type (void)
229 static GType my_type = 0;
231 static const GTypeInfo my_info = {
232 sizeof(ModestHeaderViewClass),
233 NULL, /* base init */
234 NULL, /* base finalize */
235 (GClassInitFunc) modest_header_view_class_init,
236 NULL, /* class finalize */
237 NULL, /* class data */
238 sizeof(ModestHeaderView),
240 (GInstanceInitFunc) modest_header_view_init,
244 static const GInterfaceInfo tny_folder_observer_info =
246 (GInterfaceInitFunc) tny_folder_observer_init, /* interface_init */
247 NULL, /* interface_finalize */
248 NULL /* interface_data */
250 my_type = g_type_register_static (GTK_TYPE_TREE_VIEW,
254 g_type_add_interface_static (my_type, TNY_TYPE_FOLDER_OBSERVER,
255 &tny_folder_observer_info);
263 modest_header_view_class_init (ModestHeaderViewClass *klass)
265 GObjectClass *gobject_class;
266 gobject_class = (GObjectClass*) klass;
268 parent_class = g_type_class_peek_parent (klass);
269 gobject_class->finalize = modest_header_view_finalize;
270 gobject_class->dispose = modest_header_view_dispose;
272 g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate));
274 signals[HEADER_SELECTED_SIGNAL] =
275 g_signal_new ("header_selected",
276 G_TYPE_FROM_CLASS (gobject_class),
278 G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected),
280 g_cclosure_marshal_VOID__POINTER,
281 G_TYPE_NONE, 1, G_TYPE_POINTER);
283 signals[HEADER_ACTIVATED_SIGNAL] =
284 g_signal_new ("header_activated",
285 G_TYPE_FROM_CLASS (gobject_class),
287 G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated),
289 gtk_marshal_VOID__POINTER_POINTER,
290 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
293 signals[ITEM_NOT_FOUND_SIGNAL] =
294 g_signal_new ("item_not_found",
295 G_TYPE_FROM_CLASS (gobject_class),
297 G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found),
299 g_cclosure_marshal_VOID__INT,
300 G_TYPE_NONE, 1, G_TYPE_INT);
302 signals[MSG_COUNT_CHANGED_SIGNAL] =
303 g_signal_new ("msg_count_changed",
304 G_TYPE_FROM_CLASS (gobject_class),
306 G_STRUCT_OFFSET (ModestHeaderViewClass, msg_count_changed),
308 modest_marshal_VOID__POINTER_POINTER,
309 G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER);
311 signals[UPDATING_MSG_LIST_SIGNAL] =
312 g_signal_new ("updating-msg-list",
313 G_TYPE_FROM_CLASS (gobject_class),
315 G_STRUCT_OFFSET (ModestHeaderViewClass, updating_msg_list),
317 g_cclosure_marshal_VOID__BOOLEAN,
318 G_TYPE_NONE, 1, G_TYPE_BOOLEAN);
320 #ifdef MODEST_TOOLKIT_HILDON2
321 gtk_rc_parse_string ("class \"ModestHeaderView\" style \"fremantle-touchlist\"");
327 tny_folder_observer_init (TnyFolderObserverIface *klass)
329 klass->update = folder_monitor_update;
332 static GtkTreeViewColumn*
333 get_new_column (const gchar *name, GtkCellRenderer *renderer,
334 gboolean resizable, gint sort_col_id, gboolean show_as_text,
335 GtkTreeCellDataFunc cell_data_func, gpointer user_data)
337 GtkTreeViewColumn *column;
339 column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL);
340 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
342 gtk_tree_view_column_set_resizable (column, resizable);
344 gtk_tree_view_column_set_expand (column, TRUE);
347 gtk_tree_view_column_add_attribute (column, renderer, "text",
349 if (sort_col_id >= 0)
350 gtk_tree_view_column_set_sort_column_id (column, sort_col_id);
352 gtk_tree_view_column_set_sort_indicator (column, FALSE);
353 gtk_tree_view_column_set_reorderable (column, TRUE);
356 gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func,
363 remove_all_columns (ModestHeaderView *obj)
365 GList *columns, *cursor;
367 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW(obj));
369 for (cursor = columns; cursor; cursor = cursor->next)
370 gtk_tree_view_remove_column (GTK_TREE_VIEW(obj),
371 GTK_TREE_VIEW_COLUMN(cursor->data));
372 g_list_free (columns);
376 modest_header_view_set_columns (ModestHeaderView *self, const GList *columns, TnyFolderType type)
378 GtkTreeModel *sortable, *filter_model;
379 GtkTreeViewColumn *column=NULL;
380 GtkTreeSelection *selection = NULL;
381 GtkCellRenderer *renderer_header,
382 *renderer_attach, *renderer_compact_date_or_status;
383 GtkCellRenderer *renderer_compact_header, *renderer_recpt_box,
384 *renderer_subject_box, *renderer_recpt,
386 ModestHeaderViewPrivate *priv;
387 GtkTreeViewColumn *compact_column = NULL;
390 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
391 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, FALSE);
393 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
395 priv->is_outbox = (type == TNY_FOLDER_TYPE_OUTBOX);
397 /* TODO: check whether these renderers need to be freed */
398 renderer_attach = gtk_cell_renderer_pixbuf_new ();
399 renderer_priority = gtk_cell_renderer_pixbuf_new ();
400 renderer_header = gtk_cell_renderer_text_new ();
402 renderer_compact_header = modest_vbox_cell_renderer_new ();
403 renderer_recpt_box = modest_hbox_cell_renderer_new ();
404 renderer_subject_box = modest_hbox_cell_renderer_new ();
405 renderer_recpt = gtk_cell_renderer_text_new ();
406 priv->renderer_address = renderer_recpt;
407 priv->renderer_subject = gtk_cell_renderer_text_new ();
408 renderer_compact_date_or_status = gtk_cell_renderer_text_new ();
409 priv->renderer_date_status = renderer_compact_date_or_status;
411 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_subject_box, FALSE);
412 g_object_set_data (G_OBJECT (renderer_compact_header), "subject-box-renderer", renderer_subject_box);
413 modest_vbox_cell_renderer_append (MODEST_VBOX_CELL_RENDERER (renderer_compact_header), renderer_recpt_box, FALSE);
414 g_object_set_data (G_OBJECT (renderer_compact_header), "recpt-box-renderer", renderer_recpt_box);
415 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), renderer_priority, FALSE);
416 g_object_set_data (G_OBJECT (renderer_subject_box), "priority-renderer", renderer_priority);
417 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_subject_box), priv->renderer_subject, TRUE);
418 g_object_set_data (G_OBJECT (renderer_subject_box), "subject-renderer", priv->renderer_subject);
419 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_attach, FALSE);
420 g_object_set_data (G_OBJECT (renderer_recpt_box), "attach-renderer", renderer_attach);
421 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_recpt, TRUE);
422 g_object_set_data (G_OBJECT (renderer_recpt_box), "recipient-renderer", renderer_recpt);
423 modest_hbox_cell_renderer_append (MODEST_HBOX_CELL_RENDERER (renderer_recpt_box), renderer_compact_date_or_status, FALSE);
424 g_object_set_data (G_OBJECT (renderer_recpt_box), "date-renderer", renderer_compact_date_or_status);
426 #ifdef MODEST_TOOLKIT_HILDON2
427 g_object_set (G_OBJECT (renderer_compact_header), "xpad", 0, NULL);
429 g_object_set (G_OBJECT (renderer_subject_box), "yalign", 1.0, NULL);
430 #ifndef MODEST_TOOLKIT_GTK
431 gtk_cell_renderer_set_fixed_size (renderer_subject_box, -1, 32);
432 gtk_cell_renderer_set_fixed_size (renderer_recpt_box, -1, 32);
434 g_object_set (G_OBJECT (renderer_recpt_box), "yalign", 0.0, NULL);
435 g_object_set(G_OBJECT(renderer_header),
436 "ellipsize", PANGO_ELLIPSIZE_END,
438 g_object_set (G_OBJECT (priv->renderer_subject),
439 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 1.0,
441 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (priv->renderer_subject), 1);
442 g_object_set (G_OBJECT (renderer_recpt),
443 "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.1,
445 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_recpt), 1);
446 g_object_set(G_OBJECT(renderer_compact_date_or_status),
447 "xalign", 1.0, "yalign", 0.1,
449 gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer_compact_date_or_status), 1);
450 #ifdef MODEST_TOOLKIT_HILDON2
451 g_object_set (G_OBJECT (renderer_priority),
453 "xalign", 0.0, NULL);
454 g_object_set (G_OBJECT (renderer_attach),
456 "xalign", 0.0, NULL);
458 g_object_set (G_OBJECT (renderer_priority),
459 "yalign", 0.5, NULL);
460 g_object_set (G_OBJECT (renderer_attach),
461 "yalign", 0.0, NULL);
464 #ifdef MODEST_TOOLKIT_HILDON1
465 gtk_cell_renderer_set_fixed_size (renderer_attach, 32, 26);
466 gtk_cell_renderer_set_fixed_size (renderer_priority, 32, 26);
467 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
468 #elif MODEST_TOOLKIT_HILDON2
469 gtk_cell_renderer_set_fixed_size (renderer_attach, 24 + MODEST_MARGIN_DEFAULT, 26);
470 gtk_cell_renderer_set_fixed_size (renderer_priority, 24 + MODEST_MARGIN_DEFAULT, 26);
471 gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64);
473 gtk_cell_renderer_set_fixed_size (renderer_attach, 16, 16);
474 gtk_cell_renderer_set_fixed_size (renderer_priority, 16, 16);
475 /* gtk_cell_renderer_set_fixed_size (renderer_compact_header, -1, 64); */
478 remove_all_columns (self);
480 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
481 gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
483 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
484 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
485 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
488 /* Add new columns */
489 for (cursor = columns; cursor; cursor = g_list_next(cursor)) {
490 ModestHeaderViewColumn col =
491 (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data);
493 if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) {
494 g_printerr ("modest: invalid column %d in column list\n", col);
500 case MODEST_HEADER_VIEW_COLUMN_ATTACH:
501 column = get_new_column (_("A"), renderer_attach, FALSE,
502 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
504 (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data,
506 gtk_tree_view_column_set_fixed_width (column, 45);
510 case MODEST_HEADER_VIEW_COLUMN_FROM:
511 column = get_new_column (_("From"), renderer_header, TRUE,
512 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
514 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
515 GINT_TO_POINTER(TRUE));
518 case MODEST_HEADER_VIEW_COLUMN_TO:
519 column = get_new_column (_("To"), renderer_header, TRUE,
520 TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN,
522 (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data,
523 GINT_TO_POINTER(FALSE));
526 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN:
527 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
528 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
530 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
531 GINT_TO_POINTER(MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_IN));
532 compact_column = column;
535 case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT:
536 column = get_new_column (_("Header"), renderer_compact_header, TRUE,
537 TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN,
539 (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data,
540 GINT_TO_POINTER((type == TNY_FOLDER_TYPE_OUTBOX)?
541 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUTBOX:
542 MODEST_HEADER_VIEW_COMPACT_HEADER_MODE_OUT));
543 compact_column = column;
547 case MODEST_HEADER_VIEW_COLUMN_SUBJECT:
548 column = get_new_column (_("Subject"), renderer_header, TRUE,
549 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
551 (GtkTreeCellDataFunc)_modest_header_view_header_cell_data,
555 case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE:
556 column = get_new_column (_("Received"), renderer_header, TRUE,
557 TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN,
559 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
560 GINT_TO_POINTER(TRUE));
563 case MODEST_HEADER_VIEW_COLUMN_SENT_DATE:
564 column = get_new_column (_("Sent"), renderer_header, TRUE,
565 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN,
567 (GtkTreeCellDataFunc)_modest_header_view_date_cell_data,
568 GINT_TO_POINTER(FALSE));
571 case MODEST_HEADER_VIEW_COLUMN_SIZE:
572 column = get_new_column (_("Size"), renderer_header, TRUE,
573 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
575 (GtkTreeCellDataFunc)_modest_header_view_size_cell_data,
578 case MODEST_HEADER_VIEW_COLUMN_STATUS:
579 column = get_new_column (_("Status"), renderer_compact_date_or_status, TRUE,
580 TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN,
582 (GtkTreeCellDataFunc)_modest_header_view_status_cell_data,
587 g_return_val_if_reached(FALSE);
590 /* we keep the column id around */
591 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN,
592 GINT_TO_POINTER(col));
594 /* we need this ptr when sorting the rows */
595 g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR,
597 gtk_tree_view_append_column (GTK_TREE_VIEW(self), column);
601 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
602 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
603 (GtkTreeIterCompareFunc) cmp_rows,
604 compact_column, NULL);
605 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
606 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
607 (GtkTreeIterCompareFunc) cmp_subject_rows,
608 compact_column, NULL);
612 g_signal_connect (G_OBJECT (self), "notify::style", G_CALLBACK (on_notify_style), (gpointer) self);
618 datetime_format_changed (ModestDatetimeFormatter *formatter,
619 ModestHeaderView *self)
621 gtk_widget_queue_draw (GTK_WIDGET (self));
625 modest_header_view_init (ModestHeaderView *obj)
627 ModestHeaderViewPrivate *priv;
630 priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj);
633 priv->is_outbox = FALSE;
635 priv->monitor = NULL;
636 priv->observers_lock = g_mutex_new ();
637 priv->autoselect_reference = NULL;
639 priv->status = HEADER_VIEW_INIT;
640 priv->status_timeout = 0;
641 priv->notify_status = TRUE;
643 priv->observer_list_lock = g_mutex_new();
644 priv->observer_list = NULL;
646 priv->clipboard = modest_runtime_get_email_clipboard ();
647 priv->hidding_ids = NULL;
648 priv->n_selected = 0;
649 priv->filter = MODEST_HEADER_VIEW_FILTER_NONE;
650 #ifdef MODEST_TOOLKIT_HILDON2
651 priv->live_search = NULL;
653 priv->filter_string = NULL;
654 priv->filter_string_splitted = NULL;
655 priv->filter_date_range = FALSE;
656 priv->selection_changed_handler = 0;
657 priv->acc_removed_handler = 0;
659 /* Sort parameters */
660 for (j=0; j < 2; j++) {
661 for (i=0; i < TNY_FOLDER_TYPE_NUM; i++) {
662 priv->sort_colid[j][i] = -1;
663 priv->sort_type[j][i] = GTK_SORT_DESCENDING;
667 priv->datetime_formatter = modest_datetime_formatter_new ();
668 g_signal_connect (G_OBJECT (priv->datetime_formatter), "format-changed",
669 G_CALLBACK (datetime_format_changed), (gpointer) obj);
671 setup_drag_and_drop (GTK_WIDGET(obj));
675 modest_header_view_dispose (GObject *obj)
677 ModestHeaderView *self;
678 ModestHeaderViewPrivate *priv;
679 GtkTreeSelection *sel;
681 self = MODEST_HEADER_VIEW(obj);
682 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
684 if (priv->datetime_formatter) {
685 g_object_unref (priv->datetime_formatter);
686 priv->datetime_formatter = NULL;
689 /* Free in the dispose to avoid unref cycles */
691 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (obj));
692 g_object_unref (G_OBJECT (priv->folder));
696 /* We need to do this here in the dispose because the
697 selection won't exist when finalizing */
698 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW(self));
699 if (sel && g_signal_handler_is_connected (sel, priv->selection_changed_handler)) {
700 g_signal_handler_disconnect (sel, priv->selection_changed_handler);
701 priv->selection_changed_handler = 0;
704 G_OBJECT_CLASS(parent_class)->dispose (obj);
708 modest_header_view_finalize (GObject *obj)
710 ModestHeaderView *self;
711 ModestHeaderViewPrivate *priv;
713 self = MODEST_HEADER_VIEW(obj);
714 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
716 if (g_signal_handler_is_connected (modest_runtime_get_account_store (),
717 priv->acc_removed_handler)) {
718 g_signal_handler_disconnect (modest_runtime_get_account_store (),
719 priv->acc_removed_handler);
722 /* There is no need to lock because there should not be any
723 * reference to self now. */
724 g_mutex_free(priv->observer_list_lock);
725 g_slist_free(priv->observer_list);
727 g_mutex_lock (priv->observers_lock);
729 tny_folder_monitor_stop (priv->monitor);
730 g_object_unref (G_OBJECT (priv->monitor));
732 g_mutex_unlock (priv->observers_lock);
733 g_mutex_free (priv->observers_lock);
735 /* Clear hidding array created by cut operation */
736 _clear_hidding_filter (MODEST_HEADER_VIEW (obj));
738 if (priv->autoselect_reference != NULL) {
739 gtk_tree_row_reference_free (priv->autoselect_reference);
740 priv->autoselect_reference = NULL;
743 if (priv->filter_string) {
744 g_free (priv->filter_string);
747 if (priv->filter_string_splitted) {
748 g_strfreev (priv->filter_string_splitted);
751 G_OBJECT_CLASS(parent_class)->finalize (obj);
756 modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style)
759 GtkTreeSelection *sel;
760 ModestHeaderView *self;
761 ModestHeaderViewPrivate *priv;
763 g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM,
766 obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL));
767 self = MODEST_HEADER_VIEW(obj);
768 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
770 modest_header_view_set_style (self, style);
772 gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj));
773 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE);
774 gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE);
776 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj),
777 TRUE); /* alternating row colors */
779 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
780 priv->selection_changed_handler =
781 g_signal_connect_after (sel, "changed",
782 G_CALLBACK(on_selection_changed), self);
784 g_signal_connect (self, "row-activated",
785 G_CALLBACK (on_header_row_activated), NULL);
787 #ifndef MODEST_TOOLKIT_HILDON2
788 g_signal_connect (self, "focus-in-event",
789 G_CALLBACK(on_focus_in), NULL);
790 g_signal_connect (self, "focus-out-event",
791 G_CALLBACK(on_focus_out), NULL);
794 g_signal_connect (self, "button-press-event",
795 G_CALLBACK(on_button_press_event), NULL);
796 g_signal_connect (self, "button-release-event",
797 G_CALLBACK(on_button_release_event), NULL);
799 priv->acc_removed_handler = g_signal_connect (modest_runtime_get_account_store (),
801 G_CALLBACK (on_account_removed),
804 g_signal_connect (self, "expose-event",
805 G_CALLBACK(modest_header_view_on_expose_event),
808 return GTK_WIDGET(self);
813 modest_header_view_count_selected_headers (ModestHeaderView *self)
815 GtkTreeSelection *sel;
818 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
820 /* Get selection object and check selected rows count */
821 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
822 selected_rows = gtk_tree_selection_count_selected_rows (sel);
824 return selected_rows;
828 modest_header_view_has_selected_headers (ModestHeaderView *self)
830 GtkTreeSelection *sel;
833 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
835 /* Get selection object and check selected rows count */
836 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
837 empty = gtk_tree_selection_count_selected_rows (sel) == 0;
844 modest_header_view_get_selected_headers (ModestHeaderView *self)
846 GtkTreeSelection *sel;
847 TnyList *header_list = NULL;
849 GList *list, *tmp = NULL;
850 GtkTreeModel *tree_model = NULL;
853 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
856 /* Get selected rows */
857 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
858 list = gtk_tree_selection_get_selected_rows (sel, &tree_model);
861 header_list = tny_simple_list_new();
863 list = g_list_reverse (list);
866 /* get header from selection */
867 gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data));
868 gtk_tree_model_get (tree_model, &iter,
869 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
871 /* Prepend to list */
872 tny_list_prepend (header_list, G_OBJECT (header));
873 g_object_unref (G_OBJECT (header));
875 tmp = g_list_next (tmp);
878 g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
885 /* scroll our list view so the selected item is visible */
887 scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up)
889 #ifdef MODEST_TOOLKIT_GTK
891 GtkTreePath *selected_path;
892 GtkTreePath *start, *end;
896 model = gtk_tree_view_get_model (GTK_TREE_VIEW(self));
897 selected_path = gtk_tree_model_get_path (model, iter);
899 start = gtk_tree_path_new ();
900 end = gtk_tree_path_new ();
902 gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end);
904 if (gtk_tree_path_compare (selected_path, start) < 0 ||
905 gtk_tree_path_compare (end, selected_path) < 0)
906 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self),
907 selected_path, NULL, TRUE,
910 gtk_tree_path_free (selected_path);
911 gtk_tree_path_free (start);
912 gtk_tree_path_free (end);
914 #endif /* MODEST_TOOLKIT_GTK */
919 modest_header_view_select_next (ModestHeaderView *self)
921 GtkTreeSelection *sel;
926 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
928 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
929 path = get_selected_row (GTK_TREE_VIEW(self), &model);
930 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
931 /* Unselect previous path */
932 gtk_tree_selection_unselect_path (sel, path);
934 /* Move path down and selects new one */
935 if (gtk_tree_model_iter_next (model, &iter)) {
936 gtk_tree_selection_select_iter (sel, &iter);
937 scroll_to_selected (self, &iter, FALSE);
939 gtk_tree_path_free(path);
945 modest_header_view_select_prev (ModestHeaderView *self)
947 GtkTreeSelection *sel;
952 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
954 sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
955 path = get_selected_row (GTK_TREE_VIEW(self), &model);
956 if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) {
957 /* Unselect previous path */
958 gtk_tree_selection_unselect_path (sel, path);
961 if (gtk_tree_path_prev (path)) {
962 gtk_tree_model_get_iter (model, &iter, path);
964 /* Select the new one */
965 gtk_tree_selection_select_iter (sel, &iter);
966 scroll_to_selected (self, &iter, TRUE);
969 gtk_tree_path_free (path);
974 modest_header_view_get_columns (ModestHeaderView *self)
976 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
978 return gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
984 modest_header_view_set_style (ModestHeaderView *self,
985 ModestHeaderViewStyle style)
987 ModestHeaderViewPrivate *priv;
988 gboolean show_col_headers = FALSE;
989 ModestHeaderViewStyle old_style;
991 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
992 g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM,
995 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
996 if (priv->style == style)
997 return TRUE; /* nothing to do */
1000 case MODEST_HEADER_VIEW_STYLE_DETAILS:
1001 show_col_headers = TRUE;
1003 case MODEST_HEADER_VIEW_STYLE_TWOLINES:
1006 g_return_val_if_reached (FALSE);
1008 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers);
1009 gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers);
1011 old_style = priv->style;
1012 priv->style = style;
1018 ModestHeaderViewStyle
1019 modest_header_view_get_style (ModestHeaderView *self)
1021 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), FALSE);
1023 return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style;
1026 /* This is used to automatically select the first header if the user
1027 * has not selected any header yet.
1030 modest_header_view_on_expose_event(GtkTreeView *header_view,
1031 GdkEventExpose *event,
1034 GtkTreeSelection *sel;
1035 GtkTreeModel *model;
1036 GtkTreeIter tree_iter;
1037 ModestHeaderViewPrivate *priv;
1039 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
1040 model = gtk_tree_view_get_model(header_view);
1045 #ifdef MODEST_TOOLKIT_HILDON2
1048 sel = gtk_tree_view_get_selection(header_view);
1049 if(!gtk_tree_selection_count_selected_rows(sel)) {
1050 if (gtk_tree_model_get_iter_first(model, &tree_iter)) {
1051 GtkTreePath *tree_iter_path;
1052 /* Prevent the widget from getting the focus
1053 when selecting the first item */
1054 tree_iter_path = gtk_tree_model_get_path (model, &tree_iter);
1055 g_object_set(header_view, "can-focus", FALSE, NULL);
1056 gtk_tree_selection_select_iter(sel, &tree_iter);
1057 gtk_tree_view_set_cursor (header_view, tree_iter_path, NULL, FALSE);
1058 g_object_set(header_view, "can-focus", TRUE, NULL);
1059 if (priv->autoselect_reference) {
1060 gtk_tree_row_reference_free (priv->autoselect_reference);
1062 priv->autoselect_reference = gtk_tree_row_reference_new (model, tree_iter_path);
1063 gtk_tree_path_free (tree_iter_path);
1066 if (priv->autoselect_reference != NULL && gtk_tree_row_reference_valid (priv->autoselect_reference)) {
1067 gboolean moved_selection = FALSE;
1068 GtkTreePath * last_path;
1069 if (gtk_tree_selection_count_selected_rows (sel) != 1) {
1070 moved_selection = TRUE;
1074 rows = gtk_tree_selection_get_selected_rows (sel, NULL);
1075 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1076 if (gtk_tree_path_compare (last_path, (GtkTreePath *) rows->data) != 0)
1077 moved_selection = TRUE;
1078 g_list_foreach (rows, (GFunc) gtk_tree_path_free, NULL);
1080 gtk_tree_path_free (last_path);
1082 if (moved_selection) {
1083 gtk_tree_row_reference_free (priv->autoselect_reference);
1084 priv->autoselect_reference = NULL;
1087 if (gtk_tree_model_get_iter_first (model, &tree_iter)) {
1088 GtkTreePath *current_path;
1089 current_path = gtk_tree_model_get_path (model, &tree_iter);
1090 last_path = gtk_tree_row_reference_get_path (priv->autoselect_reference);
1091 if (gtk_tree_path_compare (current_path, last_path) != 0) {
1092 g_object_set(header_view, "can-focus", FALSE, NULL);
1093 gtk_tree_selection_unselect_all (sel);
1094 gtk_tree_selection_select_iter(sel, &tree_iter);
1095 gtk_tree_view_set_cursor (header_view, current_path, NULL, FALSE);
1096 g_object_set(header_view, "can-focus", TRUE, NULL);
1097 gtk_tree_row_reference_free (priv->autoselect_reference);
1098 priv->autoselect_reference = gtk_tree_row_reference_new (model, current_path);
1100 gtk_tree_path_free (current_path);
1101 gtk_tree_path_free (last_path);
1111 modest_header_view_get_folder (ModestHeaderView *self)
1113 ModestHeaderViewPrivate *priv;
1115 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), NULL);
1117 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1120 g_object_ref (priv->folder);
1122 return priv->folder;
1126 set_folder_intern_get_headers_async_cb (TnyFolder *folder,
1132 ModestHeaderView *self;
1133 ModestHeaderViewPrivate *priv;
1135 g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data));
1137 self = MODEST_HEADER_VIEW (user_data);
1138 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1140 if (cancelled || err)
1143 /* Add IDLE observer (monitor) and another folder observer for
1144 new messages (self) */
1145 g_mutex_lock (priv->observers_lock);
1146 if (priv->monitor) {
1147 tny_folder_monitor_stop (priv->monitor);
1148 g_object_unref (G_OBJECT (priv->monitor));
1150 priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder));
1151 tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers));
1152 tny_folder_monitor_start (priv->monitor);
1153 g_mutex_unlock (priv->observers_lock);
1157 modest_header_view_set_folder_intern (ModestHeaderView *self,
1163 ModestHeaderViewPrivate *priv;
1164 GList *cols, *cursor;
1165 GtkTreeModel *filter_model, *sortable;
1167 GtkSortType sort_type;
1169 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1171 headers = TNY_LIST (tny_gtk_header_list_model_new ());
1173 /* Start the monitor in the callback of the
1174 tny_gtk_header_list_model_set_folder call. It's crucial to
1175 do it there and not just after the call because we want the
1176 monitor to observe only the headers returned by the
1177 tny_folder_get_headers_async call that it's inside the
1178 tny_gtk_header_list_model_set_folder call. This way the
1179 monitor infrastructure could successfully cope with
1180 duplicates. For example if a tny_folder_add_msg_async is
1181 happening while tny_gtk_header_list_model_set_folder is
1182 invoked, then the first call could add a header that will
1183 be added again by tny_gtk_header_list_model_set_folder, so
1184 we'd end up with duplicate headers. sergio */
1185 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1187 set_folder_intern_get_headers_async_cb,
1190 /* Init filter_row function to examine empty status */
1191 priv->status = HEADER_VIEW_INIT;
1193 /* Create sortable model */
1194 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1195 g_object_unref (headers);
1197 /* Create a tree model filter to hide and show rows for cut operations */
1198 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1199 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1200 filter_row, self, NULL);
1201 g_object_unref (sortable);
1203 /* install our special sorting functions */
1204 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1206 /* Restore sort column id */
1208 type = modest_tny_folder_guess_folder_type (folder);
1209 if (type == TNY_FOLDER_TYPE_INVALID)
1210 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1212 sort_colid = modest_header_view_get_sort_column_id (self, type);
1213 sort_type = modest_header_view_get_sort_type (self, type);
1214 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1217 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1218 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1219 (GtkTreeIterCompareFunc) cmp_rows,
1221 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1222 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1223 (GtkTreeIterCompareFunc) cmp_subject_rows,
1228 gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1229 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1230 g_object_unref (filter_model);
1237 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1239 GtkSortType sort_type)
1241 ModestHeaderViewPrivate *priv = NULL;
1242 GtkTreeModel *sortable = NULL, *filter_model = NULL;
1245 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1246 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1248 /* Get model and private data */
1249 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1250 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1251 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1252 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1255 /* Sort tree model */
1256 type = modest_tny_folder_guess_folder_type (priv->folder);
1257 if (type == TNY_FOLDER_TYPE_INVALID)
1258 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1260 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1263 /* Store new sort parameters */
1264 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1269 modest_header_view_set_sort_params (ModestHeaderView *self,
1271 GtkSortType sort_type,
1274 ModestHeaderViewPrivate *priv;
1275 ModestHeaderViewStyle style;
1277 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1278 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1279 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1281 style = modest_header_view_get_style (self);
1282 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1284 priv->sort_colid[style][type] = sort_colid;
1285 priv->sort_type[style][type] = sort_type;
1289 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1292 ModestHeaderViewPrivate *priv;
1293 ModestHeaderViewStyle style;
1295 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1296 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1298 style = modest_header_view_get_style (self);
1299 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1301 return priv->sort_colid[style][type];
1305 modest_header_view_get_sort_type (ModestHeaderView *self,
1308 ModestHeaderViewPrivate *priv;
1309 ModestHeaderViewStyle style;
1311 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1312 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1314 style = modest_header_view_get_style (self);
1315 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1317 return priv->sort_type[style][type];
1321 ModestHeaderView *header_view;
1322 RefreshAsyncUserCallback cb;
1327 folder_refreshed_cb (ModestMailOperation *mail_op,
1331 ModestHeaderViewPrivate *priv;
1332 SetFolderHelper *info;
1334 info = (SetFolderHelper*) user_data;
1336 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1340 info->cb (mail_op, folder, info->user_data);
1342 /* Start the folder count changes observer. We do not need it
1343 before the refresh. Note that the monitor could still be
1344 called for this refresh but now we know that the callback
1345 was previously called */
1346 g_mutex_lock (priv->observers_lock);
1347 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1348 g_mutex_unlock (priv->observers_lock);
1350 /* Notify the observers that the update is over */
1351 g_signal_emit (G_OBJECT (info->header_view),
1352 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1354 /* Allow filtering notifications from now on if the current
1355 folder is still the same (if not then the user has selected
1356 another one to refresh, we should wait until that refresh
1358 if (priv->folder == folder)
1359 priv->notify_status = TRUE;
1362 g_object_unref (info->header_view);
1367 refresh_folder_error_handler (ModestMailOperation *mail_op,
1370 const GError *error = modest_mail_operation_get_error (mail_op);
1372 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1373 error->code == TNY_IO_ERROR_WRITE ||
1374 error->code == TNY_IO_ERROR_READ) {
1375 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1376 /* If the mail op has been cancelled then it's not an error: don't show any message */
1377 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1378 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1379 modest_platform_information_banner (NULL, NULL, msg);
1386 modest_header_view_set_folder (ModestHeaderView *self,
1389 ModestWindow *progress_window,
1390 RefreshAsyncUserCallback callback,
1393 ModestHeaderViewPrivate *priv;
1395 g_return_if_fail (self);
1397 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1400 if (priv->status_timeout) {
1401 g_source_remove (priv->status_timeout);
1402 priv->status_timeout = 0;
1405 g_mutex_lock (priv->observers_lock);
1406 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1407 g_object_unref (priv->folder);
1408 priv->folder = NULL;
1409 g_mutex_unlock (priv->observers_lock);
1413 GtkTreeSelection *selection;
1414 SetFolderHelper *info;
1415 ModestMailOperation *mail_op = NULL;
1417 /* Set folder in the model */
1418 modest_header_view_set_folder_intern (self, folder, refresh);
1420 /* Pick my reference. Nothing to do with the mail operation */
1421 priv->folder = g_object_ref (folder);
1423 /* Do not notify about filterings until the refresh finishes */
1424 priv->notify_status = FALSE;
1426 /* Clear the selection if exists */
1427 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1428 gtk_tree_selection_unselect_all(selection);
1429 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1431 /* Notify the observers that the update begins */
1432 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1435 /* create the helper */
1436 info = g_malloc0 (sizeof (SetFolderHelper));
1437 info->header_view = g_object_ref (self);
1438 info->cb = callback;
1439 info->user_data = user_data;
1441 /* Create the mail operation (source will be the parent widget) */
1442 if (progress_window)
1443 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1444 refresh_folder_error_handler,
1447 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1450 /* Refresh the folder asynchronously */
1451 modest_mail_operation_refresh_folder (mail_op,
1453 folder_refreshed_cb,
1456 folder_refreshed_cb (mail_op, folder, info);
1460 g_object_unref (mail_op);
1462 g_mutex_lock (priv->observers_lock);
1464 if (priv->monitor) {
1465 tny_folder_monitor_stop (priv->monitor);
1466 g_object_unref (G_OBJECT (priv->monitor));
1467 priv->monitor = NULL;
1470 if (priv->autoselect_reference) {
1471 gtk_tree_row_reference_free (priv->autoselect_reference);
1472 priv->autoselect_reference = NULL;
1475 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1477 modest_header_view_notify_observers(self, NULL, NULL);
1479 g_mutex_unlock (priv->observers_lock);
1481 /* Notify the observers that the update is over */
1482 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1488 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1489 GtkTreeViewColumn *column, gpointer userdata)
1491 ModestHeaderView *self = NULL;
1493 GtkTreeModel *model = NULL;
1494 TnyHeader *header = NULL;
1495 TnyHeaderFlags flags;
1497 self = MODEST_HEADER_VIEW (treeview);
1499 model = gtk_tree_view_get_model (treeview);
1500 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1503 /* get the first selected item */
1504 gtk_tree_model_get (model, &iter,
1505 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1506 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1509 /* Dont open DELETED messages */
1510 if (flags & TNY_HEADER_FLAG_DELETED) {
1513 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1514 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1515 modest_platform_information_banner (NULL, NULL, msg);
1521 g_signal_emit (G_OBJECT(self),
1522 signals[HEADER_ACTIVATED_SIGNAL],
1528 g_object_unref (G_OBJECT (header));
1533 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1535 GtkTreeModel *model;
1536 TnyHeader *header = NULL;
1537 GtkTreePath *path = NULL;
1539 ModestHeaderView *self;
1540 GList *selected = NULL;
1542 g_return_if_fail (sel);
1543 g_return_if_fail (user_data);
1545 self = MODEST_HEADER_VIEW (user_data);
1547 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1548 if (selected != NULL)
1549 path = (GtkTreePath *) selected->data;
1550 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1551 return; /* msg was _un_selected */
1553 gtk_tree_model_get (model, &iter,
1554 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1558 g_signal_emit (G_OBJECT(self),
1559 signals[HEADER_SELECTED_SIGNAL],
1562 g_object_unref (G_OBJECT (header));
1564 /* free all items in 'selected' */
1565 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1566 g_list_free (selected);
1570 /* PROTECTED method. It's useful when we want to force a given
1571 selection to reload a msg. For example if we have selected a header
1572 in offline mode, when Modest become online, we want to reload the
1573 message automatically without an user click over the header */
1575 _modest_header_view_change_selection (GtkTreeSelection *selection,
1578 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1579 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1581 on_selection_changed (selection, user_data);
1585 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1592 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1596 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1600 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1608 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1616 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1617 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1621 case TNY_HEADER_FLAG_ATTACHMENTS:
1623 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1624 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1625 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1626 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1628 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1629 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1631 return cmp ? cmp : t1 - t2;
1633 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1634 TnyHeader *header1 = NULL, *header2 = NULL;
1636 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1637 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1638 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1639 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1641 /* This is for making priority values respect the intuitive sort relationship
1642 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1644 if (header1 && header2) {
1645 cmp = compare_priorities (tny_header_get_priority (header1),
1646 tny_header_get_priority (header2));
1647 g_object_unref (header1);
1648 g_object_unref (header2);
1650 return cmp ? cmp : t1 - t2;
1656 return &iter1 - &iter2; /* oughhhh */
1661 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1668 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1670 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1671 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1672 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1673 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1675 /* Do not use the prefixes for sorting. Consume all the blank
1676 spaces for sorting */
1677 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1678 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1681 /* If they're equal based on subject without prefix then just
1682 sort them by length. This will show messages like this.
1689 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1696 /* Drag and drop stuff */
1698 drag_data_get_cb (GtkWidget *widget,
1699 GdkDragContext *context,
1700 GtkSelectionData *selection_data,
1705 ModestHeaderView *self = NULL;
1706 ModestHeaderViewPrivate *priv = NULL;
1708 self = MODEST_HEADER_VIEW (widget);
1709 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1711 /* Set the data. Do not use the current selection because it
1712 could be different than the selection at the beginning of
1714 modest_dnd_selection_data_set_paths (selection_data,
1715 priv->drag_begin_cached_selected_rows);
1719 * We're caching the selected rows at the beginning because the
1720 * selection could change between drag-begin and drag-data-get, for
1721 * example if we have a set of rows already selected, and then we
1722 * click in one of them (without SHIFT key pressed) and begin a drag,
1723 * the selection at that moment contains all the selected lines, but
1724 * after dropping the selection, the release event provokes that only
1725 * the row used to begin the drag is selected, so at the end the
1726 * drag&drop affects only one rows instead of all the selected ones.
1730 drag_begin_cb (GtkWidget *widget,
1731 GdkDragContext *context,
1734 ModestHeaderView *self = NULL;
1735 ModestHeaderViewPrivate *priv = NULL;
1736 GtkTreeSelection *selection;
1738 self = MODEST_HEADER_VIEW (widget);
1739 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1741 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1742 priv->drag_begin_cached_selected_rows =
1743 gtk_tree_selection_get_selected_rows (selection, NULL);
1747 * We use the drag-end signal to clear the cached selection, we use
1748 * this because this allways happens, whether or not the d&d was a
1752 drag_end_cb (GtkWidget *widget,
1756 ModestHeaderView *self = NULL;
1757 ModestHeaderViewPrivate *priv = NULL;
1759 self = MODEST_HEADER_VIEW (widget);
1760 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1762 /* Free cached data */
1763 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1764 g_list_free (priv->drag_begin_cached_selected_rows);
1765 priv->drag_begin_cached_selected_rows = NULL;
1768 /* Header view drag types */
1769 const GtkTargetEntry header_view_drag_types[] = {
1770 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1774 enable_drag_and_drop (GtkWidget *self)
1776 #ifdef MODEST_TOOLKIT_HILDON2
1779 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1780 header_view_drag_types,
1781 G_N_ELEMENTS (header_view_drag_types),
1782 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1786 disable_drag_and_drop (GtkWidget *self)
1788 #ifdef MODEST_TOOLKIT_HILDON2
1791 gtk_drag_source_unset (self);
1795 setup_drag_and_drop (GtkWidget *self)
1797 #ifdef MODEST_TOOLKIT_HILDON2
1800 enable_drag_and_drop(self);
1801 g_signal_connect(G_OBJECT (self), "drag_data_get",
1802 G_CALLBACK(drag_data_get_cb), NULL);
1804 g_signal_connect(G_OBJECT (self), "drag_begin",
1805 G_CALLBACK(drag_begin_cb), NULL);
1807 g_signal_connect(G_OBJECT (self), "drag_end",
1808 G_CALLBACK(drag_end_cb), NULL);
1811 static GtkTreePath *
1812 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1814 GtkTreePath *path = NULL;
1815 GtkTreeSelection *sel = NULL;
1818 sel = gtk_tree_view_get_selection(self);
1819 rows = gtk_tree_selection_get_selected_rows (sel, model);
1821 if ((rows == NULL) || (g_list_length(rows) != 1))
1824 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1829 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1835 #ifndef MODEST_TOOLKIT_HILDON2
1837 * This function moves the tree view scroll to the current selected
1838 * row when the widget grabs the focus
1841 on_focus_in (GtkWidget *self,
1842 GdkEventFocus *event,
1845 GtkTreeSelection *selection;
1846 GtkTreeModel *model;
1847 GList *selected = NULL;
1848 GtkTreePath *selected_path = NULL;
1850 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1854 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1855 /* If none selected yet, pick the first one */
1856 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1860 /* Return if the model is empty */
1861 if (!gtk_tree_model_get_iter_first (model, &iter))
1864 path = gtk_tree_model_get_path (model, &iter);
1865 gtk_tree_selection_select_path (selection, path);
1866 gtk_tree_path_free (path);
1869 /* Need to get the all the rows because is selection multiple */
1870 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1871 if (selected == NULL) return FALSE;
1872 selected_path = (GtkTreePath *) selected->data;
1875 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1876 g_list_free (selected);
1882 on_focus_out (GtkWidget *self,
1883 GdkEventFocus *event,
1887 if (!gtk_widget_is_focus (self)) {
1888 GtkTreeSelection *selection = NULL;
1889 GList *selected_rows = NULL;
1890 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1891 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1892 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1893 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1894 gtk_tree_selection_unselect_all (selection);
1895 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1896 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1897 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1898 g_list_free (selected_rows);
1906 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1908 enable_drag_and_drop(self);
1913 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1915 GtkTreeSelection *selection = NULL;
1916 GtkTreePath *path = NULL;
1917 gboolean already_selected = FALSE, already_opened = FALSE;
1918 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1920 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1922 GtkTreeModel *model;
1924 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1925 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1927 /* Get header from model */
1928 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1929 if (gtk_tree_model_get_iter (model, &iter, path)) {
1930 GValue value = {0,};
1933 gtk_tree_model_get_value (model, &iter,
1934 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1936 header = (TnyHeader *) g_value_get_object (&value);
1937 if (TNY_IS_HEADER (header)) {
1938 status = modest_tny_all_send_queues_get_msg_status (header);
1939 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1942 g_value_unset (&value);
1946 /* Enable drag and drop only if the user clicks on a row that
1947 it's already selected. If not, let him select items using
1948 the pointer. If the message is in an OUTBOX and in sending
1949 status disable drag and drop as well */
1950 if (!already_selected ||
1951 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1953 disable_drag_and_drop(self);
1956 gtk_tree_path_free(path);
1958 /* If it's already opened then do not let the button-press
1959 event go on because it'll perform a message open because
1960 we're clicking on to an already selected header */
1965 folder_monitor_update (TnyFolderObserver *self,
1966 TnyFolderChange *change)
1968 ModestHeaderViewPrivate *priv = NULL;
1969 TnyFolderChangeChanged changed;
1970 TnyFolder *folder = NULL;
1972 changed = tny_folder_change_get_changed (change);
1974 /* Do not notify the observers if the folder of the header
1975 view has changed before this call to the observer
1977 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1978 folder = tny_folder_change_get_folder (change);
1979 if (folder != priv->folder)
1982 MODEST_DEBUG_BLOCK (
1983 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1984 g_print ("ADDED %d/%d (r/t) \n",
1985 tny_folder_change_get_new_unread_count (change),
1986 tny_folder_change_get_new_all_count (change));
1987 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1988 g_print ("ALL COUNT %d\n",
1989 tny_folder_change_get_new_all_count (change));
1990 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1991 g_print ("UNREAD COUNT %d\n",
1992 tny_folder_change_get_new_unread_count (change));
1993 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1994 g_print ("EXPUNGED %d/%d (r/t) \n",
1995 tny_folder_change_get_new_unread_count (change),
1996 tny_folder_change_get_new_all_count (change));
1997 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1998 g_print ("FOLDER RENAME\n");
1999 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2000 g_print ("MSG RECEIVED %d/%d (r/t) \n",
2001 tny_folder_change_get_new_unread_count (change),
2002 tny_folder_change_get_new_all_count (change));
2003 g_print ("---------------------------------------------------\n");
2006 /* Check folder count */
2007 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2008 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2010 g_mutex_lock (priv->observers_lock);
2012 /* Emit signal to evaluate how headers changes affects
2013 to the window view */
2014 g_signal_emit (G_OBJECT(self),
2015 signals[MSG_COUNT_CHANGED_SIGNAL],
2018 /* Added or removed headers, so data stored on cliboard are invalid */
2019 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2020 modest_email_clipboard_clear (priv->clipboard);
2022 g_mutex_unlock (priv->observers_lock);
2028 g_object_unref (folder);
2032 modest_header_view_is_empty (ModestHeaderView *self)
2034 ModestHeaderViewPrivate *priv;
2036 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2038 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2040 return priv->status == HEADER_VIEW_EMPTY;
2044 modest_header_view_clear (ModestHeaderView *self)
2046 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2048 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2052 modest_header_view_copy_selection (ModestHeaderView *header_view)
2054 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2056 /* Copy selection */
2057 _clipboard_set_selected_data (header_view, FALSE);
2061 modest_header_view_cut_selection (ModestHeaderView *header_view)
2063 ModestHeaderViewPrivate *priv = NULL;
2064 const gchar **hidding = NULL;
2065 guint i, n_selected;
2067 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2069 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2071 /* Copy selection */
2072 _clipboard_set_selected_data (header_view, TRUE);
2074 /* Get hidding ids */
2075 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2077 /* Clear hidding array created by previous cut operation */
2078 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2080 /* Copy hidding array */
2081 priv->n_selected = n_selected;
2082 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2083 for (i=0; i < n_selected; i++)
2084 priv->hidding_ids[i] = g_strdup(hidding[i]);
2086 /* Hide cut headers */
2087 modest_header_view_refilter (header_view);
2094 _clipboard_set_selected_data (ModestHeaderView *header_view,
2097 ModestHeaderViewPrivate *priv = NULL;
2098 TnyList *headers = NULL;
2100 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2101 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2103 /* Set selected data on clipboard */
2104 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2105 headers = modest_header_view_get_selected_headers (header_view);
2106 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2109 g_object_unref (headers);
2113 ModestHeaderView *self;
2118 notify_filter_change (gpointer data)
2120 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2122 g_signal_emit (info->self,
2123 signals[MSG_COUNT_CHANGED_SIGNAL],
2124 0, info->folder, NULL);
2130 notify_filter_change_destroy (gpointer data)
2132 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2133 ModestHeaderViewPrivate *priv;
2135 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2136 priv->status_timeout = 0;
2138 g_object_unref (info->self);
2139 g_object_unref (info->folder);
2140 g_slice_free (NotifyFilterInfo, info);
2144 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2146 /* For the moment we only need to filter outbox */
2147 return priv->is_outbox;
2151 header_match_string (TnyHeader *header, gchar **words)
2153 gchar *subject_fold;
2159 gchar **current_word;
2162 subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2163 if (subject_fold == NULL) {
2165 subject = tny_header_dup_subject (header);
2166 if (subject != NULL) {
2167 subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2168 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2169 subject_fold, (GDestroyNotify) g_free);
2174 from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2175 if (from_fold == NULL) {
2177 from = tny_header_dup_from (header);
2179 from_fold = from?g_utf8_casefold (from, -1):NULL;
2180 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2181 from_fold, (GDestroyNotify) g_free);
2186 to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2187 if (to_fold == NULL) {
2189 to = tny_header_dup_to (header);
2191 to_fold = to?g_utf8_casefold (to, -1):NULL;
2192 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2193 to_fold, (GDestroyNotify) g_free);
2198 cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2199 if (cc_fold == NULL) {
2201 cc = tny_header_dup_cc (header);
2203 cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2204 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2205 cc_fold, (GDestroyNotify) g_free);
2210 bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2211 if (bcc_fold == NULL) {
2213 bcc = tny_header_dup_bcc (header);
2215 bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2216 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2217 bcc_fold, (GDestroyNotify) g_free);
2224 for (current_word = words; *current_word != NULL; current_word++) {
2226 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2227 || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2228 || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2229 || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2230 || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2242 filter_row (GtkTreeModel *model,
2246 ModestHeaderViewPrivate *priv = NULL;
2247 TnyHeaderFlags flags;
2248 TnyHeader *header = NULL;
2251 gboolean visible = TRUE;
2252 gboolean found = FALSE;
2253 GValue value = {0,};
2254 HeaderViewStatus old_status;
2256 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2257 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2259 /* Get header from model */
2260 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
2261 flags = (TnyHeaderFlags) g_value_get_int (&value);
2262 g_value_unset (&value);
2263 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2264 header = (TnyHeader *) g_value_get_object (&value);
2265 g_value_unset (&value);
2267 /* Get message id from header (ensure is a valid id) */
2273 /* Hide deleted and mark as deleted heders */
2274 if (flags & TNY_HEADER_FLAG_DELETED ||
2275 flags & TNY_HEADER_FLAG_EXPUNGED) {
2280 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2281 if (current_folder_needs_filtering (priv) &&
2282 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2288 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2289 if (current_folder_needs_filtering (priv) &&
2290 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2296 if (visible && priv->filter_string) {
2297 if (!header_match_string (header, priv->filter_string_splitted)) {
2301 if (priv->filter_date_range) {
2302 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2303 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2310 /* If no data on clipboard, return always TRUE */
2311 if (modest_email_clipboard_cleared(priv->clipboard)) {
2317 if (priv->hidding_ids != NULL) {
2318 id = tny_header_dup_message_id (header);
2319 for (i=0; i < priv->n_selected && !found; i++)
2320 if (priv->hidding_ids[i] != NULL && id != NULL)
2321 found = (!strcmp (priv->hidding_ids[i], id));
2328 old_status = priv->status;
2329 priv->status = ((gboolean) priv->status) && !visible;
2330 if ((priv->notify_status) && (priv->status != old_status)) {
2331 if (priv->status_timeout)
2332 g_source_remove (priv->status_timeout);
2335 NotifyFilterInfo *info;
2337 info = g_slice_new0 (NotifyFilterInfo);
2338 info->self = g_object_ref (G_OBJECT (user_data));
2340 info->folder = tny_header_get_folder (header);
2341 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2342 notify_filter_change,
2344 notify_filter_change_destroy);
2352 _clear_hidding_filter (ModestHeaderView *header_view)
2354 ModestHeaderViewPrivate *priv = NULL;
2357 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2358 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2360 if (priv->hidding_ids != NULL) {
2361 for (i=0; i < priv->n_selected; i++)
2362 g_free (priv->hidding_ids[i]);
2363 g_free(priv->hidding_ids);
2368 modest_header_view_refilter (ModestHeaderView *header_view)
2370 GtkTreeModel *filter_model = NULL;
2371 ModestHeaderViewPrivate *priv = NULL;
2373 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2374 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2376 /* Hide cut headers */
2377 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2378 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2379 priv->status = HEADER_VIEW_INIT;
2380 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2385 * Called when an account is removed. If I'm showing a folder of the
2386 * account that has been removed then clear the view
2389 on_account_removed (TnyAccountStore *self,
2390 TnyAccount *account,
2393 ModestHeaderViewPrivate *priv = NULL;
2395 /* Ignore changes in transport accounts */
2396 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2399 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2402 TnyAccount *my_account;
2404 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2405 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2406 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2407 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2409 my_account = tny_folder_get_account (priv->folder);
2413 if (my_account == account)
2414 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2415 g_object_unref (my_account);
2421 modest_header_view_add_observer(ModestHeaderView *header_view,
2422 ModestHeaderViewObserver *observer)
2424 ModestHeaderViewPrivate *priv;
2426 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2427 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2429 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2431 g_mutex_lock(priv->observer_list_lock);
2432 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2433 g_mutex_unlock(priv->observer_list_lock);
2437 modest_header_view_remove_observer(ModestHeaderView *header_view,
2438 ModestHeaderViewObserver *observer)
2440 ModestHeaderViewPrivate *priv;
2442 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2443 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2445 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2447 g_mutex_lock(priv->observer_list_lock);
2448 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2449 g_mutex_unlock(priv->observer_list_lock);
2453 modest_header_view_notify_observers(ModestHeaderView *header_view,
2454 GtkTreeModel *model,
2455 const gchar *tny_folder_id)
2457 ModestHeaderViewPrivate *priv = NULL;
2459 ModestHeaderViewObserver *observer;
2462 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2464 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2466 g_mutex_lock(priv->observer_list_lock);
2467 iter = priv->observer_list;
2468 while(iter != NULL){
2469 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2470 modest_header_view_observer_update(observer, model,
2472 iter = g_slist_next(iter);
2474 g_mutex_unlock(priv->observer_list_lock);
2478 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2480 ModestHeaderViewPrivate *priv = NULL;
2482 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2483 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2487 modest_header_view_set_filter (ModestHeaderView *self,
2488 ModestHeaderViewFilter filter)
2490 ModestHeaderViewPrivate *priv;
2492 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2493 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2495 priv->filter |= filter;
2497 if (current_folder_needs_filtering (priv))
2498 modest_header_view_refilter (self);
2502 modest_header_view_unset_filter (ModestHeaderView *self,
2503 ModestHeaderViewFilter filter)
2505 ModestHeaderViewPrivate *priv;
2507 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2508 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2510 priv->filter &= ~filter;
2512 if (current_folder_needs_filtering (priv))
2513 modest_header_view_refilter (self);
2517 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2519 if (strcmp ("style", spec->name) == 0) {
2520 update_style (MODEST_HEADER_VIEW (obj));
2521 gtk_widget_queue_draw (GTK_WIDGET (obj));
2526 update_style (ModestHeaderView *self)
2528 ModestHeaderViewPrivate *priv;
2529 GdkColor style_color;
2530 GdkColor style_active_color;
2531 PangoAttrList *attr_list;
2533 PangoAttribute *attr;
2535 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2536 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2540 attr_list = pango_attr_list_new ();
2541 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2542 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2544 priv->secondary_color = style_color;
2545 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2546 pango_attr_list_insert (attr_list, attr);
2549 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2551 "SmallSystemFont", NULL,
2554 attr = pango_attr_font_desc_new (pango_font_description_copy
2555 (style->font_desc));
2556 pango_attr_list_insert (attr_list, attr);
2558 g_object_set (G_OBJECT (priv->renderer_address),
2559 "foreground-gdk", &(priv->secondary_color),
2560 "foreground-set", TRUE,
2561 "attributes", attr_list,
2563 g_object_set (G_OBJECT (priv->renderer_date_status),
2564 "foreground-gdk", &(priv->secondary_color),
2565 "foreground-set", TRUE,
2566 "attributes", attr_list,
2568 pango_attr_list_unref (attr_list);
2570 g_object_set (G_OBJECT (priv->renderer_address),
2571 "foreground-gdk", &(priv->secondary_color),
2572 "foreground-set", TRUE,
2573 "scale", PANGO_SCALE_SMALL,
2576 g_object_set (G_OBJECT (priv->renderer_date_status),
2577 "foreground-gdk", &(priv->secondary_color),
2578 "foreground-set", TRUE,
2579 "scale", PANGO_SCALE_SMALL,
2584 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2585 priv->active_color = style_active_color;
2586 #ifdef MODEST_TOOLKIT_HILDON2
2587 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2588 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2591 #ifdef MODEST_TOOLKIT_HILDON2
2592 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2598 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2603 GtkTreeModel *tree_model;
2608 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2616 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2617 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2621 gtk_tree_model_get (tree_model, &iter,
2622 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2629 parse_date_side (const gchar *string, time_t *date_side)
2635 gboolean result = FALSE;
2637 if (string && string[0] == '\0') {
2642 casefold = g_utf8_casefold (string, -1);
2643 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2644 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2645 date = g_date_new ();
2647 if (g_utf8_collate (casefold, today) == 0) {
2648 *date_side = time (NULL);
2653 if (g_utf8_collate (casefold, yesterday) == 0) {
2654 *date_side = time (NULL) - 24*60*60;
2659 g_date_set_parse (date, string);
2660 if (g_date_valid (date)) {
2662 g_date_to_struct_tm (date, &tm);
2663 *date_side = mktime (&tm);
2678 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2683 parts = g_strsplit (string, "..", 2);
2686 if (g_strv_length (parts) != 2) {
2693 if (!parse_date_side (parts[0], date_range_start)) {
2698 if (parse_date_side (parts[1], date_range_end)) {
2699 if (*date_range_end == 0) {
2700 *date_range_end = (time_t) -1;
2702 *date_range_end += (24*60*60 - 1);
2715 modest_header_view_set_filter_string (ModestHeaderView *self,
2716 const gchar *filter_string)
2718 ModestHeaderViewPrivate *priv;
2720 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2721 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2723 if (priv->filter_string)
2724 g_free (priv->filter_string);
2726 priv->filter_string = g_strdup (filter_string);
2727 priv->filter_date_range = FALSE;
2729 if (priv->filter_string_splitted) {
2730 g_strfreev (priv->filter_string_splitted);
2731 priv->filter_string_splitted = NULL;
2734 if (priv->filter_string) {
2735 gchar **split, **current, **current_target;
2737 split = g_strsplit (priv->filter_string, " ", 0);
2739 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2740 current_target = priv->filter_string_splitted;
2741 for (current = split; *current != 0; current ++) {
2742 gboolean has_date_range = FALSE;;
2743 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2744 time_t range_start, range_end;
2745 /* It contains .. but it's not ".." so it may be a date range */
2746 if (parse_date_range (*current, &range_start, &range_end)) {
2747 priv->filter_date_range = TRUE;
2748 has_date_range = TRUE;
2749 priv->date_range_start = range_start;
2750 priv->date_range_end = range_end;
2753 if (!has_date_range) {
2754 *current_target = g_utf8_casefold (*current, -1);
2758 *current_target = '\0';
2761 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2764 #ifdef MODEST_TOOLKIT_HILDON2
2766 on_live_search_refilter (HildonLiveSearch *livesearch,
2767 ModestHeaderView *self)
2769 const gchar *needle;
2771 needle = hildon_live_search_get_text (livesearch);
2772 if (needle && needle[0] != '\0') {
2773 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2775 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2778 priv->live_search_timeout = 0;
2784 on_live_search_refilter (HildonLiveSearch *livesearch,
2785 ModestHeaderView *self)
2787 ModestHeaderViewPrivate *priv;
2788 GtkTreeModel *model, *sortable, *filter;
2790 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2792 if (priv->live_search_timeout > 0) {
2793 g_source_remove (priv->live_search_timeout);
2794 priv->live_search_timeout = 0;
2798 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2799 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2800 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2801 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2802 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2806 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2807 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2809 on_live_search_timeout (self);
2816 modest_header_view_setup_live_search (ModestHeaderView *self)
2818 ModestHeaderViewPrivate *priv;
2820 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2821 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2822 priv->live_search = hildon_live_search_new ();
2824 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2826 return priv->live_search;