X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fwidgets%2Fmodest-header-view.c;h=85d5f786b23202ff0419e1c50898ac8feb5680eb;hp=f2acf3e5785194fd8df84eacbb714b0d3613ae7b;hb=b93b64adaaf2947f8b35889981af6ad96b18fd31;hpb=2e7120c1f07ef549a82508a233c7eb0b1200de92 diff --git a/src/widgets/modest-header-view.c b/src/widgets/modest-header-view.c index f2acf3e..85d5f78 100644 --- a/src/widgets/modest-header-view.c +++ b/src/widgets/modest-header-view.c @@ -27,48 +27,70 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ - -/* modest-tny-header-tree-view.c */ - #include -#include "modest-header-view.h" -#include +#include +#include +#include #include -#include +#include +#include +#include + +#include +#include #include -#include "modest-icon-factory.h" +#include static void modest_header_view_class_init (ModestHeaderViewClass *klass); static void modest_header_view_init (ModestHeaderView *obj); static void modest_header_view_finalize (GObject *obj); -static void on_selection_changed (GtkTreeSelection *sel, gpointer user_data); -//static void on_column_clicked (GtkTreeViewColumn *treeviewcolumn, gpointer user_data); -static gboolean refresh_folder_finish_status_update (gpointer user_data); +static gboolean on_header_clicked (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data); -enum { - MESSAGE_SELECTED_SIGNAL, - STATUS_UPDATE_SIGNAL, - LAST_SIGNAL -}; +static gint cmp_rows (GtkTreeModel *tree_model, + GtkTreeIter *iter1, + GtkTreeIter *iter2, + gpointer user_data); -typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate; -struct _ModestHeaderViewPrivate { +static void on_selection_changed (GtkTreeSelection *sel, + gpointer user_data); - TnyMsgFolderIface *tny_msg_folder; - TnyListIface *headers; +static void setup_drag_and_drop (GtkTreeView *self); - gint status_id; - GSList *columns; +static GtkTreePath * get_selected_row (GtkTreeView *self, GtkTreeModel **model); - GMutex *lock; - +static gboolean on_focus_in (GtkWidget *sef, + GdkEventFocus *event, + gpointer user_data); + +typedef struct _ModestHeaderViewPrivate ModestHeaderViewPrivate; +struct _ModestHeaderViewPrivate { + TnyFolder *folder; ModestHeaderViewStyle style; + + TnyFolderMonitor *monitor; + GMutex *monitor_lock; }; + #define MODEST_HEADER_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE((o), \ MODEST_TYPE_HEADER_VIEW, \ ModestHeaderViewPrivate)) + + + +#define MODEST_HEADER_VIEW_PTR "modest-header-view" + +enum { + HEADER_SELECTED_SIGNAL, + HEADER_ACTIVATED_SIGNAL, + ITEM_NOT_FOUND_SIGNAL, + STATUS_UPDATE_SIGNAL, + LAST_SIGNAL +}; + /* globals */ static GObjectClass *parent_class = NULL; @@ -90,6 +112,7 @@ modest_header_view_get_type (void) sizeof(ModestHeaderView), 1, /* n_preallocs */ (GInstanceInitFunc) modest_header_view_init, + NULL }; my_type = g_type_register_static (GTK_TYPE_TREE_VIEW, "ModestHeaderView", @@ -109,214 +132,44 @@ modest_header_view_class_init (ModestHeaderViewClass *klass) g_type_class_add_private (gobject_class, sizeof(ModestHeaderViewPrivate)); - signals[MESSAGE_SELECTED_SIGNAL] = - g_signal_new ("message_selected", + signals[HEADER_SELECTED_SIGNAL] = + g_signal_new ("header_selected", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (ModestHeaderViewClass,message_selected), + G_STRUCT_OFFSET (ModestHeaderViewClass,header_selected), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); - - signals[STATUS_UPDATE_SIGNAL] = - g_signal_new ("status_update", + + signals[HEADER_ACTIVATED_SIGNAL] = + g_signal_new ("header_activated", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_FIRST, - G_STRUCT_OFFSET (ModestHeaderViewClass,message_selected), + G_STRUCT_OFFSET (ModestHeaderViewClass,header_activated), NULL, NULL, - modest_marshal_VOID__STRING_INT, - G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_INT); -} - - - - - -static void -msgtype_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer, - GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) -{ - TnyMsgHeaderFlags flags; - GdkPixbuf *pixbuf = NULL; - - gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, - &flags, -1); - - if (flags & TNY_MSG_HEADER_FLAG_DELETED) - pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_DELETED); - else if (flags & TNY_MSG_HEADER_FLAG_SEEN) - pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_READ); - else - pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_UNREAD); - - g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL); -} - -static void -attach_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer, - GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) -{ - TnyMsgHeaderFlags flags; - GdkPixbuf *pixbuf = NULL; - - gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, - &flags, -1); - - if (flags & TNY_MSG_HEADER_FLAG_ATTACHMENTS) - pixbuf = modest_icon_factory_get_icon (MODEST_HEADER_ICON_ATTACH); - - g_object_set (G_OBJECT (renderer), "pixbuf", pixbuf, NULL); -} - - -static void -header_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer, - GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) -{ - TnyMsgHeaderFlags flags; - - gtk_tree_model_get (tree_model, iter, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, - &flags, -1); - - g_object_set (G_OBJECT(renderer), - "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800, - "style", (flags & TNY_MSG_HEADER_FLAG_DELETED) ? - PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, - NULL); -} - - - -/* try to make a shorter display address; changes it arg in-place */ -static gchar* -display_address (gchar *address) -{ - gchar *cursor; - - if (!address) - return NULL; - - /* simplistic --> remove from display name */ - cursor = g_strstr_len (address, strlen(address), "<"); - if (cursor) - cursor[0]='\0'; - - /* simplistic --> remove (bla bla) from display name */ - cursor = g_strstr_len (address, strlen(address), "("); - if (cursor) - cursor[0]='\0'; - - return address; -} - - - -static void -sender_receiver_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer, - GtkTreeModel *tree_model, GtkTreeIter *iter, gboolean is_sender) -{ - TnyMsgHeaderFlags flags; - gchar *address; - gint sender_receiver_col; - - if (is_sender) - sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN; - else - sender_receiver_col = TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN; - - gtk_tree_model_get (tree_model, iter, - sender_receiver_col, &address, - TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags, - -1); - - g_object_set (G_OBJECT(renderer), - "text", - display_address (address), - "weight", - (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400 : 800, - "style", - (flags & TNY_MSG_HEADER_FLAG_DELETED)?PANGO_STYLE_ITALIC:PANGO_STYLE_NORMAL, - NULL); - - g_free (address); -} - - - -/* just to prevent warnings: - * warning: `%x' yields only last 2 digits of year in some locales - */ -static size_t -my_strftime(char *s, size_t max, const char *fmt, const - struct tm *tm) { - return strftime(s, max, fmt, tm); -} - - - -/* not reentrant/thread-safe */ -const gchar* -display_date (time_t date) -{ - struct tm date_tm, now_tm; - time_t now; - - const gint buf_size = 64; - static gchar date_buf[64]; /* buf_size is not ... */ - static gchar now_buf[64]; /* ...const enough... */ + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); - now = time (NULL); - localtime_r(&now, &now_tm); - localtime_r(&date, &date_tm); - - /* get today's date */ - my_strftime (date_buf, buf_size, "%x", &date_tm); - my_strftime (now_buf, buf_size, "%x", &now_tm); /* today */ - - /* if this is today, get the time instead of the date */ - if (strcmp (date_buf, now_buf) == 0) - strftime (date_buf, buf_size, _("%X"), &date_tm); - - return date_buf; -} - + signals[ITEM_NOT_FOUND_SIGNAL] = + g_signal_new ("item_not_found", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ModestHeaderViewClass,item_not_found), + NULL, NULL, + g_cclosure_marshal_VOID__INT, + G_TYPE_NONE, 1, G_TYPE_INT); -static void -compact_header_cell_data (GtkTreeViewColumn *column, GtkCellRenderer *renderer, - GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer user_data) -{ - GObject *rendobj; - TnyMsgHeaderFlags flags; - gchar *from, *subject; - gchar *header; - time_t date; - - gtk_tree_model_get (tree_model, iter, - TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &flags, - TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &from, - TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &subject, - TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &date, - -1); - rendobj = G_OBJECT(renderer); - - header = g_strdup_printf ("%s %s\n%s", - display_address (from), - display_date(date), - subject); - - g_object_set (G_OBJECT(renderer), - "text", header, - "weight", (flags & TNY_MSG_HEADER_FLAG_SEEN) ? 400: 800, - "style", (flags & TNY_MSG_HEADER_FLAG_DELETED) ? - PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL, - NULL); - g_free (header); - g_free (from); - g_free (subject); + signals[STATUS_UPDATE_SIGNAL] = + g_signal_new ("status_update", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ModestHeaderViewClass,status_update), + NULL, NULL, + modest_marshal_VOID__STRING_INT_INT, + G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT); } - static GtkTreeViewColumn* get_new_column (const gchar *name, GtkCellRenderer *renderer, gboolean resizable, gint sort_col_id, gboolean show_as_text, @@ -325,8 +178,11 @@ get_new_column (const gchar *name, GtkCellRenderer *renderer, GtkTreeViewColumn *column; column = gtk_tree_view_column_new_with_attributes(name, renderer, NULL); - gtk_tree_view_column_set_resizable (column, resizable); gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED); + + gtk_tree_view_column_set_resizable (column, resizable); + if (resizable) + gtk_tree_view_column_set_expand (column, TRUE); if (show_as_text) gtk_tree_view_column_add_attribute (column, renderer, "text", @@ -336,14 +192,10 @@ get_new_column (const gchar *name, GtkCellRenderer *renderer, gtk_tree_view_column_set_sort_indicator (column, FALSE); gtk_tree_view_column_set_reorderable (column, TRUE); - + if (cell_data_func) gtk_tree_view_column_set_cell_data_func(column, renderer, cell_data_func, user_data, NULL); - -/* g_signal_connect (G_OBJECT (column), "clicked", */ -/* G_CALLBACK (column_clicked), obj); */ - return column; } @@ -363,107 +215,205 @@ remove_all_columns (ModestHeaderView *obj) g_list_free (columns); } - - - -static void -init_columns (ModestHeaderView *obj) +gboolean +modest_header_view_set_columns (ModestHeaderView *self, const GList *columns) { + GtkTreeModel *sortable; GtkTreeViewColumn *column=NULL; - GtkCellRenderer *renderer_msgtype, - *renderer_header, - *renderer_attach; - + GtkTreeSelection *selection = NULL; + GtkCellRenderer *renderer_msgtype,*renderer_header, + *renderer_attach, *renderer_comptact_flag, + *renderer_compact_date; ModestHeaderViewPrivate *priv; - GSList *cursor; + const GList *cursor; - priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); - + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + + /* FIXME: check whether these renderers need to be freed */ renderer_msgtype = gtk_cell_renderer_pixbuf_new (); renderer_attach = gtk_cell_renderer_pixbuf_new (); - renderer_header = gtk_cell_renderer_text_new (); - - remove_all_columns (obj); + renderer_header = gtk_cell_renderer_text_new (); + renderer_comptact_flag = gtk_cell_renderer_pixbuf_new (); + renderer_compact_date = gtk_cell_renderer_text_new (); + + g_object_set(G_OBJECT(renderer_header), + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + g_object_set(G_OBJECT(renderer_compact_date), + "xalign", 1.0, + NULL); - for (cursor = priv->columns; cursor; cursor = cursor->next) { + remove_all_columns (self); + + selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); + sortable = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); + + /* Add new columns */ + for (cursor = columns; cursor; cursor = g_list_next(cursor)) { ModestHeaderViewColumn col = (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data); + if (0> col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) { + g_printerr ("modest: invalid column %d in column list\n", col); + continue; + } + switch (col) { case MODEST_HEADER_VIEW_COLUMN_MSGTYPE: - column = get_new_column (_("M"), renderer_msgtype, FALSE, - TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, - FALSE, (GtkTreeCellDataFunc)msgtype_cell_data, + TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, + FALSE, + (GtkTreeCellDataFunc)_modest_header_view_msgtype_cell_data, NULL); + gtk_tree_view_column_set_fixed_width (column, 45); break; case MODEST_HEADER_VIEW_COLUMN_ATTACH: - column = get_new_column (_("A"), renderer_attach, FALSE, - TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, - FALSE, (GtkTreeCellDataFunc)attach_cell_data, - NULL); - break; - - case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE: - column = get_new_column (_("Received"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_COLUMN, - TRUE, (GtkTreeCellDataFunc)header_cell_data, + TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, + FALSE, + (GtkTreeCellDataFunc)_modest_header_view_attach_cell_data, NULL); + gtk_tree_view_column_set_fixed_width (column, 45); break; + case MODEST_HEADER_VIEW_COLUMN_FROM: column = get_new_column (_("From"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, - TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data, + TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data, GINT_TO_POINTER(TRUE)); break; case MODEST_HEADER_VIEW_COLUMN_TO: column = get_new_column (_("To"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, - TRUE, (GtkTreeCellDataFunc)sender_receiver_cell_data, + TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_sender_receiver_cell_data, GINT_TO_POINTER(FALSE)); break; - case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER: - column = get_new_column (_("Header"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, - TRUE, (GtkTreeCellDataFunc)compact_header_cell_data, + case MODEST_HEADER_VIEW_COLUMN_COMPACT_FLAG: + column = get_new_column (_("F"), renderer_comptact_flag, FALSE, + TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, + FALSE, + (GtkTreeCellDataFunc)_modest_header_view_compact_flag_cell_data, NULL); + gtk_tree_view_column_set_fixed_width (column, 45); + break; + + case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN: + column = get_new_column (_("Header"), renderer_header, TRUE, + TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data, + GINT_TO_POINTER(TRUE)); break; + + case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT: + column = get_new_column (_("Header"), renderer_header, TRUE, + TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_compact_header_cell_data, + GINT_TO_POINTER(FALSE)); + break; + case MODEST_HEADER_VIEW_COLUMN_SUBJECT: column = get_new_column (_("Subject"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, - TRUE, (GtkTreeCellDataFunc)header_cell_data, + TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_header_cell_data, NULL); break; + case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE: + column = get_new_column (_("Received"), renderer_header, TRUE, + TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_date_cell_data, + GINT_TO_POINTER(TRUE)); + break; - case MODEST_HEADER_VIEW_COLUMN_SENT_DATE: + case MODEST_HEADER_VIEW_COLUMN_SENT_DATE: column = get_new_column (_("Sent"), renderer_header, TRUE, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_COLUMN, - TRUE, (GtkTreeCellDataFunc)header_cell_data, - NULL); + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_date_cell_data, + GINT_TO_POINTER(FALSE)); + break; + + case MODEST_HEADER_VIEW_COLUMN_COMPACT_RECEIVED_DATE: + column = get_new_column (_("Received"), renderer_compact_date, FALSE, + TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_compact_date_cell_data, + GINT_TO_POINTER(TRUE)); + gtk_tree_view_column_set_fixed_width (column, 130); + break; + + case MODEST_HEADER_VIEW_COLUMN_COMPACT_SENT_DATE: + column = get_new_column (_("Sent"), renderer_compact_date, FALSE, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, + TRUE, + (GtkTreeCellDataFunc)_modest_header_view_compact_date_cell_data, + GINT_TO_POINTER(FALSE)); + gtk_tree_view_column_set_fixed_width (column, 130); + break; + case MODEST_HEADER_VIEW_COLUMN_SIZE: + column = get_new_column (_("Size"), renderer_header, TRUE, + TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN, + FALSE, + (GtkTreeCellDataFunc)_modest_header_view_size_cell_data, + NULL); + break; + case MODEST_HEADER_VIEW_COLUMN_STATUS: + column = get_new_column (_("Status"), renderer_compact_date, TRUE, + TNY_GTK_HEADER_LIST_MODEL_MESSAGE_SIZE_COLUMN, + FALSE, + (GtkTreeCellDataFunc)_modest_header_view_status_cell_data, + NULL); break; + + default: + g_return_val_if_reached(FALSE); } - gtk_tree_view_append_column (GTK_TREE_VIEW(obj), column); - } -} + if (sortable) + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), + col, (GtkTreeIterCompareFunc)cmp_rows, + column, NULL); + + /* we keep the column id around */ + g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_COLUMN, + GINT_TO_POINTER(col)); + + /* we need this ptr when sorting the rows */ + g_object_set_data (G_OBJECT(column), MODEST_HEADER_VIEW_PTR, + self); + gtk_tree_view_append_column (GTK_TREE_VIEW(self), column); + } + return TRUE; +} static void modest_header_view_init (ModestHeaderView *obj) { ModestHeaderViewPrivate *priv; + priv = MODEST_HEADER_VIEW_GET_PRIVATE(obj); - priv->status_id = 0; - priv->lock = g_mutex_new (); + priv->folder = NULL; + + priv->monitor = NULL; + priv->monitor_lock = g_mutex_new (); + + + setup_drag_and_drop (GTK_TREE_VIEW (obj)); } static void @@ -475,157 +425,520 @@ modest_header_view_finalize (GObject *obj) self = MODEST_HEADER_VIEW(obj); priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - if (priv->headers) - g_object_unref (G_OBJECT(priv->headers)); - - - if (priv->lock) { - g_mutex_free (priv->lock); - priv->lock = NULL; + g_mutex_lock (priv->monitor_lock); + if (priv->monitor) { + tny_folder_monitor_stop (priv->monitor); + g_object_unref (G_OBJECT (priv->monitor)); } + g_mutex_unlock (priv->monitor_lock); + g_mutex_free (priv->monitor_lock); - priv->headers = NULL; - priv->tny_msg_folder = NULL; - + if (priv->folder) { + g_object_unref (G_OBJECT (priv->folder)); + priv->folder = NULL; + } + G_OBJECT_CLASS(parent_class)->finalize (obj); } + GtkWidget* -modest_header_view_new (TnyMsgFolderIface *folder, - GSList *columns, - ModestHeaderViewStyle style) +modest_header_view_new (TnyFolder *folder, ModestHeaderViewStyle style) { GObject *obj; GtkTreeSelection *sel; ModestHeaderView *self; + ModestHeaderViewPrivate *priv; + + g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM, + NULL); obj = G_OBJECT(g_object_new(MODEST_TYPE_HEADER_VIEW, NULL)); self = MODEST_HEADER_VIEW(obj); - - if (!modest_header_view_set_folder (self, NULL)) { - g_warning ("could not set the folder"); - g_object_unref (obj); - return NULL; - } + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); modest_header_view_set_style (self, style); - modest_header_view_set_columns (self, columns); - - /* all cols */ - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(obj), TRUE); - gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(obj), TRUE); + modest_header_view_set_folder (self, NULL); + + gtk_tree_view_columns_autosize (GTK_TREE_VIEW(obj)); + gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW(obj),TRUE); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW(obj), TRUE); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(obj), TRUE); /* alternating row colors */ - sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self)); + g_signal_connect (sel, "changed", G_CALLBACK(on_selection_changed), self); + + g_signal_connect (self, "button-press-event", + G_CALLBACK(on_header_clicked), NULL); + g_signal_connect (self, "focus-in-event", + G_CALLBACK(on_focus_in), NULL); + return GTK_WIDGET(self); } -gboolean -modest_header_view_set_columns (ModestHeaderView *self, GSList *columns) + +TnyList * +modest_header_view_get_selected_headers (ModestHeaderView *self) { + GtkTreeSelection *sel; ModestHeaderViewPrivate *priv; - GSList *cursor; + TnyList *header_list = NULL; + TnyHeader *header; + GList *list, *tmp = NULL; + GtkTreeModel *tree_model = NULL; + GtkTreeIter iter; - g_return_val_if_fail (self, FALSE); + g_return_val_if_fail (self, NULL); priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - g_slist_free (priv->columns); - - for (cursor = columns; cursor; cursor = cursor->next) { - ModestHeaderViewColumn col = - (ModestHeaderViewColumn) GPOINTER_TO_INT(cursor->data); - if (0 > col || col >= MODEST_HEADER_VIEW_COLUMN_NUM) - g_warning ("invalid column in column list"); - else - priv->columns = g_slist_append (priv->columns, cursor->data); + /* Get selected rows */ + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(self)); + list = gtk_tree_selection_get_selected_rows (sel, &tree_model); + + if (list) { + header_list = tny_simple_list_new(); + + list = g_list_reverse (list); + tmp = list; + while (tmp) { + /* get header from selection */ + gtk_tree_model_get_iter (tree_model, &iter, (GtkTreePath *) (tmp->data)); + gtk_tree_model_get (tree_model, &iter, + TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, + &header, -1); + /* Prepend to list */ + tny_list_prepend (header_list, G_OBJECT (header)); + g_object_unref (G_OBJECT (header)); + + tmp = g_list_next (tmp); + } + /* Clean up*/ + g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL); + g_list_free (list); } + return header_list; +} - init_columns (self); /* redraw them */ - return TRUE; + +/* scroll our list view so the selected item is visible */ +static void +scroll_to_selected (ModestHeaderView *self, GtkTreeIter *iter, gboolean up) +{ +#ifdef MODEST_PLATFORM_GNOME + + GtkTreePath *selected_path; + GtkTreePath *start, *end; + + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW(self)); + selected_path = gtk_tree_model_get_path (model, iter); + + start = gtk_tree_path_new (); + end = gtk_tree_path_new (); + + gtk_tree_view_get_visible_range (GTK_TREE_VIEW(self), &start, &end); + + if (gtk_tree_path_compare (selected_path, start) < 0 || + gtk_tree_path_compare (end, selected_path) < 0) + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW(self), + selected_path, NULL, TRUE, + up ? 0.0 : 1.0, + up ? 0.0 : 1.0); + gtk_tree_path_free (selected_path); + gtk_tree_path_free (start); + gtk_tree_path_free (end); + +#endif /* MODEST_PLATFORM_GNOME */ } +void +modest_header_view_select_next (ModestHeaderView *self) +{ + GtkTreeSelection *sel; + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); + path = get_selected_row (GTK_TREE_VIEW(self), &model); + if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) { + /* Unselect previous path */ + gtk_tree_selection_unselect_path (sel, path); + + /* Move path down and selects new one */ + if (gtk_tree_model_iter_next (model, &iter)) { + gtk_tree_selection_select_iter (sel, &iter); + scroll_to_selected (self, &iter, FALSE); + } + gtk_tree_path_free(path); + } + +} -const GSList* -modest_header_view_get_columns (ModestHeaderView *self) +void +modest_header_view_select_prev (ModestHeaderView *self) { - ModestHeaderViewPrivate *priv; + GtkTreeSelection *sel; + GtkTreeIter iter; + GtkTreeModel *model; + GtkTreePath *path; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); + path = get_selected_row (GTK_TREE_VIEW(self), &model); + if ((path != NULL) && (gtk_tree_model_get_iter(model, &iter, path))) { + /* Unselect previous path */ + gtk_tree_selection_unselect_path (sel, path); + + /* Move path up */ + if (gtk_tree_path_prev (path)) { + gtk_tree_model_get_iter (model, &iter, path); + + /* Select the new one */ + gtk_tree_selection_select_iter (sel, &iter); + scroll_to_selected (self, &iter, TRUE); - g_return_val_if_fail (self, FALSE); - - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - return priv->columns; + } + gtk_tree_path_free (path); + } } +GList* +modest_header_view_get_columns (ModestHeaderView *self) +{ + g_return_val_if_fail (self, FALSE); + return gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); +} +gboolean +modest_header_view_is_empty (ModestHeaderView *self) +{ + g_return_val_if_fail (self, FALSE); + return FALSE; /* FIXME */ +} gboolean modest_header_view_set_style (ModestHeaderView *self, - ModestHeaderViewStyle style) + ModestHeaderViewStyle style) { + ModestHeaderViewPrivate *priv; + gboolean show_col_headers = FALSE; + ModestHeaderViewStyle old_style; + g_return_val_if_fail (self, FALSE); - g_return_val_if_fail (style >= 0 && style < MODEST_HEADER_VIEW_STYLE_NUM, + g_return_val_if_fail (style >= 0 && MODEST_HEADER_VIEW_STYLE_NUM, FALSE); + + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + if (priv->style == style) + return TRUE; /* nothing to do */ - MODEST_HEADER_VIEW_GET_PRIVATE(self)->style = style; - + switch (style) { + case MODEST_HEADER_VIEW_STYLE_DETAILS: + show_col_headers = TRUE; + break; + case MODEST_HEADER_VIEW_STYLE_TWOLINES: + break; + default: + g_return_val_if_reached (FALSE); + } + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), show_col_headers); + gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), show_col_headers); + + old_style = priv->style; + priv->style = style; + return TRUE; } + ModestHeaderViewStyle modest_header_view_get_style (ModestHeaderView *self) { g_return_val_if_fail (self, FALSE); - return MODEST_HEADER_VIEW_GET_PRIVATE(self)->style; } +/* + * This function sets a sortable model in the header view. It's just + * used for developing purposes, because it only does a + * gtk_tree_view_set_model + */ +static void +modest_header_view_set_model (GtkTreeView *header_view, GtkTreeModel *model) +{ + GtkTreeModel *old_model_sort = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view)); + + if (old_model_sort && GTK_IS_TREE_MODEL_SORT (old_model_sort)) { + GtkTreeModel *old_model; + ModestHeaderViewPrivate *priv; + + priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view); + old_model = gtk_tree_model_sort_get_model (GTK_TREE_MODEL_SORT (old_model_sort)); + /* Set new model */ + gtk_tree_view_set_model (header_view, model); -/* get the length of any prefix that should be ignored for sorting */ -static inline int -get_prefix_len (const gchar *sub) + modest_runtime_verify_object_death (old_model, ""); + modest_runtime_verify_object_death (old_model_sort, ""); + } else + gtk_tree_view_set_model (header_view, model); + + return; +} + +static void +on_progress_changed (ModestMailOperation *mail_op, + ModestHeaderView *self) { - gint i = 0; - const static gchar* prefix[] = {"Re:", "RE:", "Fwd:", "FWD:", "FW:", "AW:", NULL}; - - if (sub[0] != 'R' && sub[0] != 'F') /* optimization */ - return 0; - - while (prefix[i]) { - if (g_str_has_prefix(sub, prefix[i])) { - int prefix_len = strlen(prefix[i]); - if (sub[prefix_len] == ' ') - ++prefix_len; /* ignore space after prefix as well */ - return prefix_len; - } - ++i; + GtkTreeModel *sortable; + ModestHeaderViewPrivate *priv; + GList *cols, *cursor; + TnyList *headers; + + if (!modest_mail_operation_is_finished (mail_op)) + return; + + if (modest_mail_operation_get_error (mail_op)) + return; + + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + + headers = TNY_LIST (tny_gtk_header_list_model_new ()); + + tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers), + priv->folder, TRUE); + + sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers)); + g_object_unref (G_OBJECT (headers)); + + /* install our special sorting functions */ + cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); + while (cursor) { + gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data), + MODEST_HEADER_VIEW_COLUMN)); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), + col_id, + (GtkTreeIterCompareFunc) cmp_rows, + cursor->data, NULL); + cursor = g_list_next(cursor); } - return 0; + g_list_free (cols); + + /* Set new model */ + modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); + g_object_unref (G_OBJECT (sortable)); + + /* Add a folder observer */ + g_mutex_lock (priv->monitor_lock); + priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (priv->folder)); + tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers)); + tny_folder_monitor_start (priv->monitor); + g_mutex_unlock (priv->monitor_lock); } +TnyFolder* +modest_header_view_get_folder (ModestHeaderView *self) +{ + ModestHeaderViewPrivate *priv; + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); -static inline gint -cmp_normalized_subject (const gchar* s1, const gchar *s2) + if (priv->folder) + g_object_ref (priv->folder); + + return priv->folder; +} + +static void +modest_header_view_set_folder_intern (ModestHeaderView *self, TnyFolder *folder) +{ + TnyList *headers; + ModestHeaderViewPrivate *priv; + GList *cols, *cursor; + GtkTreeModel *sortable; + + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + + headers = TNY_LIST (tny_gtk_header_list_model_new ()); + + tny_gtk_header_list_model_set_folder (TNY_GTK_HEADER_LIST_MODEL(headers), + folder, FALSE); + + /* Add a folder observer */ + g_mutex_lock (priv->monitor_lock); + if (priv->monitor) { + tny_folder_monitor_stop (priv->monitor); + g_object_unref (G_OBJECT (priv->monitor)); + } + priv->monitor = TNY_FOLDER_MONITOR (tny_folder_monitor_new (folder)); + tny_folder_monitor_add_list (priv->monitor, TNY_LIST (headers)); + tny_folder_monitor_start (priv->monitor); + g_mutex_unlock (priv->monitor_lock); + + + sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(headers)); + g_object_unref (G_OBJECT (headers)); + + /* install our special sorting functions */ + cursor = cols = gtk_tree_view_get_columns (GTK_TREE_VIEW(self)); + while (cursor) { + gint col_id = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(cursor->data), + MODEST_HEADER_VIEW_COLUMN)); + gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), + col_id, + (GtkTreeIterCompareFunc) cmp_rows, + cursor->data, NULL); + cursor = g_list_next(cursor); + } + g_list_free (cols); + + + /* Set new model */ + modest_header_view_set_model (GTK_TREE_VIEW (self), sortable); + g_object_unref (G_OBJECT (sortable)); +} + +void +modest_header_view_set_folder (ModestHeaderView *self, TnyFolder *folder) { - gint result = 0; - register gchar *n1, *n2; + ModestHeaderViewPrivate *priv; + + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + + if (priv->folder) { + g_object_unref (priv->folder); + priv->folder = NULL; + } + + if (folder) { + ModestMailOperation *mail_op; + + modest_header_view_set_folder_intern (self, folder); + + /* Pick my reference. Nothing to do with the mail operation */ + priv->folder = g_object_ref (folder); + + /* no message selected */ + g_signal_emit (G_OBJECT(self), signals[HEADER_SELECTED_SIGNAL], 0, NULL); + + /* Create the mail operation */ + mail_op = modest_mail_operation_new (); + modest_mail_operation_queue_add (modest_runtime_get_mail_operation_queue (), + mail_op); + + /* Register a mail operation observer */ + g_signal_connect (mail_op, "progress-changed", + G_CALLBACK (on_progress_changed), self); + + /* Refresh the folder asynchronously */ + modest_mail_operation_refresh_folder (mail_op, folder); + + /* Free */ + g_object_unref (mail_op); + + } else { + g_mutex_lock (priv->monitor_lock); + modest_header_view_set_model (GTK_TREE_VIEW (self), NULL); + + if (priv->monitor) { + tny_folder_monitor_stop (priv->monitor); + g_object_unref (G_OBJECT (priv->monitor)); + priv->monitor = NULL; + } + g_mutex_unlock (priv->monitor_lock); + } +} + +static gboolean +on_header_clicked (GtkWidget *widget, GdkEventButton *event, gpointer user_data) +{ + ModestHeaderView *self = NULL; + ModestHeaderViewPrivate *priv = NULL; + GtkTreePath *path = NULL; + GtkTreeIter iter; + GtkTreeModel *model = NULL; + TnyHeader *header; + /* ignore everything but doubleclick */ + if (event->type != GDK_2BUTTON_PRESS) + return FALSE; + + self = MODEST_HEADER_VIEW (widget); + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - n1 = g_utf8_collate_key (s1 + get_prefix_len(s1), -1); - n2 = g_utf8_collate_key (s2 + get_prefix_len(s2), -1); + path = get_selected_row (GTK_TREE_VIEW(self), &model); + if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) + return FALSE; + + /* get the first selected item */ + gtk_tree_model_get (model, &iter, + TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, + &header, -1); + /* Emit signal */ + g_signal_emit (G_OBJECT(self), + signals[HEADER_ACTIVATED_SIGNAL], + 0, header); + + /* Free */ + g_object_unref (G_OBJECT (header)); + gtk_tree_path_free(path); + + return TRUE; +} + + +static void +on_selection_changed (GtkTreeSelection *sel, gpointer user_data) +{ + GtkTreeModel *model; + TnyHeader *header; + GtkTreePath *path = NULL; + GtkTreeIter iter; + ModestHeaderView *self; + ModestHeaderViewPrivate *priv; - result = strcmp (n1, n2); - g_free (n1); - g_free (n2); + g_return_if_fail (sel); + g_return_if_fail (user_data); - return result; + self = MODEST_HEADER_VIEW (user_data); + priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + + path = get_selected_row (GTK_TREE_VIEW(self), &model); + if ((path == NULL) || (!gtk_tree_model_get_iter(model, &iter, path))) + return; /* msg was _un_selected */ + + gtk_tree_model_get (model, &iter, + TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, + &header, -1); + + /* Emit signal */ + g_signal_emit (G_OBJECT(self), + signals[HEADER_SELECTED_SIGNAL], + 0, header); + + g_object_unref (G_OBJECT (header)); + gtk_tree_path_free(path); +} + + +/* PROTECTED method. It's useful when we want to force a given + selection to reload a msg. For example if we have selected a header + in offline mode, when Modest become online, we want to reload the + message automatically without an user click over the header */ +void +_modest_header_view_change_selection (GtkTreeSelection *selection, + gpointer user_data) +{ + g_return_if_fail (GTK_IS_TREE_SELECTION (selection)); + g_return_if_fail (MODEST_IS_HEADER_VIEW (user_data)); + + on_selection_changed (selection, user_data); } @@ -633,51 +946,64 @@ static gint cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2, gpointer user_data) { - gint col_id = GPOINTER_TO_INT (user_data); + gint col_id; gint t1, t2; gint val1, val2; gchar *s1, *s2; gint cmp; - g_return_val_if_fail (GTK_IS_TREE_MODEL(tree_model), -1); + static int counter = 0; + col_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(user_data), MODEST_HEADER_VIEW_COLUMN)); + if (!(++counter % 100)) { + GObject *header_view = g_object_get_data(G_OBJECT(user_data), + MODEST_HEADER_VIEW_PTR); + g_signal_emit (header_view, + signals[STATUS_UPDATE_SIGNAL], + 0, _("Sorting..."), 0, 0); + } switch (col_id) { /* first one, we decide based on the time */ - case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER: + case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_IN: case MODEST_HEADER_VIEW_COLUMN_RECEIVED_DATE: + gtk_tree_model_get (tree_model, iter1, - TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, + TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &t1,-1); gtk_tree_model_get (tree_model, iter2, - TNY_MSG_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, + TNY_GTK_HEADER_LIST_MODEL_DATE_RECEIVED_TIME_T_COLUMN, &t2,-1); return t1 - t2; - + + case MODEST_HEADER_VIEW_COLUMN_COMPACT_HEADER_OUT: case MODEST_HEADER_VIEW_COLUMN_SENT_DATE: gtk_tree_model_get (tree_model, iter1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1); gtk_tree_model_get (tree_model, iter2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1); return t1 - t2; /* next ones, we try the search criteria first, if they're the same, then we use 'sent date' */ + /* FIXME: what about received-date? */ case MODEST_HEADER_VIEW_COLUMN_SUBJECT: { gtk_tree_model_get (tree_model, iter1, - TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, + TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s1, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1); gtk_tree_model_get (tree_model, iter2, - TNY_MSG_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, + TNY_GTK_HEADER_LIST_MODEL_SUBJECT_COLUMN, &s2, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1); - - cmp = cmp_normalized_subject(s1, s2); + /* the prefix ('Re:', 'Fwd:' etc.) we ignore */ + cmp = modest_text_utils_utf8_strcmp (s1 + modest_text_utils_get_subject_prefix_len(s1), + s2 + modest_text_utils_get_subject_prefix_len(s2), + TRUE); g_free (s1); g_free (s2); @@ -687,14 +1013,14 @@ cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2, case MODEST_HEADER_VIEW_COLUMN_FROM: gtk_tree_model_get (tree_model, iter1, - TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, + TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s1, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1); gtk_tree_model_get (tree_model, iter2, - TNY_MSG_HEADER_LIST_MODEL_FROM_COLUMN, &s2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, + TNY_GTK_HEADER_LIST_MODEL_FROM_COLUMN, &s2, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1); - cmp = strcmp (s1, s2); + cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE); g_free (s1); g_free (s2); @@ -703,14 +1029,14 @@ cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2, case MODEST_HEADER_VIEW_COLUMN_TO: gtk_tree_model_get (tree_model, iter1, - TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, + TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s1, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1); gtk_tree_model_get (tree_model, iter2, - TNY_MSG_HEADER_LIST_MODEL_TO_COLUMN, &s2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, + TNY_GTK_HEADER_LIST_MODEL_TO_COLUMN, &s2, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1); - cmp = strcmp (s1, s2); + cmp = modest_text_utils_utf8_strcmp (s1, s2, TRUE); g_free (s1); g_free (s2); @@ -718,22 +1044,22 @@ cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2, case MODEST_HEADER_VIEW_COLUMN_ATTACH: - gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1); - gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1); + gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1, -1); + gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1); - cmp = (val1 & TNY_MSG_HEADER_FLAG_ATTACHMENTS) - - (val2 & TNY_MSG_HEADER_FLAG_ATTACHMENTS); + cmp = (val1 & TNY_HEADER_FLAG_ATTACHMENTS) - + (val2 & TNY_HEADER_FLAG_ATTACHMENTS); return cmp ? cmp : t1 - t2; case MODEST_HEADER_VIEW_COLUMN_MSGTYPE: - gtk_tree_model_get (tree_model, iter1, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1); - gtk_tree_model_get (tree_model, iter2, TNY_MSG_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2, - TNY_MSG_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1); - cmp = (val1 & TNY_MSG_HEADER_FLAG_SEEN) - (val2 & TNY_MSG_HEADER_FLAG_SEEN); + gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val1, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t1,-1); + gtk_tree_model_get (tree_model, iter2, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &val2, + TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2,-1); + cmp = (val1 & TNY_HEADER_FLAG_SEEN) - (val2 & TNY_HEADER_FLAG_SEEN); return cmp ? cmp : t1 - t2; @@ -742,207 +1068,146 @@ cmp_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2, } } - +/* Drag and drop stuff */ static void -refresh_folder (TnyMsgFolderIface *folder, gboolean cancelled, - gpointer user_data) +drag_data_get_cb (GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, guint time, gpointer data) { - GtkTreeModel *oldsortable, *sortable; - ModestHeaderView *self = - MODEST_HEADER_VIEW(user_data); - ModestHeaderViewPrivate *priv; - - g_return_if_fail (self); + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *source_row; - if (cancelled) - return; - - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - - if (!folder) /* when there is no folder */ - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE); - - else { /* it's a new one or a refresh */ - GSList *col; - - priv->headers = TNY_LIST_IFACE(tny_msg_header_list_model_new ()); - - tny_msg_folder_iface_get_headers (folder, priv->headers, FALSE); - tny_msg_header_list_model_set_folder (TNY_MSG_HEADER_LIST_MODEL(priv->headers), - folder, TRUE); /* async */ - - oldsortable = gtk_tree_view_get_model(GTK_TREE_VIEW (self)); - if (oldsortable && GTK_IS_TREE_MODEL_SORT(oldsortable)) { - GtkTreeModel *oldmodel = gtk_tree_model_sort_get_model - (GTK_TREE_MODEL_SORT(oldsortable)); - if (oldmodel) - g_object_unref (G_OBJECT(oldmodel)); - g_object_unref (oldsortable); - } - - sortable = gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL(priv->headers)); - - /* install our special sorting functions */ - col = priv->columns; - while (col) { - gint col_id = GPOINTER_TO_INT (col->data); - gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(sortable), col_id, - (GtkTreeIterCompareFunc)cmp_rows, - GINT_TO_POINTER(col_id), NULL); - col = col->next; + source_row = get_selected_row (GTK_TREE_VIEW(widget), &model); + if ((source_row == NULL) || (!gtk_tree_model_get_iter(model, &iter, source_row))) return; + + switch (info) { + case MODEST_HEADER_ROW: + gtk_tree_set_row_drag_data (selection_data, model, source_row); + break; + case MODEST_MSG: { + TnyHeader *hdr; + gtk_tree_model_get (model, &iter, + TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &hdr, + -1); + if (hdr) { + g_object_unref (G_OBJECT(hdr)); } - - gtk_tree_view_set_model (GTK_TREE_VIEW (self), sortable); - gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(self), TRUE); - /* no need to unref sortable */ + break; + } + default: + g_message ("default"); } + gtk_tree_path_free (source_row); } +/* Header view drag types */ +const GtkTargetEntry header_view_drag_types[] = { + { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }, + { "text/uri-list", 0, MODEST_MSG }, + +}; static void -refresh_folder_status_update (TnyMsgFolderIface *folder, const gchar *msg, - gint status_id, gpointer user_data) +setup_drag_and_drop (GtkTreeView *self) { - ModestHeaderView *self; - ModestHeaderViewPrivate *priv; - - self = MODEST_HEADER_VIEW (user_data); - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - - g_signal_emit (G_OBJECT(self), - signals[STATUS_UPDATE_SIGNAL], 0, - msg, status_id); - if (msg) - g_timeout_add (750, - (GSourceFunc)refresh_folder_finish_status_update, - self); - - priv->status_id = status_id; + gtk_drag_source_set (GTK_WIDGET (self), + GDK_BUTTON1_MASK, + header_view_drag_types, + G_N_ELEMENTS (header_view_drag_types), + GDK_ACTION_MOVE | GDK_ACTION_COPY); + + g_signal_connect(G_OBJECT (self), "drag_data_get", + G_CALLBACK(drag_data_get_cb), NULL); } - -static gboolean -refresh_folder_finish_status_update (gpointer user_data) +static GtkTreePath * +get_selected_row (GtkTreeView *self, GtkTreeModel **model) { - ModestHeaderView *self; - ModestHeaderViewPrivate *priv; + GtkTreePath *path = NULL; + GtkTreeSelection *sel = NULL; + GList *rows = NULL; + + sel = gtk_tree_view_get_selection(self); + rows = gtk_tree_selection_get_selected_rows (sel, model); - self = MODEST_HEADER_VIEW (user_data); - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); + if ((rows == NULL) || (g_list_length(rows) != 1)) + goto frees; - if (priv->status_id == 0) - return FALSE; + path = gtk_tree_path_copy(g_list_nth_data (rows, 0)); - refresh_folder_status_update (NULL, NULL, priv->status_id, - user_data); - priv->status_id = 0; - return FALSE; -} + /* Free */ + frees: + g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL); + g_list_free(rows); + return path; +} -gboolean -modest_header_view_set_folder (ModestHeaderView *self, - TnyMsgFolderIface *folder) +/* + * This function moves the tree view scroll to the current selected + * row when the widget grabs the focus + */ +static gboolean +on_focus_in (GtkWidget *self, + GdkEventFocus *event, + gpointer user_data) { - ModestHeaderViewPrivate *priv; - - g_return_val_if_fail (MODEST_IS_HEADER_VIEW (self), FALSE); + GtkTreeSelection *selection; + GtkTreeModel *model; + GList *selected = NULL; + GtkTreePath *selected_path = NULL; - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - - g_mutex_lock (priv->lock); - - if (!folder) {/* when there is no folder */ - GtkTreeModel *model; - model = gtk_tree_view_get_model (GTK_TREE_VIEW(self)); - gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(self), FALSE); - gtk_tree_view_set_model (GTK_TREE_VIEW (self), NULL); - if (model) - g_object_unref (model); - } - else { /* it's a new one or a refresh */ - tny_msg_folder_iface_refresh_async (folder, - refresh_folder, - refresh_folder_status_update, - self); - } - - /* no message selected */ - g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0, - NULL); - - g_mutex_unlock (priv->lock); - - return TRUE; -} + model = gtk_tree_view_get_model (GTK_TREE_VIEW (self)); + if (!model) + return FALSE; + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self)); + /* If none selected yet, pick the first one */ + if (gtk_tree_selection_count_selected_rows (selection) == 0) { + GtkTreeIter iter; + GtkTreePath *path; + /* Return if the model is empty */ + if (!gtk_tree_model_get_iter_first (model, &iter)) + return FALSE; -static void -on_selection_changed (GtkTreeSelection *sel, gpointer user_data) -{ - GtkTreeModel *model; - TnyMsgHeaderIface *header; - GtkTreeIter iter; - ModestHeaderView *self; - ModestHeaderViewPrivate *priv; - - g_return_if_fail (sel); - g_return_if_fail (user_data); - - self = MODEST_HEADER_VIEW (user_data); - priv = MODEST_HEADER_VIEW_GET_PRIVATE(self); - - - if (!gtk_tree_selection_get_selected (sel, &model, &iter)) - return; /* msg was _un_selected */ + path = gtk_tree_model_get_path (model, &iter); + gtk_tree_selection_select_path (selection, path); + gtk_tree_path_free (path); + } - //g_mutex_lock (priv->lock); - - gtk_tree_model_get (model, &iter, - TNY_MSG_HEADER_LIST_MODEL_INSTANCE_COLUMN, - &header, -1); - - if (header) { - const TnyMsgIface *msg = NULL; - const TnyMsgFolderIface *folder; - - folder = tny_msg_header_iface_get_folder (TNY_MSG_HEADER_IFACE(header)); - if (!folder) - g_printerr ("modest: cannot find folder\n"); - else { - msg = tny_msg_folder_iface_get_message (TNY_MSG_FOLDER_IFACE(folder), - header); - if (!msg) { - g_printerr ("modest: cannot find msg\n"); - gtk_tree_store_remove (GTK_TREE_STORE(model), - &iter); - } + /* Need to get the all the rows because is selection multiple */ + selected = gtk_tree_selection_get_selected_rows (selection, &model); + selected_path = (GtkTreePath *) selected->data; + + /* Check if we need to scroll */ + #if GTK_CHECK_VERSION(2, 8, 0) /* TODO: gtk_tree_view_get_visible_range() is only available in GTK+ 2.8 */ + GtkTreePath *start_path = NULL; + GtkTreePath *end_path = NULL; + if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (self), + &start_path, + &end_path)) { + + if ((gtk_tree_path_compare (start_path, selected_path) != -1) || + (gtk_tree_path_compare (end_path, selected_path) != 1)) { + + /* Scroll to first path */ + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (self), + selected_path, + NULL, + TRUE, + 0.5, + 0.0); } - - g_signal_emit (G_OBJECT(self), signals[MESSAGE_SELECTED_SIGNAL], 0, - msg); - - /* mark message as seen; _set_flags crashes, bug in tinymail? */ - //flags = tny_msg_header_iface_get_flags (TNY_MSG_HEADER_IFACE(header)); - //tny_msg_header_iface_set_flags (header, TNY_MSG_HEADER_FLAG_SEEN); } + #endif /* GTK_CHECK_VERSION */ - // g_mutex_unlock (priv->lock); - -} - + /* Frees */ + g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL); + g_list_free (selected); -/* static void */ -/* on_column_clicked (GtkTreeViewColumn *col, gpointer user_data) */ -/* { */ -/* GtkTreeView *treeview; */ -/* gint id; */ - -/* treeview = GTK_TREE_VIEW (user_data); */ -/* id = gtk_tree_view_column_get_sort_column_id (col); */ - -/* gtk_tree_view_set_search_column (treeview, id); */ -/* } */ + return FALSE; +}