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 ());
1172 tny_gtk_header_list_model_set_update_in_batches (TNY_GTK_HEADER_LIST_MODEL (headers), 300);
1174 /* Start the monitor in the callback of the
1175 tny_gtk_header_list_model_set_folder call. It's crucial to
1176 do it there and not just after the call because we want the
1177 monitor to observe only the headers returned by the
1178 tny_folder_get_headers_async call that it's inside the
1179 tny_gtk_header_list_model_set_folder call. This way the
1180 monitor infrastructure could successfully cope with
1181 duplicates. For example if a tny_folder_add_msg_async is
1182 happening while tny_gtk_header_list_model_set_folder is
1183 invoked, then the first call could add a header that will
1184 be added again by tny_gtk_header_list_model_set_folder, so
1185 we'd end up with duplicate headers. sergio */
1186 tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers),
1188 set_folder_intern_get_headers_async_cb,
1191 /* Init filter_row function to examine empty status */
1192 priv->status = HEADER_VIEW_INIT;
1194 /* Create sortable model */
1195 sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (headers));
1196 g_object_unref (headers);
1198 /* Create a tree model filter to hide and show rows for cut operations */
1199 filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (sortable), NULL);
1200 gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model),
1201 filter_row, self, NULL);
1202 g_object_unref (sortable);
1204 /* install our special sorting functions */
1205 cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self));
1207 /* Restore sort column id */
1209 type = modest_tny_folder_guess_folder_type (folder);
1210 if (type == TNY_FOLDER_TYPE_INVALID)
1211 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1213 sort_colid = modest_header_view_get_sort_column_id (self, type);
1214 sort_type = modest_header_view_get_sort_type (self, type);
1215 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1218 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1219 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN,
1220 (GtkTreeIterCompareFunc) cmp_rows,
1222 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (sortable),
1223 TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN,
1224 (GtkTreeIterCompareFunc) cmp_subject_rows,
1229 gtk_tree_view_set_model (GTK_TREE_VIEW (self), filter_model);
1230 modest_header_view_notify_observers (self, sortable, tny_folder_get_id (folder));
1231 g_object_unref (filter_model);
1238 modest_header_view_sort_by_column_id (ModestHeaderView *self,
1240 GtkSortType sort_type)
1242 ModestHeaderViewPrivate *priv = NULL;
1243 GtkTreeModel *sortable = NULL, *filter_model = NULL;
1246 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1247 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1249 /* Get model and private data */
1250 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
1251 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1252 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
1253 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
1256 /* Sort tree model */
1257 type = modest_tny_folder_guess_folder_type (priv->folder);
1258 if (type == TNY_FOLDER_TYPE_INVALID)
1259 g_warning ("%s: BUG: TNY_FOLDER_TYPE_INVALID", __FUNCTION__);
1261 gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (sortable),
1264 /* Store new sort parameters */
1265 modest_header_view_set_sort_params (self, sort_colid, sort_type, type);
1270 modest_header_view_set_sort_params (ModestHeaderView *self,
1272 GtkSortType sort_type,
1275 ModestHeaderViewPrivate *priv;
1276 ModestHeaderViewStyle style;
1278 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
1279 g_return_if_fail (sort_type == GTK_SORT_ASCENDING || sort_type == GTK_SORT_DESCENDING);
1280 g_return_if_fail (type != TNY_FOLDER_TYPE_INVALID);
1282 style = modest_header_view_get_style (self);
1283 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1285 priv->sort_colid[style][type] = sort_colid;
1286 priv->sort_type[style][type] = sort_type;
1290 modest_header_view_get_sort_column_id (ModestHeaderView *self,
1293 ModestHeaderViewPrivate *priv;
1294 ModestHeaderViewStyle style;
1296 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), 0);
1297 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, 0);
1299 style = modest_header_view_get_style (self);
1300 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1302 return priv->sort_colid[style][type];
1306 modest_header_view_get_sort_type (ModestHeaderView *self,
1309 ModestHeaderViewPrivate *priv;
1310 ModestHeaderViewStyle style;
1312 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), GTK_SORT_DESCENDING);
1313 g_return_val_if_fail (type != TNY_FOLDER_TYPE_INVALID, GTK_SORT_DESCENDING);
1315 style = modest_header_view_get_style (self);
1316 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1318 return priv->sort_type[style][type];
1322 ModestHeaderView *header_view;
1323 RefreshAsyncUserCallback cb;
1328 folder_refreshed_cb (ModestMailOperation *mail_op,
1332 ModestHeaderViewPrivate *priv;
1333 SetFolderHelper *info;
1335 info = (SetFolderHelper*) user_data;
1337 priv = MODEST_HEADER_VIEW_GET_PRIVATE(info->header_view);
1341 info->cb (mail_op, folder, info->user_data);
1343 /* Start the folder count changes observer. We do not need it
1344 before the refresh. Note that the monitor could still be
1345 called for this refresh but now we know that the callback
1346 was previously called */
1347 g_mutex_lock (priv->observers_lock);
1348 tny_folder_add_observer (folder, TNY_FOLDER_OBSERVER (info->header_view));
1349 g_mutex_unlock (priv->observers_lock);
1351 /* Notify the observers that the update is over */
1352 g_signal_emit (G_OBJECT (info->header_view),
1353 signals[UPDATING_MSG_LIST_SIGNAL], 0, FALSE, NULL);
1355 /* Allow filtering notifications from now on if the current
1356 folder is still the same (if not then the user has selected
1357 another one to refresh, we should wait until that refresh
1359 if (priv->folder == folder)
1360 priv->notify_status = TRUE;
1363 g_object_unref (info->header_view);
1368 refresh_folder_error_handler (ModestMailOperation *mail_op,
1371 const GError *error = modest_mail_operation_get_error (mail_op);
1373 if (error->code == TNY_SYSTEM_ERROR_MEMORY ||
1374 error->code == TNY_IO_ERROR_WRITE ||
1375 error->code == TNY_IO_ERROR_READ) {
1376 ModestMailOperationStatus st = modest_mail_operation_get_status (mail_op);
1377 /* If the mail op has been cancelled then it's not an error: don't show any message */
1378 if (st != MODEST_MAIL_OPERATION_STATUS_CANCELED) {
1379 gchar *msg = g_strdup_printf (_KR("cerm_device_memory_full"), "");
1380 modest_platform_information_banner (NULL, NULL, msg);
1387 modest_header_view_set_folder (ModestHeaderView *self,
1390 ModestWindow *progress_window,
1391 RefreshAsyncUserCallback callback,
1394 ModestHeaderViewPrivate *priv;
1396 g_return_if_fail (self);
1398 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1401 if (priv->status_timeout) {
1402 g_source_remove (priv->status_timeout);
1403 priv->status_timeout = 0;
1406 g_mutex_lock (priv->observers_lock);
1407 tny_folder_remove_observer (priv->folder, TNY_FOLDER_OBSERVER (self));
1408 g_object_unref (priv->folder);
1409 priv->folder = NULL;
1410 g_mutex_unlock (priv->observers_lock);
1414 GtkTreeSelection *selection;
1415 SetFolderHelper *info;
1416 ModestMailOperation *mail_op = NULL;
1418 /* Set folder in the model */
1419 modest_header_view_set_folder_intern (self, folder, refresh);
1421 /* Pick my reference. Nothing to do with the mail operation */
1422 priv->folder = g_object_ref (folder);
1424 /* Do not notify about filterings until the refresh finishes */
1425 priv->notify_status = FALSE;
1427 /* Clear the selection if exists */
1428 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1429 gtk_tree_selection_unselect_all(selection);
1430 g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL);
1432 /* Notify the observers that the update begins */
1433 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1436 /* create the helper */
1437 info = g_malloc0 (sizeof (SetFolderHelper));
1438 info->header_view = g_object_ref (self);
1439 info->cb = callback;
1440 info->user_data = user_data;
1442 /* Create the mail operation (source will be the parent widget) */
1443 if (progress_window)
1444 mail_op = modest_mail_operation_new_with_error_handling (G_OBJECT(progress_window),
1445 refresh_folder_error_handler,
1448 modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (),
1451 /* Refresh the folder asynchronously */
1452 modest_mail_operation_refresh_folder (mail_op,
1454 folder_refreshed_cb,
1457 folder_refreshed_cb (mail_op, folder, info);
1461 g_object_unref (mail_op);
1463 g_mutex_lock (priv->observers_lock);
1465 if (priv->monitor) {
1466 tny_folder_monitor_stop (priv->monitor);
1467 g_object_unref (G_OBJECT (priv->monitor));
1468 priv->monitor = NULL;
1471 if (priv->autoselect_reference) {
1472 gtk_tree_row_reference_free (priv->autoselect_reference);
1473 priv->autoselect_reference = NULL;
1476 gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL);
1478 modest_header_view_notify_observers(self, NULL, NULL);
1480 g_mutex_unlock (priv->observers_lock);
1482 /* Notify the observers that the update is over */
1483 g_signal_emit (G_OBJECT (self), signals[UPDATING_MSG_LIST_SIGNAL],
1489 on_header_row_activated (GtkTreeView *treeview, GtkTreePath *path,
1490 GtkTreeViewColumn *column, gpointer userdata)
1492 ModestHeaderView *self = NULL;
1494 GtkTreeModel *model = NULL;
1495 TnyHeader *header = NULL;
1496 TnyHeaderFlags flags;
1498 self = MODEST_HEADER_VIEW (treeview);
1500 model = gtk_tree_view_get_model (treeview);
1501 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1504 /* get the first selected item */
1505 gtk_tree_model_get (model, &iter,
1506 TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags,
1507 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header,
1510 /* Dont open DELETED messages */
1511 if (flags & TNY_HEADER_FLAG_DELETED) {
1514 win = gtk_widget_get_ancestor (GTK_WIDGET (treeview), GTK_TYPE_WINDOW);
1515 msg = modest_ui_actions_get_msg_already_deleted_error_msg (MODEST_WINDOW (win));
1516 modest_platform_information_banner (NULL, NULL, msg);
1522 g_signal_emit (G_OBJECT(self),
1523 signals[HEADER_ACTIVATED_SIGNAL],
1529 g_object_unref (G_OBJECT (header));
1534 on_selection_changed (GtkTreeSelection *sel, gpointer user_data)
1536 GtkTreeModel *model;
1537 TnyHeader *header = NULL;
1538 GtkTreePath *path = NULL;
1540 ModestHeaderView *self;
1541 GList *selected = NULL;
1543 g_return_if_fail (sel);
1544 g_return_if_fail (user_data);
1546 self = MODEST_HEADER_VIEW (user_data);
1548 selected = gtk_tree_selection_get_selected_rows (sel, &model);
1549 if (selected != NULL)
1550 path = (GtkTreePath *) selected->data;
1551 if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path)))
1552 return; /* msg was _un_selected */
1554 gtk_tree_model_get (model, &iter,
1555 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1559 g_signal_emit (G_OBJECT(self),
1560 signals[HEADER_SELECTED_SIGNAL],
1563 g_object_unref (G_OBJECT (header));
1565 /* free all items in 'selected' */
1566 g_list_foreach (selected, (GFunc)gtk_tree_path_free, NULL);
1567 g_list_free (selected);
1571 /* PROTECTED method. It's useful when we want to force a given
1572 selection to reload a msg. For example if we have selected a header
1573 in offline mode, when Modest become online, we want to reload the
1574 message automatically without an user click over the header */
1576 _modest_header_view_change_selection (GtkTreeSelection *selection,
1579 g_return_if_fail (GTK_IS_TREE_SELECTION (selection));
1580 g_return_if_fail (user_data && MODEST_IS_HEADER_VIEW (user_data));
1582 on_selection_changed (selection, user_data);
1586 compare_priorities (TnyHeaderFlags p1, TnyHeaderFlags p2)
1593 if (p1 == TNY_HEADER_FLAG_HIGH_PRIORITY)
1597 if (p1 == TNY_HEADER_FLAG_LOW_PRIORITY)
1601 if ((p1 == TNY_HEADER_FLAG_NORMAL_PRIORITY) && (p2 == TNY_HEADER_FLAG_HIGH_PRIORITY))
1609 cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1617 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1618 col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_FLAG_SORT));
1622 case TNY_HEADER_FLAG_ATTACHMENTS:
1624 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1,
1625 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1626 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2,
1627 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1629 cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) -
1630 (val2 & TNY_HEADER_FLAG_ATTACHMENTS);
1632 return cmp ? cmp : t1 - t2;
1634 case TNY_HEADER_FLAG_PRIORITY_MASK: {
1635 TnyHeader *header1 = NULL, *header2 = NULL;
1637 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header1,
1638 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1);
1639 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &header2,
1640 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1);
1642 /* This is for making priority values respect the intuitive sort relationship
1643 * as HIGH is 01, LOW is 10, and NORMAL is 00 */
1645 if (header1 && header2) {
1646 cmp = compare_priorities (tny_header_get_priority (header1),
1647 tny_header_get_priority (header2));
1648 g_object_unref (header1);
1649 g_object_unref (header2);
1651 return cmp ? cmp : t1 - t2;
1657 return &iter1 - &iter2; /* oughhhh */
1662 cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
1669 g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
1671 gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val1,
1672 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1);
1673 gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &val2,
1674 TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
1676 /* Do not use the prefixes for sorting. Consume all the blank
1677 spaces for sorting */
1678 cmp = modest_text_utils_utf8_strcmp (g_strchug (val1 + modest_text_utils_get_subject_prefix_len(val1)),
1679 g_strchug (val2 + modest_text_utils_get_subject_prefix_len(val2)),
1682 /* If they're equal based on subject without prefix then just
1683 sort them by length. This will show messages like this.
1690 cmp = (g_utf8_strlen (val1, -1) >= g_utf8_strlen (val2, -1)) ? 1 : -1;
1697 /* Drag and drop stuff */
1699 drag_data_get_cb (GtkWidget *widget,
1700 GdkDragContext *context,
1701 GtkSelectionData *selection_data,
1706 ModestHeaderView *self = NULL;
1707 ModestHeaderViewPrivate *priv = NULL;
1709 self = MODEST_HEADER_VIEW (widget);
1710 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1712 /* Set the data. Do not use the current selection because it
1713 could be different than the selection at the beginning of
1715 modest_dnd_selection_data_set_paths (selection_data,
1716 priv->drag_begin_cached_selected_rows);
1720 * We're caching the selected rows at the beginning because the
1721 * selection could change between drag-begin and drag-data-get, for
1722 * example if we have a set of rows already selected, and then we
1723 * click in one of them (without SHIFT key pressed) and begin a drag,
1724 * the selection at that moment contains all the selected lines, but
1725 * after dropping the selection, the release event provokes that only
1726 * the row used to begin the drag is selected, so at the end the
1727 * drag&drop affects only one rows instead of all the selected ones.
1731 drag_begin_cb (GtkWidget *widget,
1732 GdkDragContext *context,
1735 ModestHeaderView *self = NULL;
1736 ModestHeaderViewPrivate *priv = NULL;
1737 GtkTreeSelection *selection;
1739 self = MODEST_HEADER_VIEW (widget);
1740 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1742 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
1743 priv->drag_begin_cached_selected_rows =
1744 gtk_tree_selection_get_selected_rows (selection, NULL);
1748 * We use the drag-end signal to clear the cached selection, we use
1749 * this because this allways happens, whether or not the d&d was a
1753 drag_end_cb (GtkWidget *widget,
1757 ModestHeaderView *self = NULL;
1758 ModestHeaderViewPrivate *priv = NULL;
1760 self = MODEST_HEADER_VIEW (widget);
1761 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
1763 /* Free cached data */
1764 g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
1765 g_list_free (priv->drag_begin_cached_selected_rows);
1766 priv->drag_begin_cached_selected_rows = NULL;
1769 /* Header view drag types */
1770 const GtkTargetEntry header_view_drag_types[] = {
1771 { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
1775 enable_drag_and_drop (GtkWidget *self)
1777 #ifdef MODEST_TOOLKIT_HILDON2
1780 gtk_drag_source_set (self, GDK_BUTTON1_MASK,
1781 header_view_drag_types,
1782 G_N_ELEMENTS (header_view_drag_types),
1783 GDK_ACTION_MOVE | GDK_ACTION_COPY);
1787 disable_drag_and_drop (GtkWidget *self)
1789 #ifdef MODEST_TOOLKIT_HILDON2
1792 gtk_drag_source_unset (self);
1796 setup_drag_and_drop (GtkWidget *self)
1798 #ifdef MODEST_TOOLKIT_HILDON2
1801 enable_drag_and_drop(self);
1802 g_signal_connect(G_OBJECT (self), "drag_data_get",
1803 G_CALLBACK(drag_data_get_cb), NULL);
1805 g_signal_connect(G_OBJECT (self), "drag_begin",
1806 G_CALLBACK(drag_begin_cb), NULL);
1808 g_signal_connect(G_OBJECT (self), "drag_end",
1809 G_CALLBACK(drag_end_cb), NULL);
1812 static GtkTreePath *
1813 get_selected_row (GtkTreeView *self, GtkTreeModel **model)
1815 GtkTreePath *path = NULL;
1816 GtkTreeSelection *sel = NULL;
1819 sel = gtk_tree_view_get_selection(self);
1820 rows = gtk_tree_selection_get_selected_rows (sel, model);
1822 if ((rows == NULL) || (g_list_length(rows) != 1))
1825 path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
1830 g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
1836 #ifndef MODEST_TOOLKIT_HILDON2
1838 * This function moves the tree view scroll to the current selected
1839 * row when the widget grabs the focus
1842 on_focus_in (GtkWidget *self,
1843 GdkEventFocus *event,
1846 GtkTreeSelection *selection;
1847 GtkTreeModel *model;
1848 GList *selected = NULL;
1849 GtkTreePath *selected_path = NULL;
1851 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1855 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1856 /* If none selected yet, pick the first one */
1857 if (gtk_tree_selection_count_selected_rows (selection) == 0) {
1861 /* Return if the model is empty */
1862 if (!gtk_tree_model_get_iter_first (model, &iter))
1865 path = gtk_tree_model_get_path (model, &iter);
1866 gtk_tree_selection_select_path (selection, path);
1867 gtk_tree_path_free (path);
1870 /* Need to get the all the rows because is selection multiple */
1871 selected = gtk_tree_selection_get_selected_rows (selection, &model);
1872 if (selected == NULL) return FALSE;
1873 selected_path = (GtkTreePath *) selected->data;
1876 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
1877 g_list_free (selected);
1883 on_focus_out (GtkWidget *self,
1884 GdkEventFocus *event,
1888 if (!gtk_widget_is_focus (self)) {
1889 GtkTreeSelection *selection = NULL;
1890 GList *selected_rows = NULL;
1891 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
1892 if (gtk_tree_selection_count_selected_rows (selection) > 1) {
1893 selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
1894 g_signal_handlers_block_by_func (selection, on_selection_changed, self);
1895 gtk_tree_selection_unselect_all (selection);
1896 gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
1897 g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
1898 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1899 g_list_free (selected_rows);
1907 on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1909 enable_drag_and_drop(self);
1914 on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
1916 GtkTreeSelection *selection = NULL;
1917 GtkTreePath *path = NULL;
1918 gboolean already_selected = FALSE, already_opened = FALSE;
1919 ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
1921 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
1923 GtkTreeModel *model;
1925 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
1926 already_selected = gtk_tree_selection_path_is_selected (selection, path);
1928 /* Get header from model */
1929 model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
1930 if (gtk_tree_model_get_iter (model, &iter, path)) {
1931 GValue value = {0,};
1934 gtk_tree_model_get_value (model, &iter,
1935 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
1937 header = (TnyHeader *) g_value_get_object (&value);
1938 if (TNY_IS_HEADER (header)) {
1939 status = modest_tny_all_send_queues_get_msg_status (header);
1940 already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
1943 g_value_unset (&value);
1947 /* Enable drag and drop only if the user clicks on a row that
1948 it's already selected. If not, let him select items using
1949 the pointer. If the message is in an OUTBOX and in sending
1950 status disable drag and drop as well */
1951 if (!already_selected ||
1952 status == MODEST_TNY_SEND_QUEUE_SENDING ||
1954 disable_drag_and_drop(self);
1957 gtk_tree_path_free(path);
1959 /* If it's already opened then do not let the button-press
1960 event go on because it'll perform a message open because
1961 we're clicking on to an already selected header */
1966 folder_monitor_update (TnyFolderObserver *self,
1967 TnyFolderChange *change)
1969 ModestHeaderViewPrivate *priv = NULL;
1970 TnyFolderChangeChanged changed;
1971 TnyFolder *folder = NULL;
1973 changed = tny_folder_change_get_changed (change);
1975 /* Do not notify the observers if the folder of the header
1976 view has changed before this call to the observer
1978 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
1979 folder = tny_folder_change_get_folder (change);
1980 if (folder != priv->folder)
1983 MODEST_DEBUG_BLOCK (
1984 if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
1985 g_print ("ADDED %d/%d (r/t) \n",
1986 tny_folder_change_get_new_unread_count (change),
1987 tny_folder_change_get_new_all_count (change));
1988 if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
1989 g_print ("ALL COUNT %d\n",
1990 tny_folder_change_get_new_all_count (change));
1991 if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
1992 g_print ("UNREAD COUNT %d\n",
1993 tny_folder_change_get_new_unread_count (change));
1994 if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
1995 g_print ("EXPUNGED %d/%d (r/t) \n",
1996 tny_folder_change_get_new_unread_count (change),
1997 tny_folder_change_get_new_all_count (change));
1998 if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
1999 g_print ("FOLDER RENAME\n");
2000 if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
2001 g_print ("MSG RECEIVED %d/%d (r/t) \n",
2002 tny_folder_change_get_new_unread_count (change),
2003 tny_folder_change_get_new_all_count (change));
2004 g_print ("---------------------------------------------------\n");
2007 /* Check folder count */
2008 if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
2009 (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
2011 g_mutex_lock (priv->observers_lock);
2013 /* Emit signal to evaluate how headers changes affects
2014 to the window view */
2015 g_signal_emit (G_OBJECT(self),
2016 signals[MSG_COUNT_CHANGED_SIGNAL],
2019 /* Added or removed headers, so data stored on cliboard are invalid */
2020 if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
2021 modest_email_clipboard_clear (priv->clipboard);
2023 g_mutex_unlock (priv->observers_lock);
2029 g_object_unref (folder);
2033 modest_header_view_is_empty (ModestHeaderView *self)
2035 ModestHeaderViewPrivate *priv;
2037 g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
2039 priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
2041 return priv->status == HEADER_VIEW_EMPTY;
2045 modest_header_view_clear (ModestHeaderView *self)
2047 g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
2049 modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL, NULL);
2053 modest_header_view_copy_selection (ModestHeaderView *header_view)
2055 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2057 /* Copy selection */
2058 _clipboard_set_selected_data (header_view, FALSE);
2062 modest_header_view_cut_selection (ModestHeaderView *header_view)
2064 ModestHeaderViewPrivate *priv = NULL;
2065 const gchar **hidding = NULL;
2066 guint i, n_selected;
2068 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2070 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2072 /* Copy selection */
2073 _clipboard_set_selected_data (header_view, TRUE);
2075 /* Get hidding ids */
2076 hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
2078 /* Clear hidding array created by previous cut operation */
2079 _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
2081 /* Copy hidding array */
2082 priv->n_selected = n_selected;
2083 priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
2084 for (i=0; i < n_selected; i++)
2085 priv->hidding_ids[i] = g_strdup(hidding[i]);
2087 /* Hide cut headers */
2088 modest_header_view_refilter (header_view);
2095 _clipboard_set_selected_data (ModestHeaderView *header_view,
2098 ModestHeaderViewPrivate *priv = NULL;
2099 TnyList *headers = NULL;
2101 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2102 priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
2104 /* Set selected data on clipboard */
2105 g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
2106 headers = modest_header_view_get_selected_headers (header_view);
2107 modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
2110 g_object_unref (headers);
2114 ModestHeaderView *self;
2119 notify_filter_change (gpointer data)
2121 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2123 g_signal_emit (info->self,
2124 signals[MSG_COUNT_CHANGED_SIGNAL],
2125 0, info->folder, NULL);
2131 notify_filter_change_destroy (gpointer data)
2133 NotifyFilterInfo *info = (NotifyFilterInfo *) data;
2134 ModestHeaderViewPrivate *priv;
2136 priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
2137 priv->status_timeout = 0;
2139 g_object_unref (info->self);
2140 g_object_unref (info->folder);
2141 g_slice_free (NotifyFilterInfo, info);
2145 current_folder_needs_filtering (ModestHeaderViewPrivate *priv)
2147 /* For the moment we only need to filter outbox */
2148 return priv->is_outbox;
2152 header_match_string (TnyHeader *header, gchar **words)
2154 gchar *subject_fold;
2160 gchar **current_word;
2163 subject_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD);
2164 if (subject_fold == NULL) {
2166 subject = tny_header_dup_subject (header);
2167 if (subject != NULL) {
2168 subject_fold = subject?g_utf8_casefold (subject, -1):NULL;
2169 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_SUBJECT_FOLD,
2170 subject_fold, (GDestroyNotify) g_free);
2175 from_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD);
2176 if (from_fold == NULL) {
2178 from = tny_header_dup_from (header);
2180 from_fold = from?g_utf8_casefold (from, -1):NULL;
2181 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_FROM_FOLD,
2182 from_fold, (GDestroyNotify) g_free);
2187 to_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_TO_FOLD);
2188 if (to_fold == NULL) {
2190 to = tny_header_dup_to (header);
2192 to_fold = to?g_utf8_casefold (to, -1):NULL;
2193 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_TO_FOLD,
2194 to_fold, (GDestroyNotify) g_free);
2199 cc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_CC_FOLD);
2200 if (cc_fold == NULL) {
2202 cc = tny_header_dup_cc (header);
2204 cc_fold = cc?g_utf8_casefold (cc, -1):NULL;
2205 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_CC_FOLD,
2206 cc_fold, (GDestroyNotify) g_free);
2211 bcc_fold = g_object_get_data (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD);
2212 if (bcc_fold == NULL) {
2214 bcc = tny_header_dup_bcc (header);
2216 bcc_fold = bcc?g_utf8_casefold (bcc, -1):NULL;
2217 g_object_set_data_full (G_OBJECT (header), _HEADER_VIEW_BCC_FOLD,
2218 bcc_fold, (GDestroyNotify) g_free);
2225 for (current_word = words; *current_word != NULL; current_word++) {
2227 if ((subject_fold && g_strstr_len (subject_fold, -1, *current_word))
2228 || (cc_fold && g_strstr_len (cc_fold, -1, *current_word))
2229 || (bcc_fold && g_strstr_len (bcc_fold, -1, *current_word))
2230 || (to_fold && g_strstr_len (to_fold, -1, *current_word))
2231 || (from_fold && g_strstr_len (from_fold, -1, *current_word))) {
2243 filter_row (GtkTreeModel *model,
2247 ModestHeaderViewPrivate *priv = NULL;
2248 TnyHeaderFlags flags;
2249 TnyHeader *header = NULL;
2252 gboolean visible = TRUE;
2253 gboolean found = FALSE;
2254 GValue value = {0,};
2255 HeaderViewStatus old_status;
2257 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
2258 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2260 /* Get header from model */
2261 gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
2262 header = (TnyHeader *) g_value_get_object (&value);
2263 g_value_unset (&value);
2264 flags = tny_header_get_flags (header);
2266 /* Get message id from header (ensure is a valid id) */
2272 /* Hide deleted and mark as deleted heders */
2273 if (flags & TNY_HEADER_FLAG_DELETED ||
2274 flags & TNY_HEADER_FLAG_EXPUNGED) {
2279 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_DELETABLE)) {
2280 if (current_folder_needs_filtering (priv) &&
2281 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2287 if (visible && (priv->filter & MODEST_HEADER_VIEW_FILTER_MOVEABLE)) {
2288 if (current_folder_needs_filtering (priv) &&
2289 modest_tny_all_send_queues_get_msg_status (header) == MODEST_TNY_SEND_QUEUE_SENDING) {
2295 if (visible && priv->filter_string) {
2296 if (!header_match_string (header, priv->filter_string_splitted)) {
2300 if (priv->filter_date_range) {
2301 if ((tny_header_get_date_sent (TNY_HEADER (header)) < priv->date_range_start) ||
2302 ((priv->date_range_end != -1) && (tny_header_get_date_sent (TNY_HEADER (header)) > priv->date_range_end))) {
2309 /* If no data on clipboard, return always TRUE */
2310 if (modest_email_clipboard_cleared(priv->clipboard)) {
2316 if (priv->hidding_ids != NULL) {
2317 id = tny_header_dup_message_id (header);
2318 for (i=0; i < priv->n_selected && !found; i++)
2319 if (priv->hidding_ids[i] != NULL && id != NULL)
2320 found = (!strcmp (priv->hidding_ids[i], id));
2327 old_status = priv->status;
2328 priv->status = ((gboolean) priv->status) && !visible;
2329 if ((priv->notify_status) && (priv->status != old_status)) {
2330 if (priv->status_timeout)
2331 g_source_remove (priv->status_timeout);
2334 NotifyFilterInfo *info;
2336 info = g_slice_new0 (NotifyFilterInfo);
2337 info->self = g_object_ref (G_OBJECT (user_data));
2339 info->folder = tny_header_get_folder (header);
2340 priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
2341 notify_filter_change,
2343 notify_filter_change_destroy);
2351 _clear_hidding_filter (ModestHeaderView *header_view)
2353 ModestHeaderViewPrivate *priv = NULL;
2356 g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
2357 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2359 if (priv->hidding_ids != NULL) {
2360 for (i=0; i < priv->n_selected; i++)
2361 g_free (priv->hidding_ids[i]);
2362 g_free(priv->hidding_ids);
2367 modest_header_view_refilter (ModestHeaderView *header_view)
2369 GtkTreeModel *filter_model = NULL;
2370 ModestHeaderViewPrivate *priv = NULL;
2372 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
2373 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2375 /* Hide cut headers */
2376 filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
2377 if (GTK_IS_TREE_MODEL_FILTER (filter_model)) {
2378 priv->status = HEADER_VIEW_INIT;
2379 gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
2384 * Called when an account is removed. If I'm showing a folder of the
2385 * account that has been removed then clear the view
2388 on_account_removed (TnyAccountStore *self,
2389 TnyAccount *account,
2392 ModestHeaderViewPrivate *priv = NULL;
2394 /* Ignore changes in transport accounts */
2395 if (TNY_IS_TRANSPORT_ACCOUNT (account))
2398 priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
2401 TnyAccount *my_account;
2403 if (TNY_IS_MERGE_FOLDER (priv->folder) &&
2404 tny_folder_get_folder_type (priv->folder) == TNY_FOLDER_TYPE_OUTBOX) {
2405 ModestTnyAccountStore *acc_store = modest_runtime_get_account_store ();
2406 my_account = modest_tny_account_store_get_local_folders_account (acc_store);
2408 my_account = tny_folder_get_account (priv->folder);
2412 if (my_account == account)
2413 modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
2414 g_object_unref (my_account);
2420 modest_header_view_add_observer(ModestHeaderView *header_view,
2421 ModestHeaderViewObserver *observer)
2423 ModestHeaderViewPrivate *priv;
2425 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2426 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2428 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2430 g_mutex_lock(priv->observer_list_lock);
2431 priv->observer_list = g_slist_prepend(priv->observer_list, observer);
2432 g_mutex_unlock(priv->observer_list_lock);
2436 modest_header_view_remove_observer(ModestHeaderView *header_view,
2437 ModestHeaderViewObserver *observer)
2439 ModestHeaderViewPrivate *priv;
2441 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2442 g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
2444 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2446 g_mutex_lock(priv->observer_list_lock);
2447 priv->observer_list = g_slist_remove(priv->observer_list, observer);
2448 g_mutex_unlock(priv->observer_list_lock);
2452 modest_header_view_notify_observers(ModestHeaderView *header_view,
2453 GtkTreeModel *model,
2454 const gchar *tny_folder_id)
2456 ModestHeaderViewPrivate *priv = NULL;
2458 ModestHeaderViewObserver *observer;
2461 g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
2463 priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
2465 g_mutex_lock(priv->observer_list_lock);
2466 iter = priv->observer_list;
2467 while(iter != NULL){
2468 observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
2469 modest_header_view_observer_update(observer, model,
2471 iter = g_slist_next(iter);
2473 g_mutex_unlock(priv->observer_list_lock);
2477 _modest_header_view_get_display_date (ModestHeaderView *self, time_t date)
2479 ModestHeaderViewPrivate *priv = NULL;
2481 priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
2482 return modest_datetime_formatter_display_datetime (priv->datetime_formatter, date);
2486 modest_header_view_set_filter (ModestHeaderView *self,
2487 ModestHeaderViewFilter filter)
2489 ModestHeaderViewPrivate *priv;
2491 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2492 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2494 priv->filter |= filter;
2496 if (current_folder_needs_filtering (priv))
2497 modest_header_view_refilter (self);
2501 modest_header_view_unset_filter (ModestHeaderView *self,
2502 ModestHeaderViewFilter filter)
2504 ModestHeaderViewPrivate *priv;
2506 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2507 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2509 priv->filter &= ~filter;
2511 if (current_folder_needs_filtering (priv))
2512 modest_header_view_refilter (self);
2516 on_notify_style (GObject *obj, GParamSpec *spec, gpointer userdata)
2518 if (strcmp ("style", spec->name) == 0) {
2519 update_style (MODEST_HEADER_VIEW (obj));
2520 gtk_widget_queue_draw (GTK_WIDGET (obj));
2525 update_style (ModestHeaderView *self)
2527 ModestHeaderViewPrivate *priv;
2528 GdkColor style_color;
2529 GdkColor style_active_color;
2530 PangoAttrList *attr_list;
2532 PangoAttribute *attr;
2534 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2535 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2539 attr_list = pango_attr_list_new ();
2540 if (!gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "SecondaryTextColor", &style_color)) {
2541 gdk_color_parse (MODEST_SECONDARY_COLOR, &style_color);
2543 priv->secondary_color = style_color;
2544 attr = pango_attr_foreground_new (style_color.red, style_color.green, style_color.blue);
2545 pango_attr_list_insert (attr_list, attr);
2548 style = gtk_rc_get_style_by_paths (gtk_widget_get_settings
2550 "SmallSystemFont", NULL,
2553 attr = pango_attr_font_desc_new (pango_font_description_copy
2554 (style->font_desc));
2555 pango_attr_list_insert (attr_list, attr);
2557 g_object_set (G_OBJECT (priv->renderer_address),
2558 "foreground-gdk", &(priv->secondary_color),
2559 "foreground-set", TRUE,
2560 "attributes", attr_list,
2562 g_object_set (G_OBJECT (priv->renderer_date_status),
2563 "foreground-gdk", &(priv->secondary_color),
2564 "foreground-set", TRUE,
2565 "attributes", attr_list,
2567 pango_attr_list_unref (attr_list);
2569 g_object_set (G_OBJECT (priv->renderer_address),
2570 "foreground-gdk", &(priv->secondary_color),
2571 "foreground-set", TRUE,
2572 "scale", PANGO_SCALE_SMALL,
2575 g_object_set (G_OBJECT (priv->renderer_date_status),
2576 "foreground-gdk", &(priv->secondary_color),
2577 "foreground-set", TRUE,
2578 "scale", PANGO_SCALE_SMALL,
2583 if (gtk_style_lookup_color (gtk_widget_get_style (GTK_WIDGET (self)), "ActiveTextColor", &style_active_color)) {
2584 priv->active_color = style_active_color;
2585 #ifdef MODEST_TOOLKIT_HILDON2
2586 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (TRUE));
2587 g_object_set_data (G_OBJECT (priv->renderer_subject), ACTIVE_COLOR, &(priv->active_color));
2590 #ifdef MODEST_TOOLKIT_HILDON2
2591 g_object_set_data (G_OBJECT (priv->renderer_subject), BOLD_IS_ACTIVE_COLOR, GINT_TO_POINTER (FALSE));
2597 modest_header_view_get_header_at_pos (ModestHeaderView *header_view,
2602 GtkTreeModel *tree_model;
2607 if (!gtk_tree_view_get_dest_row_at_pos ((GtkTreeView *) header_view,
2615 tree_model = gtk_tree_view_get_model ((GtkTreeView *) header_view);
2616 if (!gtk_tree_model_get_iter (tree_model, &iter, path))
2620 gtk_tree_model_get (tree_model, &iter,
2621 TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
2628 parse_date_side (const gchar *string, time_t *date_side)
2634 gboolean result = FALSE;
2636 if (string && string[0] == '\0') {
2641 casefold = g_utf8_casefold (string, -1);
2642 today = g_utf8_casefold (dgettext ("gtk20", "Today"), -1);
2643 yesterday = g_utf8_casefold (dgettext ("gtk20", "Yesterday"), -1);
2644 date = g_date_new ();
2646 if (g_utf8_collate (casefold, today) == 0) {
2647 *date_side = time (NULL);
2652 if (g_utf8_collate (casefold, yesterday) == 0) {
2653 *date_side = time (NULL) - 24*60*60;
2658 g_date_set_parse (date, string);
2659 if (g_date_valid (date)) {
2661 g_date_to_struct_tm (date, &tm);
2662 *date_side = mktime (&tm);
2677 parse_date_range (const gchar *string, time_t *date_range_start, time_t *date_range_end)
2682 parts = g_strsplit (string, "..", 2);
2685 if (g_strv_length (parts) != 2) {
2692 if (!parse_date_side (parts[0], date_range_start)) {
2697 if (parse_date_side (parts[1], date_range_end)) {
2698 if (*date_range_end == 0) {
2699 *date_range_end = (time_t) -1;
2701 *date_range_end += (24*60*60 - 1);
2714 modest_header_view_set_filter_string (ModestHeaderView *self,
2715 const gchar *filter_string)
2717 ModestHeaderViewPrivate *priv;
2719 g_return_if_fail (MODEST_IS_HEADER_VIEW (self));
2720 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2722 if (priv->filter_string)
2723 g_free (priv->filter_string);
2725 priv->filter_string = g_strdup (filter_string);
2726 priv->filter_date_range = FALSE;
2728 if (priv->filter_string_splitted) {
2729 g_strfreev (priv->filter_string_splitted);
2730 priv->filter_string_splitted = NULL;
2733 if (priv->filter_string) {
2734 gchar **split, **current, **current_target;
2736 split = g_strsplit (priv->filter_string, " ", 0);
2738 priv->filter_string_splitted = g_malloc0 (sizeof (gchar *)*(g_strv_length (split) + 1));
2739 current_target = priv->filter_string_splitted;
2740 for (current = split; *current != 0; current ++) {
2741 gboolean has_date_range = FALSE;;
2742 if (g_strstr_len (*current, -1, "..") && strcmp(*current, "..")) {
2743 time_t range_start, range_end;
2744 /* It contains .. but it's not ".." so it may be a date range */
2745 if (parse_date_range (*current, &range_start, &range_end)) {
2746 priv->filter_date_range = TRUE;
2747 has_date_range = TRUE;
2748 priv->date_range_start = range_start;
2749 priv->date_range_end = range_end;
2752 if (!has_date_range) {
2753 *current_target = g_utf8_casefold (*current, -1);
2757 *current_target = '\0';
2760 modest_header_view_refilter (MODEST_HEADER_VIEW (self));
2763 #ifdef MODEST_TOOLKIT_HILDON2
2765 on_live_search_refilter (HildonLiveSearch *livesearch,
2766 ModestHeaderView *self)
2768 const gchar *needle;
2770 needle = hildon_live_search_get_text (livesearch);
2771 if (needle && needle[0] != '\0') {
2772 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), needle);
2774 modest_header_view_set_filter_string (MODEST_HEADER_VIEW (self), NULL);
2777 priv->live_search_timeout = 0;
2783 on_live_search_refilter (HildonLiveSearch *livesearch,
2784 ModestHeaderView *self)
2786 ModestHeaderViewPrivate *priv;
2787 GtkTreeModel *model, *sortable, *filter;
2789 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2791 if (priv->live_search_timeout > 0) {
2792 g_source_remove (priv->live_search_timeout);
2793 priv->live_search_timeout = 0;
2797 filter = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
2798 if (GTK_IS_TREE_MODEL_FILTER (filter)) {
2799 sortable = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter));
2800 if (GTK_IS_TREE_MODEL_SORT (sortable)) {
2801 model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (sortable));
2805 if (model && tny_list_get_length (TNY_LIST (model)) > 250) {
2806 priv->live_search_timeout = g_timeout_add (1000, (GSourceFunc) on_live_search_timeout, self);
2808 on_live_search_timeout (self);
2815 modest_header_view_setup_live_search (ModestHeaderView *self)
2817 ModestHeaderViewPrivate *priv;
2819 g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), NULL);
2820 priv = MODEST_HEADER_VIEW_GET_PRIVATE (self);
2821 priv->live_search = hildon_live_search_new ();
2823 g_signal_connect (G_OBJECT (priv->live_search), "refilter", G_CALLBACK (on_live_search_refilter), self);
2825 return priv->live_search;