Files
evolution/mail/em-folder-tree.c
Matthew Barnes a133df0caf Add an "ellipsize" property to EMFolderTree.
So we don't have to access GConf directly from EMFolderTree.  The
property gets bound to an EShellSettings property, which is in turn
bound to the "no_folder_dots" GConf key by way of a transform function.
2010-10-18 16:00:29 -04:00

3147 lines
81 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Authors:
* Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <libxml/tree.h>
#include <glib/gi18n.h>
#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include "e-util/e-account-utils.h"
#include "e-util/e-mktemp.h"
#include "e-util/e-icon-factory.h"
#include "e-util/e-alert-dialog.h"
#include "e-util/e-util.h"
#include "misc/e-selectable.h"
#include "em-vfolder-rule.h"
#include "mail-mt.h"
#include "mail-ops.h"
#include "mail-tools.h"
#include "mail-send-recv.h"
#include "mail-vfolder.h"
#include "em-utils.h"
#include "em-folder-tree.h"
#include "em-folder-utils.h"
#include "em-folder-selector.h"
#include "em-folder-properties.h"
#include "em-event.h"
#include "e-mail-local.h"
#include "e-mail-session.h"
/* backward-compatibility cruft */
#include "e-util/gtk-compat.h"
#define d(x)
#define EM_FOLDER_TREE_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), EM_TYPE_FOLDER_TREE, EMFolderTreePrivate))
struct _selected_uri {
gchar *key; /* store:path or account/path */
gchar *uri;
CamelStore *store;
gchar *path;
};
struct _EMFolderTreePrivate {
EMailSession *session;
/* selected_uri structures of each path pending selection. */
GSList *select_uris;
/* Removed as they're encountered, so use this
* to find URI's not presnet but selected. */
GHashTable *select_uris_table;
guint32 excluded;
gboolean (*excluded_func) (EMFolderTree *folder_tree,
GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
gpointer excluded_data;
guint cursor_set:1; /* set to TRUE means we or something
* else has set the cursor, otherwise
* we need to set it when we set the
* selection */
guint autoscroll_id;
guint autoexpand_id;
GtkTreeRowReference *autoexpand_row;
guint loading_row_id;
guint loaded_row_id;
GtkTreeRowReference *drag_row;
gboolean skip_double_click;
GtkCellRenderer *text_renderer;
PangoEllipsizeMode ellipsize;
GtkWidget *selectable; /* an ESelectable, where to pass selectable calls */
/* Signal handler IDs */
gulong selection_changed_handler_id;
};
enum {
PROP_0,
PROP_ELLIPSIZE,
PROP_SESSION
};
enum {
FOLDER_ACTIVATED, /* aka double-clicked or user hit enter */
FOLDER_SELECTED,
POPUP_EVENT,
HIDDEN_KEY_EVENT,
LAST_SIGNAL
};
/* Drag & Drop types */
enum DndDragType {
DND_DRAG_TYPE_FOLDER, /* drag an evo folder */
DND_DRAG_TYPE_TEXT_URI_LIST, /* drag to an mbox file */
NUM_DRAG_TYPES
};
enum DndDropType {
DND_DROP_TYPE_UID_LIST, /* drop a list of message uids */
DND_DROP_TYPE_FOLDER, /* drop an evo folder */
DND_DROP_TYPE_MESSAGE_RFC822, /* drop a message/rfc822 stream */
DND_DROP_TYPE_TEXT_URI_LIST, /* drop an mbox file */
NUM_DROP_TYPES
};
static GtkTargetEntry drag_types[] = {
{ (gchar *) "x-folder", 0, DND_DRAG_TYPE_FOLDER },
{ (gchar *) "text/uri-list", 0, DND_DRAG_TYPE_TEXT_URI_LIST },
};
static GtkTargetEntry drop_types[] = {
{ (gchar *) "x-uid-list" , 0, DND_DROP_TYPE_UID_LIST },
{ (gchar *) "x-folder", 0, DND_DROP_TYPE_FOLDER },
{ (gchar *) "message/rfc822", 0, DND_DROP_TYPE_MESSAGE_RFC822 },
{ (gchar *) "text/uri-list", 0, DND_DROP_TYPE_TEXT_URI_LIST },
};
static GdkAtom drag_atoms[NUM_DRAG_TYPES];
static GdkAtom drop_atoms[NUM_DROP_TYPES];
static guint signals[LAST_SIGNAL] = { 0 };
extern CamelStore *vfolder_store;
struct _folder_tree_selection_data {
GtkTreeModel *model;
GtkTreeIter *iter;
gboolean set;
};
static gpointer parent_class = NULL;
struct _EMFolderTreeGetFolderInfo {
MailMsg base;
/* input data */
GtkTreeRowReference *root;
EMFolderTree *folder_tree;
CamelStore *store;
guint32 flags;
gchar *top;
/* output data */
CamelFolderInfo *fi;
};
static gchar *
folder_tree_get_folder_info__desc (struct _EMFolderTreeGetFolderInfo *m)
{
gchar *ret, *name;
name = camel_service_get_name ((CamelService *)m->store, TRUE);
ret = g_strdup_printf(_("Scanning folders in \"%s\""), name);
g_free (name);
return ret;
}
static void
folder_tree_get_folder_info__exec (struct _EMFolderTreeGetFolderInfo *m)
{
guint32 flags = m->flags | CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
m->fi = camel_store_get_folder_info_sync (
m->store, m->top, flags,
m->base.cancellable, &m->base.error);
/* XXX POP3 stores always return an error because they have
* no folder hierarchy to scan. Clear that error so the
* user doesn't see it. */
if (g_error_matches (m->base.error,
CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER))
g_clear_error (&m->base.error);
}
static void
folder_tree_get_folder_info__done (struct _EMFolderTreeGetFolderInfo *m)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeIter root, iter, titer;
CamelFolderInfo *fi;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
gboolean is_store, need_add_node;
/* check that we haven't been destroyed */
g_return_if_fail (GTK_IS_TREE_VIEW (m->folder_tree));
/* check that our parent folder hasn't been deleted/unsubscribed */
if (!gtk_tree_row_reference_valid (m->root))
return;
tree_view = GTK_TREE_VIEW (m->folder_tree);
model = gtk_tree_view_get_model (tree_view);
si = em_folder_tree_model_lookup_store_info (
EM_FOLDER_TREE_MODEL (model), m->store);
if (si == NULL) {
/* store has been removed in the interim - do nothing */
return;
}
path = gtk_tree_row_reference_get_path (m->root);
gtk_tree_model_get_iter (model, &root, path);
/* If we had an error, then we need to re-set the
* load subdirs state and collapse the node. */
if (!m->fi && m->base.error != NULL) {
gtk_tree_store_set (
GTK_TREE_STORE (model), &root,
COL_BOOL_LOAD_SUBDIRS, TRUE, -1);
gtk_tree_view_collapse_row (tree_view, path);
gtk_tree_path_free (path);
return;
}
gtk_tree_path_free (path);
/* make sure we still need to load the tree subfolders... */
gtk_tree_model_get (model, &root, COL_BOOL_IS_STORE, &is_store, -1);
/* get the first child (which will be a dummy node) */
gtk_tree_model_iter_children (model, &iter, &root);
need_add_node = TRUE;
/* Traverse to the last valid iter, or the "Loading..." node */
do {
gboolean is_store_node = FALSE, is_folder_node = FALSE;
titer = iter; /* Preserve the last valid iter */
gtk_tree_model_get (
model, &iter, COL_BOOL_IS_STORE, &is_store_node,
COL_BOOL_IS_FOLDER, &is_folder_node, -1);
/* stop on a "Loading..." node */
if (!is_store_node && !is_folder_node) {
/* remember it found a "Loading..." node and overwrite or remove it later */
need_add_node = FALSE;
break;
}
} while (gtk_tree_model_iter_next (model, &iter));
iter = titer;
/* FIXME: camel's IMAP code is totally on crack here, @top's
* folder info should be @fi and fi->child should be what we
* want to fill our tree with... *sigh* */
if (m->top && m->fi && !strcmp (m->fi->full_name, m->top)) {
if (!(fi = m->fi->child))
fi = m->fi->next;
} else
fi = m->fi;
if (fi == NULL) {
/* no children afterall... remove the "Loading..." placeholder node */
if (!need_add_node)
gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
if (is_store) {
path = gtk_tree_model_get_path (model, &root);
gtk_tree_view_collapse_row (tree_view, path);
gtk_tree_path_free (path);
return;
}
} else {
gint fully_loaded = (m->flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? TRUE : FALSE;
do {
if (g_hash_table_lookup (si->full_hash, fi->full_name) == NULL) {
if (need_add_node)
gtk_tree_store_append (GTK_TREE_STORE (model), &iter, &root);
need_add_node = TRUE;
em_folder_tree_model_set_folder_info (
EM_FOLDER_TREE_MODEL (model),
&iter, si, fi, fully_loaded);
}
fi = fi->next;
} while (fi != NULL);
/* all children are known, remove the "Loading..." node */
if (!need_add_node)
gtk_tree_store_remove (GTK_TREE_STORE (model), &iter);
}
gtk_tree_store_set (
GTK_TREE_STORE (model), &root,
COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
}
static void
folder_tree_get_folder_info__free (struct _EMFolderTreeGetFolderInfo *m)
{
camel_store_free_folder_info (m->store, m->fi);
gtk_tree_row_reference_free (m->root);
g_object_unref (m->folder_tree);
g_object_unref (m->store);
g_free (m->top);
}
static MailMsgInfo get_folder_info_info = {
sizeof (struct _EMFolderTreeGetFolderInfo),
(MailMsgDescFunc) folder_tree_get_folder_info__desc,
(MailMsgExecFunc) folder_tree_get_folder_info__exec,
(MailMsgDoneFunc) folder_tree_get_folder_info__done,
(MailMsgFreeFunc) folder_tree_get_folder_info__free
};
static void
folder_tree_emit_popup_event (EMFolderTree *folder_tree,
GdkEvent *event)
{
g_signal_emit (folder_tree, signals[POPUP_EVENT], 0, event);
}
static void
folder_tree_free_select_uri (struct _selected_uri *u)
{
g_free (u->uri);
if (u->store)
g_object_unref (u->store);
g_free (u->key);
g_free (u->path);
g_free (u);
}
static gboolean
folder_tree_select_func (GtkTreeSelection *selection,
GtkTreeModel *model,
GtkTreePath *path,
gboolean selected)
{
EMFolderTreePrivate *priv;
GtkTreeView *tree_view;
gboolean is_store;
guint32 flags;
GtkTreeIter iter;
tree_view = gtk_tree_selection_get_tree_view (selection);
priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
if (selected)
return TRUE;
if (priv->excluded == 0 && priv->excluded_func == NULL)
return TRUE;
if (!gtk_tree_model_get_iter (model, &iter, path))
return TRUE;
if (priv->excluded_func != NULL)
return priv->excluded_func (
EM_FOLDER_TREE (tree_view), model,
&iter, priv->excluded_data);
gtk_tree_model_get (
model, &iter, COL_UINT_FLAGS, &flags,
COL_BOOL_IS_STORE, &is_store, -1);
if (is_store)
flags |= CAMEL_FOLDER_NOSELECT;
return (flags & priv->excluded) == 0;
}
/* NOTE: Removes and frees the selected uri structure */
static void
folder_tree_select_uri (EMFolderTree *folder_tree,
GtkTreePath *path,
struct _selected_uri *u)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
GtkTreeSelection *selection;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.8f, 0.0f);
g_hash_table_remove (priv->select_uris_table, u->key);
priv->select_uris = g_slist_remove (priv->select_uris, u);
folder_tree_free_select_uri (u);
}
static void
folder_tree_expand_node (const gchar *key,
EMFolderTree *folder_tree)
{
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeRowReference *row;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
EAccount *account;
EMailSession *session;
CamelStore *store;
const gchar *p;
gchar *uid;
gsize n;
struct _selected_uri *u;
if (!(p = strchr (key, '/')))
n = strlen (key);
else
n = (p - key);
uid = g_alloca (n + 1);
memcpy (uid, key, n);
uid[n] = '\0';
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
session = em_folder_tree_get_session (folder_tree);
if ((account = e_get_account_by_uid (uid)) && account->enabled) {
store = (CamelStore *) camel_session_get_service (
CAMEL_SESSION (session), account->source->url,
CAMEL_PROVIDER_STORE, NULL);
if (store == NULL)
return;
} else if (!strcmp (uid, "vfolder")) {
if (!(store = vfolder_store))
return;
g_object_ref (store);
} else if (!strcmp (uid, "local")) {
if (!(store = e_mail_local_get_store ()))
return;
g_object_ref (store);
} else {
return;
}
si = em_folder_tree_model_lookup_store_info (
EM_FOLDER_TREE_MODEL (model), store);
if (si == NULL) {
g_object_unref (store);
return;
}
g_object_unref (store);
if (p != NULL) {
if (!(row = g_hash_table_lookup (si->full_hash, p + 1)))
return;
} else
row = si->row;
path = gtk_tree_row_reference_get_path (row);
gtk_tree_view_expand_to_path (tree_view, path);
u = g_hash_table_lookup (folder_tree->priv->select_uris_table, key);
if (u)
folder_tree_select_uri (folder_tree, path, u);
gtk_tree_path_free (path);
}
static void
folder_tree_maybe_expand_row (EMFolderTreeModel *model,
GtkTreePath *tree_path,
GtkTreeIter *iter,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
struct _EMFolderTreeModelStoreInfo *si;
gboolean is_store;
CamelStore *store;
EAccount *account;
gchar *full_name;
gchar *key;
struct _selected_uri *u;
gtk_tree_model_get ((GtkTreeModel *) model, iter,
COL_STRING_FULL_NAME, &full_name,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_IS_STORE, &is_store,
-1);
si = em_folder_tree_model_lookup_store_info (model, store);
if ((account = e_get_account_by_name (si->display_name))) {
key = g_strdup_printf ("%s/%s", account->uid, full_name ? full_name : "");
} else if (CAMEL_IS_VEE_STORE (store)) {
/* vfolder store */
key = g_strdup_printf ("vfolder/%s", full_name ? full_name : "");
} else {
/* local store */
key = g_strdup_printf ("local/%s", full_name ? full_name : "");
}
u = g_hash_table_lookup (priv->select_uris_table, key);
if (u) {
gchar *c = strrchr (key, '/');
*c = '\0';
folder_tree_expand_node (key, folder_tree);
folder_tree_select_uri (folder_tree, tree_path, u);
}
g_free (full_name);
g_free (key);
}
static void
folder_tree_clear_selected_list (EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
g_slist_foreach (priv->select_uris, (GFunc) folder_tree_free_select_uri, NULL);
g_slist_free (priv->select_uris);
g_hash_table_destroy (priv->select_uris_table);
priv->select_uris = NULL;
priv->select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
priv->cursor_set = FALSE;
}
static void
folder_tree_cell_edited_cb (EMFolderTree *folder_tree,
const gchar *path_string,
const gchar *new_name)
{
CamelFolderInfo *folder_info;
CamelStore *store;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
gchar *old_name = NULL;
gchar *old_full_name = NULL;
gchar *new_full_name = NULL;
gchar **strv;
gpointer parent;
guint index;
GError *local_error = NULL;
/* XXX Consider splitting this into separate async functions:
* em_folder_tree_rename_folder_async()
* em_folder_tree_rename_folder_finish() */
parent = gtk_widget_get_toplevel (GTK_WIDGET (folder_tree));
parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
path = gtk_tree_path_new_from_string (path_string);
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_DISPLAY_NAME, &old_name,
COL_STRING_FULL_NAME, &old_full_name, -1);
if (!old_name || !old_full_name || g_strcmp0 (new_name, old_name) == 0)
goto exit;
/* Check for invalid characters. */
if (strchr (new_name, '/') != NULL) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder",
old_name, new_name,
_("Folder names cannot contain '/'"), NULL);
goto exit;
}
/* Build the new name from the old name. */
strv = g_strsplit_set (old_full_name, "/", 0);
index = g_strv_length (strv) - 1;
g_free (strv[index]);
strv[index] = g_strdup (new_name);
new_full_name = g_strjoinv ("/", strv);
g_strfreev (strv);
/* Check for duplicate folder name. */
/* FIXME camel_store_get_folder_info() may block. */
folder_info = camel_store_get_folder_info_sync (
store, new_full_name,
CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
if (folder_info != NULL) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder-exists",
old_name, new_name, NULL);
camel_store_free_folder_info (store, folder_info);
goto exit;
}
/* FIXME camel_store_rename_folder_sync() may block. */
if (!camel_store_rename_folder_sync (
store, old_full_name, new_full_name, NULL, &local_error)) {
e_alert_run_dialog_for_args (
parent, "mail:no-rename-folder",
old_full_name, new_full_name,
local_error->message, NULL);
g_clear_error (&local_error);
goto exit;
}
exit:
g_free (old_name);
g_free (old_full_name);
g_free (new_full_name);
}
static void
folder_tree_selection_changed_cb (EMFolderTree *folder_tree,
GtkTreeSelection *selection)
{
GtkTreeModel *model;
GtkTreeIter iter;
GList *list;
guint32 flags = 0;
guint unread = 0;
guint old_unread = 0;
gchar *full_name = NULL;
gchar *uri = NULL;
list = gtk_tree_selection_get_selected_rows (selection, &model);
if (list == NULL)
goto exit;
gtk_tree_model_get_iter (model, &iter, list->data);
gtk_tree_model_get (
model, &iter,
COL_STRING_FULL_NAME, &full_name,
COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags,
COL_UINT_UNREAD, &unread, COL_UINT_UNREAD_LAST_SEL,
&old_unread, -1);
/* Sync unread counts to distinguish new incoming mail. */
if (unread != old_unread)
gtk_tree_store_set (
GTK_TREE_STORE (model), &iter,
COL_UINT_UNREAD_LAST_SEL, unread, -1);
exit:
g_signal_emit (
folder_tree, signals[FOLDER_SELECTED], 0, full_name, uri, flags);
g_free (full_name);
g_free (uri);
g_list_foreach (list, (GFunc) gtk_tree_path_free, NULL);
g_list_free (list);
}
static void
folder_tree_set_session (EMFolderTree *folder_tree,
EMailSession *session)
{
g_return_if_fail (CAMEL_IS_SESSION (session));
g_return_if_fail (folder_tree->priv->session == NULL);
folder_tree->priv->session = g_object_ref (session);
}
static void
folder_tree_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ELLIPSIZE:
em_folder_tree_set_ellipsize (
EM_FOLDER_TREE (object),
g_value_get_enum (value));
return;
case PROP_SESSION:
folder_tree_set_session (
EM_FOLDER_TREE (object),
g_value_get_object (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
folder_tree_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_ELLIPSIZE:
g_value_set_enum (
value,
em_folder_tree_get_ellipsize (
EM_FOLDER_TREE (object)));
return;
case PROP_SESSION:
g_value_set_object (
value,
em_folder_tree_get_session (
EM_FOLDER_TREE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
folder_tree_dispose (GObject *object)
{
EMFolderTreePrivate *priv;
GtkTreeModel *model;
priv = EM_FOLDER_TREE_GET_PRIVATE (object);
model = gtk_tree_view_get_model (GTK_TREE_VIEW (object));
if (priv->loaded_row_id != 0) {
g_signal_handler_disconnect (model, priv->loaded_row_id);
priv->loaded_row_id = 0;
}
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
if (priv->session != NULL) {
g_object_unref (priv->session);
priv->session = NULL;
}
if (priv->text_renderer != NULL) {
g_object_unref (priv->text_renderer);
priv->text_renderer = NULL;
}
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
folder_tree_finalize (GObject *object)
{
EMFolderTreePrivate *priv;
priv = EM_FOLDER_TREE_GET_PRIVATE (object);
if (priv->select_uris != NULL) {
g_slist_foreach (
priv->select_uris,
(GFunc) folder_tree_free_select_uri, NULL);
g_slist_free (priv->select_uris);
g_hash_table_destroy (priv->select_uris_table);
priv->select_uris = NULL;
}
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gboolean
folder_tree_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
EMFolderTreePrivate *priv;
GtkWidgetClass *widget_class;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreePath *path;
gulong handler_id;
priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
priv->cursor_set = TRUE;
if (event->button != 3)
goto chainup;
if (!gtk_tree_view_get_path_at_pos (
tree_view, event->x, event->y,
&path, NULL, NULL, NULL))
goto chainup;
/* Select and focus the row that was right-clicked, but prevent
* a "folder-selected" signal emission since this does not count
* as a folder selection in the sense we mean. */
handler_id = priv->selection_changed_handler_id;
g_signal_handler_block (selection, handler_id);
gtk_tree_selection_select_path (selection, path);
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
g_signal_handler_unblock (selection, handler_id);
gtk_tree_path_free (path);
folder_tree_emit_popup_event (
EM_FOLDER_TREE (tree_view), (GdkEvent *) event);
chainup:
/* Chain up to parent's button_press_event() method. */
widget_class = GTK_WIDGET_CLASS (parent_class);
return widget_class->button_press_event (widget, event);
}
static gboolean
folder_tree_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
EMFolderTreePrivate *priv;
GtkWidgetClass *widget_class;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
if (event && event->type == GDK_KEY_PRESS &&
(event->keyval == GDK_KEY_space ||
event->keyval == '.' ||
event->keyval == ',' ||
event->keyval == '[' ||
event->keyval == ']')) {
g_signal_emit (widget, signals[HIDDEN_KEY_EVENT], 0, event);
return TRUE;
}
priv = EM_FOLDER_TREE_GET_PRIVATE (widget);
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
folder_tree_clear_selected_list (EM_FOLDER_TREE (widget));
priv->cursor_set = TRUE;
/* Chain up to parent's key_press_event() method. */
widget_class = GTK_WIDGET_CLASS (parent_class);
return widget_class->key_press_event (widget, event);
}
static gboolean
folder_tree_popup_menu (GtkWidget *widget)
{
folder_tree_emit_popup_event (EM_FOLDER_TREE (widget), NULL);
return TRUE;
}
static void
folder_tree_row_activated (GtkTreeView *tree_view,
GtkTreePath *path,
GtkTreeViewColumn *column)
{
EMFolderTreePrivate *priv;
GtkTreeModel *model;
gchar *full_name, *uri;
GtkTreeIter iter;
guint32 flags;
priv = EM_FOLDER_TREE_GET_PRIVATE (tree_view);
model = gtk_tree_view_get_model (tree_view);
if (priv->skip_double_click)
return;
if (!gtk_tree_model_get_iter (model, &iter, path))
return;
gtk_tree_model_get (
model, &iter, COL_STRING_FULL_NAME, &full_name,
COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags, -1);
folder_tree_clear_selected_list (EM_FOLDER_TREE (tree_view));
g_signal_emit (
tree_view, signals[FOLDER_SELECTED], 0, full_name, uri, flags);
g_signal_emit (
tree_view, signals[FOLDER_ACTIVATED], 0, full_name, uri);
g_free (full_name);
g_free (uri);
}
static gboolean
folder_tree_test_collapse_row (GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path)
{
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter cursor;
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &cursor))
goto exit;
/* Select the collapsed node IFF it is a
* parent of the currently selected folder. */
if (gtk_tree_store_is_ancestor (GTK_TREE_STORE (model), iter, &cursor))
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
exit:
return FALSE;
}
static void
folder_tree_row_expanded (GtkTreeView *tree_view,
GtkTreeIter *iter,
GtkTreePath *path)
{
struct _EMFolderTreeGetFolderInfo *msg;
GtkTreeModel *model;
CamelStore *store;
gchar *full_name;
gboolean load;
model = gtk_tree_view_get_model (tree_view);
gtk_tree_model_get (
model, iter,
COL_STRING_FULL_NAME, &full_name,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_LOAD_SUBDIRS, &load, -1);
if (!load) {
g_free (full_name);
return;
}
gtk_tree_store_set (
GTK_TREE_STORE (model), iter,
COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
msg = mail_msg_new (&get_folder_info_info);
msg->root = gtk_tree_row_reference_new (model, path);
g_object_ref (store);
msg->store = store;
msg->folder_tree = g_object_ref (tree_view);
msg->top = full_name;
msg->flags =
CAMEL_STORE_FOLDER_INFO_RECURSIVE |
CAMEL_STORE_FOLDER_INFO_FAST;
mail_msg_unordered_push (msg);
}
static void
folder_tree_class_init (EMFolderTreeClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
GtkTreeViewClass *tree_view_class;
parent_class = g_type_class_peek_parent (class);
g_type_class_add_private (class, sizeof (EMFolderTreePrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = folder_tree_set_property;
object_class->get_property = folder_tree_get_property;
object_class->dispose = folder_tree_dispose;
object_class->finalize = folder_tree_finalize;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->button_press_event = folder_tree_button_press_event;
widget_class->key_press_event = folder_tree_key_press_event;
widget_class->popup_menu = folder_tree_popup_menu;
tree_view_class = GTK_TREE_VIEW_CLASS (class);
tree_view_class->row_activated = folder_tree_row_activated;
tree_view_class->test_collapse_row = folder_tree_test_collapse_row;
tree_view_class->row_expanded = folder_tree_row_expanded;
g_object_class_install_property (
object_class,
PROP_ELLIPSIZE,
g_param_spec_enum (
"ellipsize",
NULL,
NULL,
PANGO_TYPE_ELLIPSIZE_MODE,
PANGO_ELLIPSIZE_NONE,
G_PARAM_READWRITE));
g_object_class_install_property (
object_class,
PROP_SESSION,
g_param_spec_object (
"session",
NULL,
NULL,
E_TYPE_MAIL_SESSION,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
signals[FOLDER_SELECTED] = g_signal_new (
"folder-selected",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeClass, folder_selected),
NULL, NULL,
e_marshal_VOID__STRING_STRING_UINT,
G_TYPE_NONE, 3,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_UINT);
signals[FOLDER_ACTIVATED] = g_signal_new (
"folder-activated",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
NULL, NULL,
e_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
signals[POPUP_EVENT] = g_signal_new (
"popup-event",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (EMFolderTreeClass, popup_event),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1,
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
signals[HIDDEN_KEY_EVENT] =
g_signal_new ("hidden-key-event",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (EMFolderTreeClass, hidden_key_event),
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE, 1, GDK_TYPE_EVENT);
}
static gboolean
subdirs_contain_unread (GtkTreeModel *model, GtkTreeIter *root)
{
guint unread;
GtkTreeIter iter;
if (!gtk_tree_model_iter_children (model, &iter, root))
return FALSE;
do {
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
if (unread)
return TRUE;
if (gtk_tree_model_iter_has_child (model, &iter))
if (subdirs_contain_unread (model, &iter))
return TRUE;
} while (gtk_tree_model_iter_next (model, &iter));
return FALSE;
}
static void
render_display_name (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
{
gboolean is_store, bold, subdirs_unread = FALSE;
gboolean editable;
guint unread;
gchar *display;
gchar *name;
gtk_tree_model_get (model, iter, COL_STRING_DISPLAY_NAME, &name,
COL_BOOL_IS_STORE, &is_store,
COL_UINT_UNREAD, &unread, -1);
g_object_get (renderer, "editable", &editable, NULL);
bold = is_store || unread;
if (gtk_tree_model_iter_has_child (model, iter)) {
gboolean expanded = TRUE;
g_object_get (renderer, "is-expanded", &expanded, NULL);
if (!bold || !expanded)
subdirs_unread = subdirs_contain_unread (model, iter);
}
bold = !editable && (bold || subdirs_unread);
if (!is_store && !editable && unread) {
/* Translators: This is the string used for displaying the
* folder names in folder trees. The first "%s" will be
* replaced by the folder's name and "%u" will be replaced
* with the number of unread messages in the folder. The
* second %s will be replaced with a "+" letter for collapsed
* folders with unread messages in some subfolder too,
* or with an empty string for other cases.
*
* Most languages should translate this as "%s (%u%s)". The
* languages that use localized digits (like Persian) may
* need to replace "%u" with "%Iu". Right-to-left languages
* (like Arabic and Hebrew) may need to add bidirectional
* formatting codes to take care of the cases the folder
* name appears in either direction.
*
* Do not translate the "folder-display|" part. Remove it
* from your translation.
*/
display = g_strdup_printf (
C_("folder-display", "%s (%u%s)"),
name, unread, subdirs_unread ? "+" : "");
g_free (name);
} else
display = name;
g_object_set (renderer, "text", display,
"weight", bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
NULL);
g_free (display);
}
static void
render_icon (GtkTreeViewColumn *column,
GtkCellRenderer *renderer,
GtkTreeModel *model,
GtkTreeIter *iter)
{
GtkTreeSelection *selection;
GtkTreePath *drag_dest_row;
GtkWidget *tree_view;
GIcon *icon;
guint unread;
guint old_unread;
gchar *icon_name;
gboolean is_selected;
gboolean is_drafts = FALSE;
gboolean is_drag_dest = FALSE;
gtk_tree_model_get (
model, iter,
COL_STRING_ICON_NAME, &icon_name,
COL_UINT_UNREAD_LAST_SEL, &old_unread,
COL_UINT_UNREAD, &unread,
COL_BOOL_IS_DRAFT, &is_drafts,
-1);
if (icon_name == NULL)
return;
tree_view = gtk_tree_view_column_get_tree_view (column);
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
is_selected = gtk_tree_selection_iter_is_selected (selection, iter);
gtk_tree_view_get_drag_dest_row (
GTK_TREE_VIEW (tree_view), &drag_dest_row, NULL);
if (drag_dest_row != NULL) {
GtkTreePath *path;
path = gtk_tree_model_get_path (model, iter);
if (gtk_tree_path_compare (path, drag_dest_row) == 0)
is_drag_dest = TRUE;
gtk_tree_path_free (path);
gtk_tree_path_free (drag_dest_row);
}
if (g_strcmp0 (icon_name, "folder") == 0) {
if (is_selected) {
g_free (icon_name);
icon_name = g_strdup ("folder-open");
} else if (is_drag_dest) {
g_free (icon_name);
icon_name = g_strdup ("folder-drag-accept");
}
}
icon = g_themed_icon_new (icon_name);
/* Show an emblem if there's new mail. */
if (!is_selected && unread > old_unread && !is_drafts) {
GIcon *temp_icon;
GEmblem *emblem;
temp_icon = g_themed_icon_new ("emblem-new");
emblem = g_emblem_new (temp_icon);
g_object_unref (temp_icon);
temp_icon = g_emblemed_icon_new (icon, emblem);
g_object_unref (emblem);
g_object_unref (icon);
icon = temp_icon;
}
g_object_set (renderer, "gicon", icon, NULL);
g_object_unref (icon);
g_free (icon_name);
}
static GtkTreeView *
folder_tree_new (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkWidget *tree;
/* FIXME Gross hack */
tree = GTK_WIDGET (folder_tree);
gtk_widget_set_can_focus (tree, TRUE);
folder_tree->priv->selectable = NULL;
column = gtk_tree_view_column_new ();
gtk_tree_view_append_column ((GtkTreeView *) tree, column);
renderer = gtk_cell_renderer_pixbuf_new ();
gtk_tree_view_column_pack_start (column, renderer, FALSE);
gtk_tree_view_column_add_attribute (
column, renderer, "visible", COL_BOOL_IS_FOLDER);
gtk_tree_view_column_set_cell_data_func (
column, renderer, (GtkTreeCellDataFunc)
render_icon, NULL, NULL);
renderer = gtk_cell_renderer_text_new ();
gtk_tree_view_column_pack_start (column, renderer, TRUE);
gtk_tree_view_column_set_cell_data_func (
column, renderer, render_display_name, NULL, NULL);
folder_tree->priv->text_renderer = g_object_ref (renderer);
g_object_bind_property (
folder_tree, "ellipsize",
renderer, "ellipsize",
G_BINDING_SYNC_CREATE);
g_signal_connect_swapped (
renderer, "edited",
G_CALLBACK (folder_tree_cell_edited_cb), folder_tree);
selection = gtk_tree_view_get_selection ((GtkTreeView *) tree);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
gtk_tree_selection_set_select_function (
selection, (GtkTreeSelectionFunc)
folder_tree_select_func, NULL, NULL);
gtk_tree_view_set_headers_visible ((GtkTreeView *) tree, FALSE);
gtk_tree_view_set_search_column ((GtkTreeView *)tree, COL_STRING_DISPLAY_NAME);
return (GtkTreeView *) tree;
}
static void
folder_tree_copy_expanded_cb (GtkTreeView *unused,
GtkTreePath *path,
GtkTreeView *tree_view)
{
gtk_tree_view_expand_row (tree_view, path, FALSE);
}
static void
folder_tree_copy_selection_cb (GtkTreeModel *model,
GtkTreePath *path,
GtkTreeIter *iter,
GtkTreeView *tree_view)
{
GtkTreeSelection *selection;
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_select_path (selection, path);
/* Center the tree view on the selected path. */
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5, 0.0);
}
static void
folder_tree_copy_state (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreeModel *model;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
selection = em_folder_tree_model_get_selection (
EM_FOLDER_TREE_MODEL (model));
if (selection == NULL)
return;
gtk_tree_view_map_expanded_rows (
tree_view, (GtkTreeViewMappingFunc)
folder_tree_copy_expanded_cb, folder_tree);
gtk_tree_selection_selected_foreach (
selection, (GtkTreeSelectionForeachFunc)
folder_tree_copy_selection_cb, folder_tree);
}
static void
em_folder_tree_construct (EMFolderTree *folder_tree)
{
folder_tree_new (folder_tree);
folder_tree_copy_state (folder_tree);
gtk_widget_show (GTK_WIDGET (folder_tree));
}
static void
folder_tree_init (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GHashTable *select_uris_table;
EMFolderTreeModel *model;
gulong handler_id;
AtkObject *a11y;
select_uris_table = g_hash_table_new (g_str_hash, g_str_equal);
folder_tree->priv = EM_FOLDER_TREE_GET_PRIVATE (folder_tree);
folder_tree->priv->select_uris_table = select_uris_table;
tree_view = GTK_TREE_VIEW (folder_tree);
model = em_folder_tree_model_get_default ();
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_view_set_model (tree_view, GTK_TREE_MODEL (model));
handler_id = g_signal_connect (
model, "loading-row",
G_CALLBACK (folder_tree_maybe_expand_row), folder_tree);
folder_tree->priv->loading_row_id = handler_id;
handler_id = g_signal_connect (
model, "loaded-row",
G_CALLBACK (folder_tree_maybe_expand_row), folder_tree);
folder_tree->priv->loaded_row_id = handler_id;
handler_id = g_signal_connect_swapped (
selection, "changed",
G_CALLBACK (folder_tree_selection_changed_cb), folder_tree);
folder_tree->priv->selection_changed_handler_id = handler_id;
a11y = gtk_widget_get_accessible (GTK_WIDGET (folder_tree));
atk_object_set_name (a11y, _("Mail Folder Tree"));
/* FIXME Kill this thing. */
em_folder_tree_construct (folder_tree);
}
/* Sets a selectable widget, which will be used for update-actions and
* select-all selectable interface functions. This can be NULL, then nothing
* can be selected and calling selectable function does nothing. */
void
em_folder_tree_set_selectable_widget (EMFolderTree *folder_tree,
GtkWidget *selectable)
{
if (selectable)
g_return_if_fail (E_IS_SELECTABLE (selectable));
folder_tree->priv->selectable = selectable;
}
static void
folder_tree_selectable_update_actions (ESelectable *selectable,
EFocusTracker *focus_tracker,
GdkAtom *clipboard_targets,
gint n_clipboard_targets)
{
EMFolderTree *folder_tree;
folder_tree = EM_FOLDER_TREE (selectable);
g_return_if_fail (folder_tree != NULL);
if (folder_tree->priv->selectable != NULL) {
ESelectableInterface *interface;
ESelectable *selectable;
selectable = E_SELECTABLE (folder_tree->priv->selectable);
interface = E_SELECTABLE_GET_INTERFACE (selectable);
g_return_if_fail (interface->update_actions != NULL);
interface->update_actions (
selectable, focus_tracker,
clipboard_targets, n_clipboard_targets);
}
}
#define folder_tree_selectable_func(_func) \
static void \
folder_tree_selectable_ ## _func (ESelectable *selectable) \
{ \
ESelectableInterface *interface; \
EMFolderTree *folder_tree; \
\
\
folder_tree = EM_FOLDER_TREE (selectable); \
g_return_if_fail (folder_tree != NULL); \
\
if (folder_tree->priv->selectable == NULL) \
return; \
\
selectable = E_SELECTABLE (folder_tree->priv->selectable); \
interface = E_SELECTABLE_GET_INTERFACE (selectable); \
\
if (interface-> _func == NULL) \
return; \
\
if (gtk_widget_get_can_focus (GTK_WIDGET (selectable)) && \
!gtk_widget_has_focus (GTK_WIDGET (selectable))) \
gtk_widget_grab_focus (GTK_WIDGET (selectable)); \
\
interface-> _func (selectable); \
}
folder_tree_selectable_func (cut_clipboard);
folder_tree_selectable_func (copy_clipboard);
folder_tree_selectable_func (paste_clipboard);
folder_tree_selectable_func (delete_selection);
folder_tree_selectable_func (select_all);
#undef folder_tree_selectable_func
static void
folder_tree_selectable_init (ESelectableInterface *interface)
{
interface->update_actions = folder_tree_selectable_update_actions;
interface->cut_clipboard = folder_tree_selectable_cut_clipboard;
interface->copy_clipboard = folder_tree_selectable_copy_clipboard;
interface->paste_clipboard = folder_tree_selectable_paste_clipboard;
interface->delete_selection = folder_tree_selectable_delete_selection;
interface->select_all = folder_tree_selectable_select_all;
}
GType
em_folder_tree_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY (type == 0)) {
static const GTypeInfo type_info = {
sizeof (EMFolderTreeClass),
(GBaseInitFunc) NULL,
(GBaseFinalizeFunc) NULL,
(GClassInitFunc) folder_tree_class_init,
(GClassFinalizeFunc) NULL,
NULL, /* class_data */
sizeof (EMFolderTree),
0, /* n_preallocs */
(GInstanceInitFunc) folder_tree_init,
NULL /* value_table */
};
static const GInterfaceInfo selectable_info = {
(GInterfaceInitFunc) folder_tree_selectable_init,
(GInterfaceFinalizeFunc) NULL,
NULL /* interface_data */
};
type = g_type_register_static (
GTK_TYPE_TREE_VIEW, "EMFolderTree", &type_info, 0);
g_type_add_interface_static (
type, E_TYPE_SELECTABLE, &selectable_info);
}
return type;
}
GtkWidget *
em_folder_tree_new (EMailSession *session)
{
g_return_val_if_fail (E_IS_MAIL_SESSION (session), NULL);
return g_object_new (
EM_TYPE_FOLDER_TREE, "session", session, NULL);
}
PangoEllipsizeMode
em_folder_tree_get_ellipsize (EMFolderTree *folder_tree)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), 0);
return folder_tree->priv->ellipsize;
}
void
em_folder_tree_set_ellipsize (EMFolderTree *folder_tree,
PangoEllipsizeMode ellipsize)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
if (ellipsize == folder_tree->priv->ellipsize)
return;
folder_tree->priv->ellipsize = ellipsize;
g_object_notify (G_OBJECT (folder_tree), "ellipsize");
}
EMailSession *
em_folder_tree_get_session (EMFolderTree *folder_tree)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
return folder_tree->priv->session;
}
static void
tree_drag_begin (GtkWidget *widget,
GdkDragContext *context,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GdkColormap *colormap;
GdkPixmap *pixmap;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
tree_view = GTK_TREE_VIEW (widget);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
path = gtk_tree_model_get_path (model, &iter);
priv->drag_row = gtk_tree_row_reference_new (model, path);
pixmap = gtk_tree_view_create_row_drag_icon (tree_view, path);
colormap = gdk_drawable_get_colormap (pixmap);
gtk_drag_set_icon_pixmap (context, colormap, pixmap, NULL, 0, 0);
gtk_tree_path_free (path);
}
static void
tree_drag_data_delete (GtkWidget *widget,
GdkDragContext *context,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
gchar *full_name = NULL;
GtkTreeModel *model;
GtkTreePath *src_path;
gboolean is_store;
CamelStore *store;
GtkTreeIter iter;
if (!priv->drag_row || (src_path = gtk_tree_row_reference_get_path (priv->drag_row)))
return;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
if (!gtk_tree_model_get_iter (model, &iter, src_path))
goto fail;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &full_name,
COL_BOOL_IS_STORE, &is_store, -1);
if (is_store)
goto fail;
/* FIXME camel_store_delete_folder_sync() may block. */
camel_store_delete_folder_sync (store, full_name, NULL, NULL);
fail:
gtk_tree_path_free (src_path);
g_free (full_name);
}
static void
tree_drag_data_get (GtkWidget *widget,
GdkDragContext *context,
GtkSelectionData *selection,
guint info,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
gchar *full_name = NULL, *uri = NULL;
GtkTreeModel *model;
GtkTreePath *src_path;
CamelFolder *folder;
CamelStore *store;
GtkTreeIter iter;
if (!priv->drag_row || !(src_path = gtk_tree_row_reference_get_path (priv->drag_row)))
return;
model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
if (!gtk_tree_model_get_iter (model, &iter, src_path))
goto fail;
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &full_name,
COL_STRING_URI, &uri, -1);
/* make sure user isn't trying to drag on a placeholder row */
if (full_name == NULL)
goto fail;
switch (info) {
case DND_DRAG_TYPE_FOLDER:
/* dragging to a new location in the folder tree */
gtk_selection_data_set (
selection, drag_atoms[info], 8,
(guchar *) uri, strlen (uri) + 1);
break;
case DND_DRAG_TYPE_TEXT_URI_LIST:
/* dragging to nautilus or something, probably */
/* FIXME camel_store_get_folder_sync() may block. */
if ((folder = camel_store_get_folder_sync (
store, full_name, 0, NULL, NULL))) {
GPtrArray *uids = camel_folder_get_uids (folder);
em_utils_selection_set_urilist (selection, folder, uids);
camel_folder_free_uids (folder, uids);
g_object_unref (folder);
}
break;
default:
abort ();
}
fail:
gtk_tree_path_free (src_path);
g_free (full_name);
g_free (uri);
}
/* Drop handling */
struct _DragDataReceivedAsync {
MailMsg base;
/* input data */
GdkDragContext *context;
/* Only selection->data and selection->length are valid */
GtkSelectionData *selection;
EMailSession *session;
CamelStore *store;
gchar *full_name;
guint32 action;
guint info;
guint move:1;
guint moved:1;
guint aborted:1;
};
static void
folder_tree_drop_folder (struct _DragDataReceivedAsync *m)
{
CamelFolder *folder;
CamelStore *parent_store;
const gchar *full_name;
const guchar *data;
data = gtk_selection_data_get_data (m->selection);
d(printf(" * Drop folder '%s' onto '%s'\n", data, m->full_name));
folder = e_mail_session_uri_to_folder_sync (
m->session, (gchar *) data, 0,
m->base.cancellable, &m->base.error);
if (folder == NULL)
return;
full_name = camel_folder_get_full_name (folder);
parent_store = camel_folder_get_parent_store (folder);
em_folder_utils_copy_folders (
parent_store, full_name, m->store,
m->full_name ? m->full_name : "", m->move);
g_object_unref (folder);
}
static gchar *
folder_tree_drop_async__desc (struct _DragDataReceivedAsync *m)
{
CamelURL *url;
const guchar *data;
gchar *buf;
data = gtk_selection_data_get_data (m->selection);
if (m->info == DND_DROP_TYPE_FOLDER) {
url = camel_url_new ((gchar *)data, NULL);
if (m->move)
buf = g_strdup_printf (
_("Moving folder %s"), url->fragment ?
url->fragment : url->path + 1);
else
buf = g_strdup_printf (
_("Copying folder %s"), url->fragment ?
url->fragment : url->path + 1);
camel_url_free (url);
return buf;
} else {
if (m->move)
return g_strdup_printf (
_("Moving messages into folder %s"),
m->full_name);
else
return g_strdup_printf (
_("Copying messages into folder %s"),
m->full_name);
}
}
static void
folder_tree_drop_async__exec (struct _DragDataReceivedAsync *m)
{
CamelFolder *folder;
/* for types other than folder, we can't drop to the root path */
if (m->info == DND_DROP_TYPE_FOLDER) {
/* copy or move (aka rename) a folder */
folder_tree_drop_folder (m);
} else if (m->full_name == NULL) {
g_set_error (
&m->base.error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Cannot drop message(s) into toplevel store"));
} else if ((folder = camel_store_get_folder_sync (
m->store, m->full_name, 0,
m->base.cancellable, &m->base.error))) {
switch (m->info) {
case DND_DROP_TYPE_UID_LIST:
/* import a list of uids from another evo folder */
em_utils_selection_get_uidlist (
m->selection, m->session, folder, m->move,
m->base.cancellable, &m->base.error);
m->moved = m->move && (m->base.error == NULL);
break;
case DND_DROP_TYPE_MESSAGE_RFC822:
/* import a message/rfc822 stream */
em_utils_selection_get_message (m->selection, folder);
break;
case DND_DROP_TYPE_TEXT_URI_LIST:
/* import an mbox, maildir, or mh folder? */
em_utils_selection_get_urilist (m->selection, folder);
break;
default:
abort ();
}
g_object_unref (folder);
}
}
static void
folder_tree_drop_async__free (struct _DragDataReceivedAsync *m)
{
g_object_unref (m->session);
g_object_unref (m->context);
g_object_unref (m->store);
g_free (m->full_name);
gtk_selection_data_free (m->selection);
}
static MailMsgInfo folder_tree_drop_async_info = {
sizeof (struct _DragDataReceivedAsync),
(MailMsgDescFunc) folder_tree_drop_async__desc,
(MailMsgExecFunc) folder_tree_drop_async__exec,
(MailMsgDoneFunc) NULL,
(MailMsgFreeFunc) folder_tree_drop_async__free
};
static void
tree_drag_data_action (struct _DragDataReceivedAsync *m)
{
m->move = m->action == GDK_ACTION_MOVE;
mail_msg_unordered_push (m);
}
static void
tree_drag_data_received (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
GtkSelectionData *selection,
guint info,
guint time,
EMFolderTree *folder_tree)
{
GtkTreeViewDropPosition pos;
GtkTreeModel *model;
GtkTreeView *tree_view;
GtkTreePath *dest_path;
EMailSession *session;
struct _DragDataReceivedAsync *m;
gboolean is_store;
CamelStore *store;
GtkTreeIter iter;
gchar *full_name;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
session = em_folder_tree_get_session (folder_tree);
if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &dest_path, &pos))
return;
/* this means we are receiving no data */
if (gtk_selection_data_get_data (selection) == NULL) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
if (gtk_selection_data_get_length (selection) == -1) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
if (!gtk_tree_model_get_iter (model, &iter, dest_path)) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_IS_STORE, &is_store,
COL_STRING_FULL_NAME, &full_name, -1);
/* make sure user isn't try to drop on a placeholder row */
if (full_name == NULL && !is_store) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
m = mail_msg_new (&folder_tree_drop_async_info);
m->session = g_object_ref (session);
m->context = g_object_ref (context);
m->store = g_object_ref (store);
m->full_name = full_name;
m->action = gdk_drag_context_get_selected_action (context);
m->info = info;
/* need to copy, goes away once we exit */
m->selection = gtk_selection_data_copy (selection);
tree_drag_data_action (m);
}
static gboolean
is_special_local_folder (const gchar *name)
{
return strcmp (name, "Drafts") == 0
|| strcmp (name, "Inbox") == 0
|| strcmp (name, "Outbox") == 0
|| strcmp (name, "Sent") == 0
|| strcmp (name, "Templates") == 0;
}
static GdkAtom
folder_tree_drop_target (EMFolderTree *folder_tree,
GdkDragContext *context,
GtkTreePath *path,
GdkDragAction *actions,
GdkDragAction *suggested_action)
{
EMFolderTreePrivate *p = folder_tree->priv;
gchar *full_name = NULL, *uri = NULL, *src_uri = NULL;
CamelStore *local, *sstore, *dstore;
GdkAtom atom = GDK_NONE;
gboolean is_store;
GtkTreeModel *model;
GtkTreeIter iter;
GList *targets;
guint32 flags = 0;
/* This is a bit of a mess, but should handle all the cases properly */
model = gtk_tree_view_get_model (GTK_TREE_VIEW (folder_tree));
if (!gtk_tree_model_get_iter (model, &iter, path))
return GDK_NONE;
/* We may override these further down. */
*actions = gdk_drag_context_get_actions (context);
*suggested_action = gdk_drag_context_get_suggested_action (context);
gtk_tree_model_get (
model, &iter,
COL_BOOL_IS_STORE, &is_store,
COL_STRING_FULL_NAME, &full_name,
COL_UINT_FLAGS, &flags,
COL_POINTER_CAMEL_STORE, &dstore,
COL_STRING_URI, &uri, -1);
local = e_mail_local_get_store ();
targets = gdk_drag_context_list_targets (context);
/* Check for special destinations */
if (uri && full_name) {
/* don't allow copying/moving into the UNMATCHED vfolder */
if (!strncmp (uri, "vfolder:", 8) && !strcmp (full_name, CAMEL_UNMATCHED_NAME))
goto done;
/* don't allow copying/moving into a vTrash/vJunk folder */
if (!strcmp (full_name, CAMEL_VTRASH_NAME)
|| !strcmp (full_name, CAMEL_VJUNK_NAME))
goto done;
if (flags & CAMEL_FOLDER_NOSELECT)
goto done;
}
if (p->drag_row) {
GtkTreePath *src_path = gtk_tree_row_reference_get_path (p->drag_row);
if (src_path) {
guint32 src_flags = 0;
if (gtk_tree_model_get_iter (model, &iter, src_path))
gtk_tree_model_get (
model, &iter,
COL_POINTER_CAMEL_STORE, &sstore,
COL_STRING_URI, &src_uri,
COL_UINT_FLAGS, &src_flags, -1);
/* can't dnd onto itself or below itself - bad things happen,
no point dragging to where we were either */
if (gtk_tree_path_compare (path, src_path) == 0
|| gtk_tree_path_is_descendant (path, src_path)
|| (gtk_tree_path_is_ancestor (path, src_path)
&& gtk_tree_path_get_depth (path) == gtk_tree_path_get_depth (src_path)-1)) {
gtk_tree_path_free (src_path);
goto done;
}
gtk_tree_path_free (src_path);
if ((src_flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_INBOX ||
(src_flags & CAMEL_FOLDER_SYSTEM) != 0) {
/* allow only copy of the Inbox and other system folders */
GdkAtom xfolder;
/* force copy for special local folders */
*suggested_action = GDK_ACTION_COPY;
*actions = GDK_ACTION_COPY;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
goto done;
}
}
}
/* Check for special sources, and vfolder stuff */
if (src_uri) {
CamelURL *url;
gchar *url_path;
/* FIXME: this is a total hack, but i think all we can do at present */
/* Check for dragging from special folders which can't be moved/copied */
url = camel_url_new (src_uri, NULL);
url_path = url->fragment?url->fragment:url->path;
if (url_path && url_path[0]) {
/* don't allow moving any of the the local special folders */
if (sstore == local && is_special_local_folder (url_path)) {
GdkAtom xfolder;
camel_url_free (url);
/* force copy for special local folders */
*suggested_action = GDK_ACTION_COPY;
*actions = GDK_ACTION_COPY;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
goto done;
}
/* Don't allow copying/moving the UNMATCHED vfolder. */
if (!strcmp (url->protocol, "vfolder") &&
!strcmp (url_path, CAMEL_UNMATCHED_NAME)) {
camel_url_free (url);
goto done;
}
/* Don't allow copying/moving any vTrash/vJunk
* folder nor maildir 'inbox'. */
if (strcmp (url_path, CAMEL_VTRASH_NAME) == 0
|| strcmp (url_path, CAMEL_VJUNK_NAME) == 0
/* Dont allow drag from maildir 'inbox' */
|| strcmp(url_path, ".") == 0) {
camel_url_free (url);
goto done;
}
}
camel_url_free (url);
/* Search Folders can only be dropped into other Search Folders */
if (strncmp(src_uri, "vfolder:", 8) == 0) {
/* force move only for vfolders */
*suggested_action = GDK_ACTION_MOVE;
if (uri && strncmp(uri, "vfolder:", 8) == 0) {
GdkAtom xfolder;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
}
goto done;
}
}
/* can't drag anything but a vfolder into a vfolder */
if (uri && strncmp(uri, "vfolder:", 8) == 0)
goto done;
/* Now we either have a store or a normal folder */
if (is_store) {
GdkAtom xfolder;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder) {
atom = xfolder;
goto done;
}
targets = targets->next;
}
} else {
gint i;
while (targets != NULL) {
for (i = 0; i < NUM_DROP_TYPES; i++) {
if (targets->data == (gpointer) drop_atoms[i]) {
atom = drop_atoms[i];
goto done;
}
}
targets = targets->next;
}
}
done:
g_free (full_name);
g_free (uri);
return atom;
}
static gboolean
tree_drag_drop (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeViewColumn *column;
GtkTreeView *tree_view;
gint cell_x, cell_y;
GdkDragAction actions;
GdkDragAction suggested_action;
GtkTreePath *path;
GdkAtom target;
tree_view = GTK_TREE_VIEW (folder_tree);
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
if (!gtk_tree_view_get_path_at_pos (
tree_view, x, y, &path, &column, &cell_x, &cell_y))
return FALSE;
target = folder_tree_drop_target (
folder_tree, context, path,
&actions, &suggested_action);
gtk_tree_path_free (path);
return (target != GDK_NONE);
}
static void
tree_drag_end (GtkWidget *widget,
GdkDragContext *context,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
if (priv->drag_row != NULL) {
gtk_tree_row_reference_free (priv->drag_row);
priv->drag_row = NULL;
}
/* FIXME: undo anything done in drag-begin */
}
static void
tree_drag_leave (GtkWidget *widget,
GdkDragContext *context,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
tree_view = GTK_TREE_VIEW (folder_tree);
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
gtk_tree_view_set_drag_dest_row (
tree_view, NULL, GTK_TREE_VIEW_DROP_BEFORE);
}
#define SCROLL_EDGE_SIZE 15
static gboolean
tree_autoscroll (EMFolderTree *folder_tree)
{
GtkAdjustment *adjustment;
GtkTreeView *tree_view;
GdkRectangle rect;
GdkWindow *window;
gint offset, y;
gdouble page_size;
gdouble upper;
gdouble value;
/* get the y pointer position relative to the treeview */
tree_view = GTK_TREE_VIEW (folder_tree);
window = gtk_tree_view_get_bin_window (tree_view);
gdk_window_get_pointer (window, NULL, &y, NULL);
/* rect is in coorinates relative to the scrolled window relative to the treeview */
gtk_tree_view_get_visible_rect (tree_view, &rect);
/* move y into the same coordinate system as rect */
y += rect.y;
/* see if we are near the top edge */
if ((offset = y - (rect.y + 2 * SCROLL_EDGE_SIZE)) > 0) {
/* see if we are near the bottom edge */
if ((offset = y - (rect.y + rect.height - 2 * SCROLL_EDGE_SIZE)) < 0)
return TRUE;
}
adjustment = gtk_tree_view_get_vadjustment (tree_view);
page_size = gtk_adjustment_get_value (adjustment);
upper = gtk_adjustment_get_upper (adjustment);
value = gtk_adjustment_get_value (adjustment);
value = CLAMP (value + offset, 0.0, upper - page_size);
gtk_adjustment_set_value (adjustment, value);
return TRUE;
}
static gboolean
tree_autoexpand (EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeView *tree_view;
GtkTreePath *path;
tree_view = GTK_TREE_VIEW (folder_tree);
path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
return TRUE;
}
static gboolean
tree_drag_motion (GtkWidget *widget,
GdkDragContext *context,
gint x,
gint y,
guint time,
EMFolderTree *folder_tree)
{
EMFolderTreePrivate *priv = folder_tree->priv;
GtkTreeViewDropPosition pos;
GtkTreeView *tree_view;
GtkTreeModel *model;
GdkDragAction actions;
GdkDragAction suggested_action;
GdkDragAction chosen_action = 0;
GtkTreePath *path;
GtkTreeIter iter;
GdkAtom target;
gint i;
tree_view = GTK_TREE_VIEW (folder_tree);
model = gtk_tree_view_get_model (tree_view);
if (!gtk_tree_view_get_dest_row_at_pos (tree_view, x, y, &path, &pos))
return FALSE;
if (priv->autoscroll_id == 0)
priv->autoscroll_id = g_timeout_add (
150, (GSourceFunc) tree_autoscroll, folder_tree);
gtk_tree_model_get_iter (model, &iter, path);
if (gtk_tree_model_iter_has_child (model, &iter) &&
!gtk_tree_view_row_expanded (tree_view, path)) {
if (priv->autoexpand_id != 0) {
GtkTreePath *autoexpand_path;
autoexpand_path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
if (gtk_tree_path_compare (autoexpand_path, path) != 0) {
/* row changed, restart timer */
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = gtk_tree_row_reference_new (model, path);
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = g_timeout_add (
600, (GSourceFunc)
tree_autoexpand, folder_tree);
}
gtk_tree_path_free (autoexpand_path);
} else {
priv->autoexpand_id = g_timeout_add (
600, (GSourceFunc)
tree_autoexpand, folder_tree);
priv->autoexpand_row = gtk_tree_row_reference_new (model, path);
}
} else if (priv->autoexpand_id != 0) {
gtk_tree_row_reference_free (priv->autoexpand_row);
priv->autoexpand_row = NULL;
g_source_remove (priv->autoexpand_id);
priv->autoexpand_id = 0;
}
target = folder_tree_drop_target (
folder_tree, context, path,
&actions, &suggested_action);
for (i = 0; target != GDK_NONE && i < NUM_DROP_TYPES; i++) {
if (drop_atoms[i] != target)
continue;
switch (i) {
case DND_DROP_TYPE_UID_LIST:
case DND_DROP_TYPE_FOLDER:
chosen_action = suggested_action;
if (chosen_action == GDK_ACTION_COPY && (actions & GDK_ACTION_MOVE))
chosen_action = GDK_ACTION_MOVE;
gtk_tree_view_set_drag_dest_row (
tree_view, path,
GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
break;
default:
gtk_tree_view_set_drag_dest_row (
tree_view, path,
GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
chosen_action = suggested_action;
break;
}
break;
}
gdk_drag_status (context, chosen_action, time);
return chosen_action != 0;
}
void
em_folder_tree_enable_drag_and_drop (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
static gint setup = 0;
gint i;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
if (!setup) {
for (i=0; i<NUM_DRAG_TYPES; i++)
drag_atoms[i] = gdk_atom_intern (drag_types[i].target, FALSE);
for (i=0; i<NUM_DROP_TYPES; i++)
drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE);
setup = 1;
}
gtk_drag_source_set (
GTK_WIDGET (tree_view), GDK_BUTTON1_MASK,drag_types,
NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
gtk_drag_dest_set (
GTK_WIDGET (tree_view), GTK_DEST_DEFAULT_ALL, drop_types,
NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
g_signal_connect (
tree_view, "drag-begin",
G_CALLBACK (tree_drag_begin), folder_tree);
g_signal_connect (
tree_view, "drag-data-delete",
G_CALLBACK (tree_drag_data_delete), folder_tree);
g_signal_connect (
tree_view, "drag-data-get",
G_CALLBACK (tree_drag_data_get), folder_tree);
g_signal_connect (
tree_view, "drag-data-received",
G_CALLBACK (tree_drag_data_received), folder_tree);
g_signal_connect (
tree_view, "drag-drop",
G_CALLBACK (tree_drag_drop), folder_tree);
g_signal_connect (
tree_view, "drag-end",
G_CALLBACK (tree_drag_end), folder_tree);
g_signal_connect (
tree_view, "drag-leave",
G_CALLBACK (tree_drag_leave), folder_tree);
g_signal_connect (
tree_view, "drag-motion",
G_CALLBACK (tree_drag_motion), folder_tree);
}
void
em_folder_tree_set_excluded (EMFolderTree *folder_tree, guint32 flags)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
folder_tree->priv->excluded = flags;
}
void
em_folder_tree_set_excluded_func (EMFolderTree *folder_tree,
EMFTExcludeFunc exclude,
gpointer data)
{
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
g_return_if_fail (exclude != NULL);
folder_tree->priv->excluded_func = exclude;
folder_tree->priv->excluded_data = data;
}
GList *
em_folder_tree_get_selected_uris (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GtkTreeModel *model;
GList *list = NULL, *rows, *l;
GSList *sl;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
/* at first, add lost uris */
for (sl = folder_tree->priv->select_uris; sl; sl = g_slist_next (sl))
list = g_list_append (list, g_strdup (((struct _selected_uri *)sl->data)->uri));
rows = gtk_tree_selection_get_selected_rows (selection, &model);
for (l=rows; l; l=g_list_next (l)) {
GtkTreeIter iter;
GtkTreePath *path = l->data;
if (gtk_tree_model_get_iter (model, &iter, path)) {
gchar *uri;
gtk_tree_model_get (model, &iter, COL_STRING_URI, &uri, -1);
list = g_list_prepend (list, uri);
}
gtk_tree_path_free (path);
}
g_list_free (rows);
return g_list_reverse (list);
}
static void
get_selected_uris_path_iterate (GtkTreeModel *model,
GtkTreePath *treepath,
GtkTreeIter *iter,
gpointer data)
{
GList **list = (GList **) data;
gchar *full_name;
gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1);
*list = g_list_append (*list, full_name);
}
GList *
em_folder_tree_get_selected_paths (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeView *tree_view;
GList *list = NULL;
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
gtk_tree_selection_selected_foreach (
selection, get_selected_uris_path_iterate, &list);
return list;
}
void
em_folder_tree_set_selected_list (EMFolderTree *folder_tree,
GList *list,
gboolean expand_only)
{
EMFolderTreePrivate *priv = folder_tree->priv;
EMailSession *session;
gint id = 0;
session = em_folder_tree_get_session (folder_tree);
/* FIXME: need to remove any currently selected stuff? */
if (!expand_only)
folder_tree_clear_selected_list (folder_tree);
for (;list;list = list->next) {
struct _selected_uri *u = g_malloc0 (sizeof (*u));
CamelURL *url;
u->uri = g_strdup (list->data);
u->store = (CamelStore *) camel_session_get_service (
CAMEL_SESSION (session), u->uri,
CAMEL_PROVIDER_STORE, NULL);
url = camel_url_new (u->uri, NULL);
if (u->store == NULL || url == NULL) {
if (!expand_only) {
u->key = g_strdup_printf("dummy-%d:%s", id++, u->uri);
g_hash_table_insert (priv->select_uris_table, u->key, u);
priv->select_uris = g_slist_append (priv->select_uris, u);
}
} else {
const gchar *path;
gchar *expand_key, *end;
EAccount *account;
if (((CamelService *)u->store)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
path = url->fragment;
else
path = url->path && url->path[0]=='/' ? url->path+1:url->path;
if (path == NULL)
path = "";
/* This makes sure all our parents up to the root are
* expanded. FIXME: Why does the expanded state store
* this made up path rather than the euri? */
if ((account = e_get_account_by_source_url (u->uri)))
expand_key = g_strdup_printf ("%s/%s", account->uid, path);
else if (CAMEL_IS_VEE_STORE (u->store))
expand_key = g_strdup_printf ("vfolder/%s", path);
else
expand_key = g_strdup_printf ("local/%s", path);
if (!expand_only) {
u->key = g_strdup (expand_key);
g_hash_table_insert (priv->select_uris_table, u->key, u);
priv->select_uris = g_slist_append (priv->select_uris, u);
}
end = strrchr (expand_key, '/');
do {
folder_tree_expand_node (expand_key, folder_tree);
*end = 0;
end = strrchr (expand_key, '/');
} while (end);
g_free (expand_key);
}
if (url)
camel_url_free (url);
}
}
#if 0
static void
dump_fi (CamelFolderInfo *fi, gint depth)
{
gint i;
while (fi != NULL) {
for (i = 0; i < depth; i++)
fputs (" ", stdout);
printf ("path='%s'; full_name='%s'\n", fi->path, fi->full_name);
if (fi->child)
dump_fi (fi->child, depth + 1);
fi = fi->sibling;
}
}
#endif
void
em_folder_tree_set_selected (EMFolderTree *folder_tree,
const gchar *uri,
gboolean expand_only)
{
GList *l = NULL;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
if (uri && uri[0])
l = g_list_append (l, (gpointer)uri);
em_folder_tree_set_selected_list (folder_tree, l, expand_only);
g_list_free (l);
}
void
em_folder_tree_select_next_path (EMFolderTree *folder_tree, gboolean skip_read_folders)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter, parent, child;
GtkTreePath *current_path, *path = NULL;
guint unread = 0;
EMFolderTreePrivate *priv = folder_tree->priv;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
current_path = gtk_tree_model_get_path (model, &iter);
do {
if (gtk_tree_model_iter_has_child (model, &iter)) {
gtk_tree_model_iter_children (model, &child, &iter);
path = gtk_tree_model_get_path (model, &child);
iter = child;
} else {
while (1) {
gboolean has_parent = gtk_tree_model_iter_parent (model, &parent, &iter);
if (gtk_tree_model_iter_next (model, &iter)) {
path = gtk_tree_model_get_path (model, &iter);
break;
} else {
if (has_parent) {
iter = parent;
} else {
/* Reached end. Wrapup*/
gtk_tree_model_get_iter_first (model, &iter);
path = gtk_tree_model_get_path (model, &iter);
break;
}
}
}
}
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
/* TODO : Flags here for better options */
} while (skip_read_folders && unread <=0 &&
gtk_tree_path_compare (current_path, path));
}
if (path) {
if (!gtk_tree_view_row_expanded (tree_view, path))
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (tree_view, path, NULL, TRUE, 0.5f, 0.0f);
}
return;
}
static gboolean
folder_tree_descend (GtkTreeModel *model,
GtkTreeIter *iter,
GtkTreeIter *root)
{
GtkTreeIter parent;
gint n_children;
/* Finds the rightmost descendant of the given root. */
if (root == NULL) {
n_children = gtk_tree_model_iter_n_children (model, NULL);
/* This will invalidate the iterator and return FALSE. */
if (n_children == 0)
return gtk_tree_model_get_iter_first (model, iter);
gtk_tree_model_iter_nth_child (
model, &parent, NULL, n_children - 1);
} else
parent = *root;
n_children = gtk_tree_model_iter_n_children (model, &parent);
while (n_children > 0) {
GtkTreeIter child;
gtk_tree_model_iter_nth_child (
model, &child, &parent, n_children - 1);
parent = child;
n_children = gtk_tree_model_iter_n_children (model, &parent);
}
*iter = parent;
return TRUE;
}
void
em_folder_tree_select_prev_path (EMFolderTree *folder_tree,
gboolean skip_read_folders)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreePath *path = NULL;
GtkTreePath *sentinel;
GtkTreeIter iter;
guint unread = 0;
EMFolderTreePrivate *priv = folder_tree->priv;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
/* Nothing selected means nothing to do. */
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return;
/* This prevents us from looping over the model indefinitely,
* looking for unread messages when there are none. */
sentinel = gtk_tree_model_get_path (model, &iter);
do {
GtkTreeIter descendant;
if (path != NULL)
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &iter);
if (gtk_tree_path_prev (path)) {
gtk_tree_model_get_iter (model, &iter, path);
folder_tree_descend (model, &descendant, &iter);
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &descendant);
} else if (gtk_tree_path_get_depth (path) > 1) {
gtk_tree_path_up (path);
} else {
folder_tree_descend (model, &descendant, NULL);
gtk_tree_path_free (path);
path = gtk_tree_model_get_path (model, &descendant);
}
gtk_tree_model_get_iter (model, &iter, path);
gtk_tree_model_get (model, &iter, COL_UINT_UNREAD, &unread, -1);
} while (skip_read_folders && unread <= 0 &&
gtk_tree_path_compare (path, sentinel) != 0);
if (!gtk_tree_view_row_expanded (tree_view, path))
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_selection_select_path (selection, path);
if (!priv->cursor_set) {
gtk_tree_view_set_cursor (tree_view, path, NULL, FALSE);
priv->cursor_set = TRUE;
}
gtk_tree_view_scroll_to_cell (
tree_view, path, NULL, TRUE, 0.5f, 0.0f);
gtk_tree_path_free (sentinel);
gtk_tree_path_free (path);
}
void
em_folder_tree_edit_selected (EMFolderTree *folder_tree)
{
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkTreeView *tree_view;
GtkTreeModel *model;
GtkTreePath *path = NULL;
GtkTreeIter iter;
g_return_if_fail (EM_IS_FOLDER_TREE (folder_tree));
tree_view = GTK_TREE_VIEW (folder_tree);
column = gtk_tree_view_get_column (tree_view, 0);
selection = gtk_tree_view_get_selection (tree_view);
renderer = folder_tree->priv->text_renderer;
if (gtk_tree_selection_get_selected (selection, &model, &iter))
path = gtk_tree_model_get_path (model, &iter);
if (path == NULL)
return;
/* Make the text cell renderer editable, but only temporarily.
* We don't want editing to be activated by simply clicking on
* the folder name. Too easy for accidental edits to occur. */
g_object_set (renderer, "editable", TRUE, NULL);
gtk_tree_view_expand_to_path (tree_view, path);
gtk_tree_view_set_cursor_on_cell (
tree_view, path, column, renderer, TRUE);
g_object_set (renderer, "editable", FALSE, NULL);
gtk_tree_path_free (path);
}
gchar *
em_folder_tree_get_selected_uri (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *uri = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return NULL;
gtk_tree_model_get (model, &iter, COL_STRING_URI, &uri, -1);
return uri;
}
gchar *
em_folder_tree_get_selected_path (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *name = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (!gtk_tree_selection_get_selected (selection, &model, &iter))
return NULL;
gtk_tree_model_get (model, &iter, COL_STRING_FULL_NAME, &name, -1);
return name;
}
CamelFolder *
em_folder_tree_get_selected_folder (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *full_name = NULL;
CamelStore *store = NULL;
CamelFolder *folder = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_selected (selection, &model, &iter))
gtk_tree_model_get (model, &iter, COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &full_name, -1);
/* FIXME camel_store_get_folder_sync() may block. */
if (store && full_name)
folder = camel_store_get_folder_sync (
store, full_name,
CAMEL_STORE_FOLDER_INFO_FAST, NULL, NULL);
g_free (full_name);
return folder;
}
CamelFolderInfo *
em_folder_tree_get_selected_folder_info (EMFolderTree *folder_tree)
{
GtkTreeView *tree_view;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
gchar *full_name = NULL, *name = NULL, *uri = NULL;
CamelStore *store = NULL;
CamelFolderInfo *fi = NULL;
g_return_val_if_fail (EM_IS_FOLDER_TREE (folder_tree), NULL);
tree_view = GTK_TREE_VIEW (folder_tree);
selection = gtk_tree_view_get_selection (tree_view);
if (gtk_tree_selection_get_selected (selection, &model, &iter))
gtk_tree_model_get (model, &iter, COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FULL_NAME, &full_name,
COL_STRING_DISPLAY_NAME, &name,
COL_STRING_URI, &uri, -1);
fi = camel_folder_info_new ();
fi->full_name = g_strdup (full_name);
fi->uri = g_strdup (uri);
fi->name = g_strdup (name);
if (!fi->full_name)
goto done;
g_free (fi->name);
if (!g_ascii_strcasecmp (fi->full_name, "INBOX"))
fi->name = g_strdup (_("Inbox"));
else
fi->name = g_strdup (name);
done:
return fi;
}
void
em_folder_tree_set_skip_double_click (EMFolderTree *folder_tree, gboolean skip)
{
folder_tree->priv->skip_double_click = skip;
}
/* stores come first, then by uri */
static gint
sort_by_store_and_uri (gconstpointer name1, gconstpointer name2)
{
const gchar *n1 = name1, *n2 = name2;
gboolean is_store1, is_store2;
if (n1 == NULL || n2 == NULL) {
if (n1 == n2)
return 0;
else
return n1 ? -1 : 1;
}
is_store1 = g_str_has_prefix (n1, "Store ");
is_store2 = g_str_has_prefix (n2, "Store ");
if ((is_store1 || is_store2) && (!is_store1 || !is_store2)) {
return is_store1 ? -1 : 1;
}
return strcmp (n1, n2);
}
/* restores state of a tree (collapsed/expanded) as stores in the given key_file */
void
em_folder_tree_restore_state (EMFolderTree *folder_tree,
GKeyFile *key_file)
{
EShell *shell;
GtkTreeModel *tree_model;
GtkTreeView *tree_view;
GtkTreeIter iter;
gboolean valid;
gchar **groups_arr;
GSList *groups, *group;
gint ii;
/* Make sure we have a key file to restore state from. */
if (key_file == NULL)
return;
/* XXX Pass this in. */
shell = e_shell_get_default ();
tree_view = GTK_TREE_VIEW (folder_tree);
tree_model = gtk_tree_view_get_model (tree_view);
/* Set the initial folder tree expanded state in two stages:
*
* 1) Iterate over the "Store" and "Folder" state file groups
* and apply the "Expanded" keys where possible.
*
* 2) Iterate over the top-level nodes in the folder tree
* (these are all stores) and expand those that have no
* corresponding "Expanded" key in the state file. This
* ensures that new stores are expanded by default.
*/
/* Stage 1 */
/* Collapse all so we have a clean slate. */
gtk_tree_view_collapse_all (tree_view);
groups_arr = g_key_file_get_groups (key_file, NULL);
groups = NULL;
for (ii = 0; groups_arr[ii] != NULL; ii++) {
groups = g_slist_prepend (groups, groups_arr[ii]);
}
groups = g_slist_sort (groups, sort_by_store_and_uri);
for (group = groups; group != NULL; group = group->next) {
GtkTreeRowReference *reference;
GtkTreePath *path;
GtkTreeIter iter;
const gchar *group_name = group->data;
const gchar *key = STATE_KEY_EXPANDED;
const gchar *uri;
gboolean expanded;
if (g_str_has_prefix (group_name, "Store ")) {
uri = group_name + 6;
expanded = TRUE;
} else if (g_str_has_prefix (group_name, "Folder ")) {
uri = group_name + 7;
expanded = FALSE;
} else
continue;
if (g_key_file_has_key (key_file, group_name, key, NULL))
expanded = g_key_file_get_boolean (
key_file, group_name, key, NULL);
if (!expanded)
continue;
reference = em_folder_tree_model_lookup_uri (
EM_FOLDER_TREE_MODEL (tree_model), uri);
if (reference == NULL)
continue;
path = gtk_tree_row_reference_get_path (reference);
gtk_tree_model_get_iter (tree_model, &iter, path);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
}
g_slist_free (groups);
g_strfreev (groups_arr);
/* Stage 2 */
valid = gtk_tree_model_get_iter_first (tree_model, &iter);
while (valid) {
const gchar *key = STATE_KEY_EXPANDED;
gboolean expand_row;
gchar *group_name;
gchar *uri;
gtk_tree_model_get (
tree_model, &iter, COL_STRING_URI, &uri, -1);
if (uri == NULL)
goto next;
group_name = g_strdup_printf ("Store %s", uri);
/* Expand stores that have no "Expanded" key. */
expand_row = !g_key_file_has_key (
key_file, group_name, key, NULL);
/* Do not expand local stores in Express mode. */
if (e_shell_get_express_mode (shell)) {
expand_row &= (strncmp (uri, "vfolder", 7) != 0);
expand_row &= (strncmp (uri, "mbox", 4) != 0);
}
if (expand_row) {
GtkTreePath *path;
path = gtk_tree_model_get_path (tree_model, &iter);
gtk_tree_view_expand_row (tree_view, path, FALSE);
gtk_tree_path_free (path);
}
g_free (group_name);
g_free (uri);
next:
valid = gtk_tree_model_iter_next (tree_model, &iter);
}
}