+
+static gint
+cmp_subject_rows (GtkTreeModel *tree_model, GtkTreeIter *iter1, GtkTreeIter *iter2,
+ gpointer user_data)
+{
+ gint t1, t2;
+ gchar *val1, *val2;
+ gint cmp;
+/* static int counter = 0; */
+
+ g_return_val_if_fail (GTK_IS_TREE_VIEW_COLUMN(user_data), 0);
+
+ gtk_tree_model_get (tree_model, iter1, TNY_GTK_HEADER_LIST_MODEL_SUBJECT_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_SUBJECT_COLUMN, &val2,
+ TNY_GTK_HEADER_LIST_MODEL_DATE_SENT_TIME_T_COLUMN, &t2, -1);
+
+ cmp = modest_text_utils_utf8_strcmp (val1 + modest_text_utils_get_subject_prefix_len(val1),
+ val2 + modest_text_utils_get_subject_prefix_len(val2),
+ TRUE);
+ g_free (val1);
+ g_free (val2);
+ return cmp;
+}
+
+/* Drag and drop stuff */
+static void
+drag_data_get_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ GtkSelectionData *selection_data,
+ guint info,
+ guint time,
+ gpointer data)
+{
+ ModestHeaderView *self = NULL;
+ ModestHeaderViewPrivate *priv = NULL;
+
+ self = MODEST_HEADER_VIEW (widget);
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
+
+ /* Set the data. Do not use the current selection because it
+ could be different than the selection at the beginning of
+ the d&d */
+ modest_dnd_selection_data_set_paths (selection_data,
+ priv->drag_begin_cached_selected_rows);
+}
+
+/**
+ * We're caching the selected rows at the beginning because the
+ * selection could change between drag-begin and drag-data-get, for
+ * example if we have a set of rows already selected, and then we
+ * click in one of them (without SHIFT key pressed) and begin a drag,
+ * the selection at that moment contains all the selected lines, but
+ * after dropping the selection, the release event provokes that only
+ * the row used to begin the drag is selected, so at the end the
+ * drag&drop affects only one rows instead of all the selected ones.
+ *
+ */
+static void
+drag_begin_cb (GtkWidget *widget,
+ GdkDragContext *context,
+ gpointer data)
+{
+ ModestHeaderView *self = NULL;
+ ModestHeaderViewPrivate *priv = NULL;
+ GtkTreeSelection *selection;
+
+ self = MODEST_HEADER_VIEW (widget);
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+ priv->drag_begin_cached_selected_rows =
+ gtk_tree_selection_get_selected_rows (selection, NULL);
+}
+
+/**
+ * We use the drag-end signal to clear the cached selection, we use
+ * this because this allways happens, whether or not the d&d was a
+ * success
+ */
+static void
+drag_end_cb (GtkWidget *widget,
+ GdkDragContext *dc,
+ gpointer data)
+{
+ ModestHeaderView *self = NULL;
+ ModestHeaderViewPrivate *priv = NULL;
+
+ self = MODEST_HEADER_VIEW (widget);
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(self);
+
+ /* Free cached data */
+ g_list_foreach (priv->drag_begin_cached_selected_rows, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (priv->drag_begin_cached_selected_rows);
+ priv->drag_begin_cached_selected_rows = NULL;
+}
+
+/* Header view drag types */
+const GtkTargetEntry header_view_drag_types[] = {
+ { GTK_TREE_PATH_AS_STRING_LIST, GTK_TARGET_SAME_APP, MODEST_HEADER_ROW }
+};
+
+static void
+enable_drag_and_drop (GtkWidget *self)
+{
+ gtk_drag_source_set (self, GDK_BUTTON1_MASK,
+ header_view_drag_types,
+ G_N_ELEMENTS (header_view_drag_types),
+ GDK_ACTION_MOVE | GDK_ACTION_COPY);
+}
+
+static void
+disable_drag_and_drop (GtkWidget *self)
+{
+ gtk_drag_source_unset (self);
+}
+
+static void
+setup_drag_and_drop (GtkWidget *self)
+{
+ enable_drag_and_drop(self);
+ g_signal_connect(G_OBJECT (self), "drag_data_get",
+ G_CALLBACK(drag_data_get_cb), NULL);
+
+ g_signal_connect(G_OBJECT (self), "drag_begin",
+ G_CALLBACK(drag_begin_cb), NULL);
+
+ g_signal_connect(G_OBJECT (self), "drag_end",
+ G_CALLBACK(drag_end_cb), NULL);
+}
+
+static GtkTreePath *
+get_selected_row (GtkTreeView *self, GtkTreeModel **model)
+{
+ 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);
+
+ if ((rows == NULL) || (g_list_length(rows) != 1))
+ goto frees;
+
+ path = gtk_tree_path_copy(g_list_nth_data (rows, 0));
+
+
+ /* Free */
+ frees:
+ g_list_foreach(rows,(GFunc) gtk_tree_path_free, NULL);
+ g_list_free(rows);
+
+ return path;
+}
+
+/*
+ * 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)
+{
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GList *selected = NULL;
+ GtkTreePath *selected_path = NULL;
+
+ 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;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+ }
+
+ /* Need to get the all the rows because is selection multiple */
+ selected = gtk_tree_selection_get_selected_rows (selection, &model);
+ if (selected == NULL) return FALSE;
+ selected_path = (GtkTreePath *) selected->data;
+
+ /* Frees */
+ g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected);
+
+ return FALSE;
+}
+
+static gboolean
+on_focus_out (GtkWidget *self,
+ GdkEventFocus *event,
+ gpointer user_data)
+{
+
+ if (!gtk_widget_is_focus (self)) {
+ GtkTreeSelection *selection = NULL;
+ GList *selected_rows = NULL;
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (self));
+ if (gtk_tree_selection_count_selected_rows (selection) > 1) {
+ selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);
+ g_signal_handlers_block_by_func (selection, on_selection_changed, self);
+ gtk_tree_selection_unselect_all (selection);
+ gtk_tree_selection_select_path (selection, (GtkTreePath *) selected_rows->data);
+ g_signal_handlers_unblock_by_func (selection, on_selection_changed, self);
+ g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
+ g_list_free (selected_rows);
+ }
+ }
+ return FALSE;
+}
+
+static gboolean
+on_button_release_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
+{
+ enable_drag_and_drop(self);
+ return FALSE;
+}
+
+static gboolean
+on_button_press_event(GtkWidget * self, GdkEventButton * event, gpointer userdata)
+{
+ GtkTreeSelection *selection = NULL;
+ GtkTreePath *path = NULL;
+ gboolean already_selected = FALSE, already_opened = FALSE;
+ ModestTnySendQueueStatus status = MODEST_TNY_SEND_QUEUE_UNKNOWN;
+
+ if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(self), event->x, event->y, &path, NULL, NULL, NULL)) {
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+
+ selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
+ already_selected = gtk_tree_selection_path_is_selected (selection, path);
+
+ /* Get header from model */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (self));
+ if (gtk_tree_model_get_iter (model, &iter, path)) {
+ GValue value = {0,};
+ TnyHeader *header;
+
+ gtk_tree_model_get_value (model, &iter,
+ TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN,
+ &value);
+ header = (TnyHeader *) g_value_get_object (&value);
+ if (TNY_IS_HEADER (header)) {
+ status = modest_tny_all_send_queues_get_msg_status (header);
+ already_opened = modest_window_mgr_find_registered_header (modest_runtime_get_window_mgr (),
+ header, NULL);
+ }
+ g_value_unset (&value);
+ }
+ }
+
+ /* Enable drag and drop only if the user clicks on a row that
+ it's already selected. If not, let him select items using
+ the pointer. If the message is in an OUTBOX and in sending
+ status disable drag and drop as well */
+ if (!already_selected ||
+ status == MODEST_TNY_SEND_QUEUE_SENDING ||
+ already_opened)
+ disable_drag_and_drop(self);
+
+ if (path != NULL)
+ gtk_tree_path_free(path);
+
+ /* If it's already opened then do not let the button-press
+ event go on because it'll perform a message open because
+ we're clicking on to an already selected header */
+ return FALSE;
+}
+
+static void
+folder_monitor_update (TnyFolderObserver *self,
+ TnyFolderChange *change)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ TnyFolderChangeChanged changed;
+ TnyFolder *folder = NULL;
+
+ changed = tny_folder_change_get_changed (change);
+
+ /* Do not notify the observers if the folder of the header
+ view has changed before this call to the observer
+ happens */
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
+ folder = tny_folder_change_get_folder (change);
+ if (folder != priv->folder)
+ goto frees;
+
+ MODEST_DEBUG_BLOCK (
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS)
+ g_print ("ADDED %d/%d (r/t) \n",
+ tny_folder_change_get_new_unread_count (change),
+ tny_folder_change_get_new_all_count (change));
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_ALL_COUNT)
+ g_print ("ALL COUNT %d\n",
+ tny_folder_change_get_new_all_count (change));
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_UNREAD_COUNT)
+ g_print ("UNREAD COUNT %d\n",
+ tny_folder_change_get_new_unread_count (change));
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)
+ g_print ("EXPUNGED %d/%d (r/t) \n",
+ tny_folder_change_get_new_unread_count (change),
+ tny_folder_change_get_new_all_count (change));
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_FOLDER_RENAME)
+ g_print ("FOLDER RENAME\n");
+ if (changed & TNY_FOLDER_CHANGE_CHANGED_MSG_RECEIVED)
+ g_print ("MSG RECEIVED %d/%d (r/t) \n",
+ tny_folder_change_get_new_unread_count (change),
+ tny_folder_change_get_new_all_count (change));
+ g_print ("---------------------------------------------------\n");
+ );
+
+ /* Check folder count */
+ if ((changed & TNY_FOLDER_CHANGE_CHANGED_ADDED_HEADERS) ||
+ (changed & TNY_FOLDER_CHANGE_CHANGED_EXPUNGED_HEADERS)) {
+
+ g_mutex_lock (priv->observers_lock);
+
+ /* Emit signal to evaluate how headers changes affects
+ to the window view */
+ g_signal_emit (G_OBJECT(self),
+ signals[MSG_COUNT_CHANGED_SIGNAL],
+ 0, folder, change);
+
+ /* Added or removed headers, so data stored on cliboard are invalid */
+ if (modest_email_clipboard_check_source_folder (priv->clipboard, folder))
+ modest_email_clipboard_clear (priv->clipboard);
+
+ g_mutex_unlock (priv->observers_lock);
+ }
+
+ /* Free */
+ frees:
+ if (folder != NULL)
+ g_object_unref (folder);
+}
+
+gboolean
+modest_header_view_is_empty (ModestHeaderView *self)
+{
+ ModestHeaderViewPrivate *priv;
+
+ g_return_val_if_fail (self && MODEST_IS_HEADER_VIEW(self), TRUE);
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (MODEST_HEADER_VIEW (self));
+
+ return priv->status == HEADER_VIEW_EMPTY;
+}
+
+void
+modest_header_view_clear (ModestHeaderView *self)
+{
+ g_return_if_fail (self && MODEST_IS_HEADER_VIEW(self));
+
+ modest_header_view_set_folder (self, NULL, FALSE, NULL, NULL);
+}
+
+void
+modest_header_view_copy_selection (ModestHeaderView *header_view)
+{
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
+
+ /* Copy selection */
+ _clipboard_set_selected_data (header_view, FALSE);
+}
+
+void
+modest_header_view_cut_selection (ModestHeaderView *header_view)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ const gchar **hidding = NULL;
+ guint i, n_selected;
+
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
+
+ /* Copy selection */
+ _clipboard_set_selected_data (header_view, TRUE);
+
+ /* Get hidding ids */
+ hidding = modest_email_clipboard_get_hidding_ids (priv->clipboard, &n_selected);
+
+ /* Clear hidding array created by previous cut operation */
+ _clear_hidding_filter (MODEST_HEADER_VIEW (header_view));
+
+ /* Copy hidding array */
+ priv->n_selected = n_selected;
+ priv->hidding_ids = g_malloc0(sizeof(gchar *) * n_selected);
+ for (i=0; i < n_selected; i++)
+ priv->hidding_ids[i] = g_strdup(hidding[i]);
+
+ /* Hide cut headers */
+ modest_header_view_refilter (header_view);
+}
+
+
+
+
+static void
+_clipboard_set_selected_data (ModestHeaderView *header_view,
+ gboolean delete)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ TnyList *headers = NULL;
+
+ g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (header_view);
+
+ /* Set selected data on clipboard */
+ g_return_if_fail (MODEST_IS_EMAIL_CLIPBOARD (priv->clipboard));
+ headers = modest_header_view_get_selected_headers (header_view);
+ modest_email_clipboard_set_data (priv->clipboard, priv->folder, headers, delete);
+
+ /* Free */
+ g_object_unref (headers);
+}
+
+typedef struct {
+ ModestHeaderView *self;
+ TnyFolder *folder;
+} NotifyFilterInfo;
+
+static gboolean
+notify_filter_change (gpointer data)
+{
+ NotifyFilterInfo *info = (NotifyFilterInfo *) data;
+
+ g_signal_emit (info->self,
+ signals[MSG_COUNT_CHANGED_SIGNAL],
+ 0, info->folder, NULL);
+
+ return FALSE;
+}
+
+static void
+notify_filter_change_destroy (gpointer data)
+{
+ NotifyFilterInfo *info = (NotifyFilterInfo *) data;
+ ModestHeaderViewPrivate *priv;
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (info->self);
+ priv->status_timeout = 0;
+
+ g_object_unref (info->self);
+ g_object_unref (info->folder);
+ g_slice_free (NotifyFilterInfo, info);
+}
+
+static gboolean
+filter_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer user_data)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ TnyHeaderFlags flags;
+ TnyHeader *header = NULL;
+ guint i;
+ gchar *id = NULL;
+ gboolean visible = TRUE;
+ gboolean found = FALSE;
+ GValue value = {0,};
+ HeaderViewStatus old_status;
+
+ g_return_val_if_fail (MODEST_IS_HEADER_VIEW (user_data), FALSE);
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
+
+ /* Get header from model */
+ gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_FLAGS_COLUMN, &value);
+ flags = (TnyHeaderFlags) g_value_get_int (&value);
+ g_value_unset (&value);
+ gtk_tree_model_get_value (model, iter, TNY_GTK_HEADER_LIST_MODEL_INSTANCE_COLUMN, &value);
+ header = (TnyHeader *) g_value_get_object (&value);
+ g_value_unset (&value);
+
+ /* Hide deleted and mark as deleted heders */
+ if (flags & TNY_HEADER_FLAG_DELETED ||
+ flags & TNY_HEADER_FLAG_EXPUNGED) {
+ visible = FALSE;
+ goto frees;
+ }
+
+ /* If no data on clipboard, return always TRUE */
+ if (modest_email_clipboard_cleared(priv->clipboard)) {
+ visible = TRUE;
+ goto frees;
+ }
+
+ /* Get message id from header (ensure is a valid id) */
+ if (!header) {
+ visible = FALSE;
+ goto frees;
+ }
+
+ /* Check hiding */
+ if (priv->hidding_ids != NULL) {
+ id = tny_header_dup_message_id (header);
+ for (i=0; i < priv->n_selected && !found; i++)
+ if (priv->hidding_ids[i] != NULL && id != NULL)
+ found = (!strcmp (priv->hidding_ids[i], id));
+
+ visible = !found;
+ g_free(id);
+ }
+
+ frees:
+ old_status = priv->status;
+ priv->status = ((gboolean) priv->status) && !visible;
+ if ((priv->notify_status) && (priv->status != old_status)) {
+ NotifyFilterInfo *info;
+
+ if (priv->status_timeout)
+ g_source_remove (priv->status_timeout);
+
+ info = g_slice_new0 (NotifyFilterInfo);
+ info->self = g_object_ref (G_OBJECT (user_data));
+ info->folder = tny_header_get_folder (header);
+ priv->status_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT, 1000,
+ notify_filter_change,
+ info,
+ notify_filter_change_destroy);
+ }
+
+ return visible;
+}
+
+static void
+_clear_hidding_filter (ModestHeaderView *header_view)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ guint i;
+
+ g_return_if_fail (MODEST_IS_HEADER_VIEW (header_view));
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
+
+ if (priv->hidding_ids != NULL) {
+ for (i=0; i < priv->n_selected; i++)
+ g_free (priv->hidding_ids[i]);
+ g_free(priv->hidding_ids);
+ }
+}
+
+void
+modest_header_view_refilter (ModestHeaderView *header_view)
+{
+ GtkTreeModel *model = NULL;
+ ModestHeaderViewPrivate *priv = NULL;
+
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW (header_view));
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
+
+ /* Hide cut headers */
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (header_view));
+ if (GTK_IS_TREE_MODEL_FILTER (model)) {
+ priv->status = HEADER_VIEW_INIT;
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
+ }
+}
+
+/*
+ * Called when an account is removed. If I'm showing a folder of the
+ * account that has been removed then clear the view
+ */
+static void
+on_account_removed (TnyAccountStore *self,
+ TnyAccount *account,
+ gpointer user_data)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+
+ /* Ignore changes in transport accounts */
+ if (TNY_IS_TRANSPORT_ACCOUNT (account))
+ return;
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE (user_data);
+
+ if (priv->folder) {
+ TnyAccount *my_account;
+
+ my_account = tny_folder_get_account (priv->folder);
+ if (my_account == account)
+ modest_header_view_clear (MODEST_HEADER_VIEW (user_data));
+ g_object_unref (my_account);
+ }
+}
+
+void
+modest_header_view_add_observer(ModestHeaderView *header_view,
+ ModestHeaderViewObserver *observer)
+{
+ ModestHeaderViewPrivate *priv;
+
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
+ g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
+
+ g_mutex_lock(priv->observer_list_lock);
+ priv->observer_list = g_slist_prepend(priv->observer_list, observer);
+ g_mutex_unlock(priv->observer_list_lock);
+}
+
+void
+modest_header_view_remove_observer(ModestHeaderView *header_view,
+ ModestHeaderViewObserver *observer)
+{
+ ModestHeaderViewPrivate *priv;
+
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
+ g_return_if_fail (observer && MODEST_IS_HEADER_VIEW_OBSERVER(observer));
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
+
+ g_mutex_lock(priv->observer_list_lock);
+ priv->observer_list = g_slist_remove(priv->observer_list, observer);
+ g_mutex_unlock(priv->observer_list_lock);
+}
+
+static void
+modest_header_view_notify_observers(ModestHeaderView *header_view,
+ GtkTreeModel *model,
+ const gchar *tny_folder_id)
+{
+ ModestHeaderViewPrivate *priv = NULL;
+ GSList *iter;
+ ModestHeaderViewObserver *observer;
+
+
+ g_return_if_fail (header_view && MODEST_IS_HEADER_VIEW(header_view));
+
+ priv = MODEST_HEADER_VIEW_GET_PRIVATE(header_view);
+
+ g_mutex_lock(priv->observer_list_lock);
+ iter = priv->observer_list;
+ while(iter != NULL){
+ observer = MODEST_HEADER_VIEW_OBSERVER(iter->data);
+ modest_header_view_observer_update(observer, model,
+ tny_folder_id);
+ iter = g_slist_next(iter);
+ }
+ g_mutex_unlock(priv->observer_list_lock);
+}
+