Files
evolution/mail/em-folder-tree.c
Jeffrey Stedfast 7fecce2d23 We need to recursively copy the dragged folder tree over to the drop
2004-04-08  Jeffrey Stedfast  <fejj@ximian.com>

	* em-folder-tree.c (emft_drop_folder): We need to recursively copy
	the dragged folder tree over to the drop location, not just the
	parent folder. We also need to subscribe to the newly created
	folder in some cases.

svn path=/trunk/; revision=25375
2004-04-08 18:23:29 +00:00

2838 lines
77 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright 2003 Ximian, Inc. (www.ximian.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
*
*/
#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 <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <camel/camel-session.h>
#include <camel/camel-store.h>
#include <camel/camel-folder.h>
#include <camel/camel-vee-store.h>
#include <camel/camel-vtrash-folder.h>
#include <camel/camel-stream-mem.h>
#include <camel/camel-file-utils.h>
#include "e-util/e-mktemp.h"
#include "e-util/e-request.h"
#include "e-util/e-dialog-utils.h"
#include "filter/vfolder-rule.h"
#include "mail-mt.h"
#include "mail-ops.h"
#include "mail-tools.h"
#include "mail-config.h"
#include "mail-component.h"
#include "mail-vfolder.h"
#include "em-utils.h"
#include "em-popup.h"
#include "em-marshal.h"
#include "em-folder-tree.h"
#include "em-folder-selector.h"
#include "em-folder-selection.h"
#include "em-folder-properties.h"
#define d(x) x
struct _EMFolderTreePrivate {
GtkTreeView *treeview;
EMFolderTreeModel *model;
char *selected_uri;
char *selected_path;
guint32 excluded;
gboolean do_multiselect;
/* when doing a multiselect, folders that we didn't find */
GList *lost_folders;
guint save_state_id;
guint autoscroll_id;
guint loading_row_id;
guint loaded_row_id;
GtkTreeRowReference *drag_row;
};
enum {
FOLDER_ACTIVATED, /* aka double-clicked or user hit enter */
FOLDER_SELECTED,
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[] = {
{ "x-folder", 0, DND_DRAG_TYPE_FOLDER },
{ "text/uri-list", 0, DND_DRAG_TYPE_TEXT_URI_LIST },
};
static GtkTargetEntry drop_types[] = {
{ "x-uid-list" , 0, DND_DROP_TYPE_UID_LIST },
{ "x-folder", 0, DND_DROP_TYPE_FOLDER },
{ "message/rfc822", 0, DND_DROP_TYPE_MESSAGE_RFC822 },
{ "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 CamelSession *session;
static void em_folder_tree_class_init (EMFolderTreeClass *klass);
static void em_folder_tree_init (EMFolderTree *emft);
static void em_folder_tree_destroy (GtkObject *obj);
static void em_folder_tree_finalize (GObject *obj);
static gboolean emft_save_state (EMFolderTree *emft);
static void emft_queue_save_state (EMFolderTree *emft);
static void emft_tree_row_activated (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, EMFolderTree *emft);
static void emft_tree_row_collapsed (GtkTreeView *treeview, GtkTreeIter *root, GtkTreePath *path, EMFolderTree *emft);
static void emft_tree_row_expanded (GtkTreeView *treeview, GtkTreeIter *root, GtkTreePath *path, EMFolderTree *emft);
static gboolean emft_tree_button_press (GtkTreeView *treeview, GdkEventButton *event, EMFolderTree *emft);
static void emft_tree_selection_changed (GtkTreeSelection *selection, EMFolderTree *emft);
struct _emft_selection_data {
GtkTreeModel *model;
GtkTreeIter *iter;
gboolean set;
};
static GtkVBoxClass *parent_class = NULL;
GType
em_folder_tree_get_type (void)
{
static GType type = 0;
if (!type) {
static const GTypeInfo info = {
sizeof (EMFolderTreeClass),
NULL, /* base_class_init */
NULL, /* base_class_finalize */
(GClassInitFunc) em_folder_tree_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (EMFolderTree),
0, /* n_preallocs */
(GInstanceInitFunc) em_folder_tree_init,
};
type = g_type_register_static (GTK_TYPE_VBOX, "EMFolderTree", &info, 0);
}
return type;
}
static void
em_folder_tree_class_init (EMFolderTreeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkObjectClass *gtk_object_class = GTK_OBJECT_CLASS (klass);
parent_class = g_type_class_ref (GTK_TYPE_VBOX);
object_class->finalize = em_folder_tree_finalize;
gtk_object_class->destroy = em_folder_tree_destroy;
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,
em_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
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,
em_marshal_VOID__STRING_STRING,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_STRING);
}
static gboolean
subdirs_contain_unread (GtkTreeModel *model, GtkTreeIter *root)
{
unsigned int 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;
}
enum {
FOLDER_ICON_NORMAL,
FOLDER_ICON_INBOX,
FOLDER_ICON_OUTBOX,
FOLDER_ICON_TRASH,
FOLDER_ICON_JUNK,
FOLDER_ICON_LAST
};
static GdkPixbuf *folder_icons[FOLDER_ICON_LAST];
static void
render_pixbuf (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
{
static gboolean initialised = FALSE;
GdkPixbuf *pixbuf = NULL;
gboolean is_store;
char *path;
if (!initialised) {
folder_icons[FOLDER_ICON_NORMAL] = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/folder-mini.png", NULL);
folder_icons[FOLDER_ICON_INBOX] = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/inbox-mini.png", NULL);
folder_icons[FOLDER_ICON_OUTBOX] = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/outbox-mini.png", NULL);
folder_icons[FOLDER_ICON_TRASH] = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/evolution-trash-mini.png", NULL);
folder_icons[FOLDER_ICON_JUNK] = gdk_pixbuf_new_from_file (EVOLUTION_ICONSDIR "/evolution-junk-mini.png", NULL);
initialised = TRUE;
}
gtk_tree_model_get (model, iter, COL_STRING_FOLDER_PATH, &path,
COL_BOOL_IS_STORE, &is_store, -1);
if (!is_store && path != NULL) {
if (!strcasecmp (path, "/Inbox"))
pixbuf = folder_icons[FOLDER_ICON_INBOX];
else if (!strcasecmp (path, "/Outbox"))
pixbuf = folder_icons[FOLDER_ICON_OUTBOX];
else if (*path == '/' && !strcasecmp (path + 1, CAMEL_VTRASH_NAME))
pixbuf = folder_icons[FOLDER_ICON_TRASH];
else if (*path == '/' && !strcasecmp (path + 1, CAMEL_VJUNK_NAME))
pixbuf = folder_icons[FOLDER_ICON_JUNK];
else
pixbuf = folder_icons[FOLDER_ICON_NORMAL];
}
g_object_set (renderer, "pixbuf", pixbuf, "visible", !is_store, NULL);
}
static void
render_display_name (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
{
gboolean is_store, bold;
unsigned int unread;
char *display;
char *name;
gtk_tree_model_get (model, iter, COL_STRING_DISPLAY_NAME, &name,
COL_BOOL_IS_STORE, &is_store,
COL_UINT_UNREAD, &unread, -1);
if (!(bold = is_store || unread)) {
if (gtk_tree_model_iter_has_child (model, iter))
bold = subdirs_contain_unread (model, iter);
}
if (!is_store && unread)
display = g_strdup_printf ("%s (%u)", name, unread);
else
display = g_strdup (name);
g_object_set (renderer, "text", display,
"weight", bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
NULL);
g_free (display);
}
static gboolean
emft_select_func(GtkTreeSelection *selection, GtkTreeModel *model, GtkTreePath *path, gboolean selected, gpointer data)
{
EMFolderTree *emft = data;
gboolean is_store;
guint32 flags;
GtkTreeIter iter;
/* NB: This will be called with selection==NULL from tree_row_activated */
if (emft->priv->excluded == 0)
return TRUE;
if (!gtk_tree_model_get_iter(model, &iter, path))
return TRUE;
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 & emft->priv->excluded) == 0;
}
static void
em_folder_tree_init (EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv;
priv = g_new0 (struct _EMFolderTreePrivate, 1);
priv->lost_folders = NULL;
priv->selected_uri = NULL;
priv->selected_path = NULL;
priv->treeview = NULL;
priv->model = NULL;
priv->drag_row = NULL;
emft->priv = priv;
}
static void
em_folder_tree_finalize (GObject *obj)
{
EMFolderTree *emft = (EMFolderTree *) obj;
/* clear list of lost uris */
if (emft->priv->lost_folders) {
g_list_foreach (emft->priv->lost_folders, (GFunc) g_free, NULL);
g_list_free (emft->priv->lost_folders);
emft->priv->lost_folders = NULL;
}
g_free (emft->priv->selected_uri);
g_free (emft->priv->selected_path);
g_free (emft->priv);
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
em_folder_tree_destroy (GtkObject *obj)
{
EMFolderTree *emft = (EMFolderTree *) obj;
struct _EMFolderTreePrivate *priv = emft->priv;
if (priv->loading_row_id != 0) {
g_signal_handler_disconnect (priv->model, priv->loading_row_id);
priv->loading_row_id = 0;
}
if (priv->loaded_row_id != 0) {
g_signal_handler_disconnect (priv->model, priv->loaded_row_id);
priv->loaded_row_id = 0;
}
if (priv->save_state_id != 0) {
g_source_remove (priv->save_state_id);
emft_save_state (emft);
}
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
priv->treeview = NULL;
priv->model = NULL;
GTK_OBJECT_CLASS (parent_class)->destroy (obj);
}
static GtkTreeView *
folder_tree_new (EMFolderTree *emft, EMFolderTreeModel *model)
{
GtkTreeSelection *selection;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
GtkWidget *tree;
tree = gtk_tree_view_new_with_model ((GtkTreeModel *) model);
GTK_WIDGET_SET_FLAGS(tree, GTK_CAN_FOCUS);
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_set_cell_data_func (column, renderer, render_pixbuf, 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);
selection = gtk_tree_view_get_selection ((GtkTreeView *) tree);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
gtk_tree_selection_set_select_function(selection, emft_select_func, emft, NULL);
gtk_tree_view_set_headers_visible ((GtkTreeView *) tree, FALSE);
return (GtkTreeView *) tree;
}
static void
em_folder_tree_construct (EMFolderTree *emft, EMFolderTreeModel *model)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeSelection *selection;
GtkWidget *scrolled;
scrolled = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
priv->model = model;
priv->treeview = folder_tree_new (emft, model);
gtk_widget_show ((GtkWidget *) priv->treeview);
g_signal_connect (priv->treeview, "row-expanded", G_CALLBACK (emft_tree_row_expanded), emft);
g_signal_connect (priv->treeview, "row-collapsed", G_CALLBACK (emft_tree_row_collapsed), emft);
g_signal_connect (priv->treeview, "row-activated", G_CALLBACK (emft_tree_row_activated), emft);
g_signal_connect (priv->treeview, "button-press-event", G_CALLBACK (emft_tree_button_press), emft);
selection = gtk_tree_view_get_selection ((GtkTreeView *) priv->treeview);
g_signal_connect (selection, "changed", G_CALLBACK (emft_tree_selection_changed), emft);
gtk_container_add ((GtkContainer *) scrolled, (GtkWidget *) priv->treeview);
gtk_widget_show (scrolled);
gtk_box_pack_start ((GtkBox *) emft, scrolled, TRUE, TRUE, 0);
}
GtkWidget *
em_folder_tree_new (void)
{
EMFolderTreeModel *model;
EMFolderTree *emft;
model = em_folder_tree_model_new (mail_component_peek_base_directory (mail_component_peek ()));
emft = (EMFolderTree *) em_folder_tree_new_with_model (model);
g_object_unref (model);
return (GtkWidget *) emft;
}
static void
emft_expand_node (EMFolderTreeModel *model, const char *key, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
struct _EMFolderTreeModelStoreInfo *si;
extern CamelStore *vfolder_store;
GtkTreeRowReference *row;
GtkTreePath *path;
EAccount *account;
CamelStore *store;
const char *p;
char *uid;
size_t n;
if (!(p = strchr (key, '/')))
n = strlen (key);
else
n = (p - key);
uid = g_alloca (n + 1);
memcpy (uid, key, n);
uid[n] = '\0';
if ((account = mail_config_get_account_by_uid (uid)) && account->enabled) {
CamelException ex;
camel_exception_init (&ex);
store = (CamelStore *) camel_session_get_service (session, account->source->url, CAMEL_PROVIDER_STORE, &ex);
camel_exception_clear (&ex);
if (store == NULL)
return;
} else if (!strcmp (uid, "vfolder")) {
if (!(store = vfolder_store))
return;
camel_object_ref (store);
} else if (!strcmp (uid, "local")) {
if (!(store = mail_component_peek_local_store (NULL)))
return;
camel_object_ref (store);
} else {
return;
}
if (!(si = g_hash_table_lookup (priv->model->store_hash, store))) {
camel_object_unref (store);
return;
}
camel_object_unref (store);
if (p != NULL) {
if (!(row = g_hash_table_lookup (si->path_hash, p)))
return;
} else
row = si->row;
path = gtk_tree_row_reference_get_path (row);
gtk_tree_view_expand_row (priv->treeview, path, FALSE);
gtk_tree_path_free (path);
}
static void
emft_maybe_expand_row (EMFolderTreeModel *model, GtkTreePath *tree_path, GtkTreeIter *iter, EMFolderTree *emft)
{
struct _EMFolderTreeModelStoreInfo *si;
CamelStore *store;
EAccount *account;
char *path, *key;
gtk_tree_model_get ((GtkTreeModel *) model, iter,
COL_STRING_FOLDER_PATH, &path,
COL_POINTER_CAMEL_STORE, &store,
-1);
si = g_hash_table_lookup (model->store_hash, store);
if ((account = mail_config_get_account_by_name (si->display_name))) {
key = g_strdup_printf ("%s%s", account->uid, path);
} else if (CAMEL_IS_VEE_STORE (store)) {
/* vfolder store */
key = g_strdup_printf ("vfolder%s", path);
} else {
/* local store */
key = g_strdup_printf ("local%s", path);
}
if (em_folder_tree_model_get_expanded (model, key)) {
gtk_tree_view_expand_to_path (emft->priv->treeview, tree_path);
gtk_tree_view_expand_row (emft->priv->treeview, tree_path, FALSE);
}
g_free (key);
}
GtkWidget *
em_folder_tree_new_with_model (EMFolderTreeModel *model)
{
EMFolderTree *emft;
emft = g_object_new (EM_TYPE_FOLDER_TREE, NULL);
em_folder_tree_construct (emft, model);
g_object_ref (model);
em_folder_tree_model_expand_foreach (model, emft_expand_node, emft);
emft->priv->loading_row_id = g_signal_connect (model, "loading-row", G_CALLBACK (emft_maybe_expand_row), emft);
emft->priv->loaded_row_id = g_signal_connect (model, "loaded-row", G_CALLBACK (emft_maybe_expand_row), emft);
return (GtkWidget *) emft;
}
static void
tree_drag_begin (GtkWidget *widget, GdkDragContext *context, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreePath *path;
GtkTreeIter iter;
selection = gtk_tree_view_get_selection ((GtkTreeView *) widget);
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);
gtk_tree_path_free (path);
/* FIXME: set a drag icon? */
}
static void
tree_drag_data_delete(GtkWidget *widget, GdkDragContext *context, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreePath *src_path;
const char *full_name;
gboolean is_store;
CamelStore *store;
CamelException ex;
GtkTreeIter iter;
char *path;
if (!priv->drag_row || (src_path = gtk_tree_row_reference_get_path (priv->drag_row)))
return;
if (!gtk_tree_model_get_iter((GtkTreeModel *)priv->model, &iter, src_path))
goto fail;
gtk_tree_model_get((GtkTreeModel *)priv->model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FOLDER_PATH, &path,
COL_BOOL_IS_STORE, &is_store, -1);
if (is_store)
goto fail;
full_name = path[0] == '/' ? path + 1 : path;
camel_exception_init(&ex);
camel_store_delete_folder(store, full_name, &ex);
if (camel_exception_is_set(&ex))
camel_exception_clear(&ex);
fail:
gtk_tree_path_free(src_path);
}
static void
tree_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreePath *src_path;
const char *full_name;
CamelFolder *folder;
CamelStore *store;
CamelException ex;
GtkTreeIter iter;
char *path, *uri;
if (!priv->drag_row || !(src_path = gtk_tree_row_reference_get_path(priv->drag_row)))
return;
if (!gtk_tree_model_get_iter((GtkTreeModel *)priv->model, &iter, src_path))
goto fail;
gtk_tree_model_get((GtkTreeModel *)priv->model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FOLDER_PATH, &path,
COL_STRING_URI, &uri, -1);
/* make sure user isn't trying to drag on a placeholder row */
if (path == NULL)
goto fail;
full_name = path[0] == '/' ? path + 1 : path;
camel_exception_init(&ex);
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, uri, strlen (uri) + 1);
break;
case DND_DRAG_TYPE_TEXT_URI_LIST:
/* dragging to nautilus or something, probably */
if ((folder = camel_store_get_folder(store, full_name, 0, &ex))) {
GPtrArray *uids = camel_folder_get_uids(folder);
em_utils_selection_set_urilist(selection, folder, uids);
camel_folder_free_uids(folder, uids);
camel_object_unref(folder);
}
break;
default:
abort();
}
if (camel_exception_is_set(&ex))
camel_exception_clear(&ex);
fail:
gtk_tree_path_free(src_path);
}
/* Drop handling */
struct _DragDataReceivedAsync {
struct _mail_msg msg;
/* input data */
GdkDragContext *context;
union {
CamelStreamMem *rfc822;
char *folder;
char **urilist;
struct {
char *uri;
GPtrArray *uids;
} uidlist;
} selection;
CamelStore *store;
char *full_name;
gboolean move;
guint info;
/* output data */
gboolean moved;
};
static void
emft_drop_uid_list(struct _DragDataReceivedAsync *m, CamelFolder *dest)
{
CamelFolder *src;
d(printf(" * drop uid list from '%s'\n", m->selection.uidlist.uri));
if (!(src = mail_tool_uri_to_folder(m->selection.uidlist.uri, 0, &m->msg.ex)))
return;
camel_folder_transfer_messages_to(src, m->selection.uidlist.uids, dest, NULL, m->move, &m->msg.ex);
camel_object_unref(src);
m->moved = m->move && !camel_exception_is_set(&m->msg.ex);
}
static void
emft_drop_folder_rec (CamelStore *store, CamelFolderInfo *fi, const char *parent_name, CamelException *ex)
{
CamelFolder *src, *dest;
CamelFolderInfo *nfi;
char *new_name;
while (fi != NULL) {
if (!(src = mail_tool_uri_to_folder (fi->uri, 0, ex)))
break;
/* handles dropping to the root properly */
if (parent_name[0])
new_name = g_strdup_printf ("%s/%s", parent_name, src->name);
else
new_name = g_strdup (src->name);
if ((nfi = camel_store_create_folder (store, parent_name, src->name, ex))) {
camel_store_free_folder_info (store, nfi);
if (camel_store_supports_subscriptions (store))
camel_store_subscribe_folder (store, new_name, ex);
/* copy the folder to the new location */
if ((dest = camel_store_get_folder (store, new_name, 0, ex))) {
GPtrArray *uids;
uids = camel_folder_get_uids (src);
camel_folder_transfer_messages_to (src, uids, dest, NULL, FALSE, ex);
camel_folder_free_uids (src, uids);
camel_object_unref (dest);
}
}
camel_object_unref (src);
if (fi->child)
emft_drop_folder_rec (store, fi->child, new_name, ex);
g_free (new_name);
fi = fi->next;
}
}
static void
emft_drop_folder(struct _DragDataReceivedAsync *m)
{
CamelFolder *src;
char *new_name;
d(printf(" * Drop folder '%s' onto '%s'\n", m->selection.folder, m->full_name));
if (!(src = mail_tool_uri_to_folder(m->selection.folder, 0, &m->msg.ex)))
return;
/* handles dropping to the root properly */
if (m->full_name[0])
new_name = g_strdup_printf("%s/%s", m->full_name, src->name);
else
new_name = g_strdup(src->name);
if (src->parent_store == m->store && m->move) {
/* simple case, rename */
camel_store_rename_folder(m->store, src->full_name, new_name, &m->msg.ex);
m->moved = !camel_exception_is_set (&m->msg.ex);
} else {
CamelFolderInfo *fi, *nfi;
/* FIXME: should check we're not coming from a vfolder, otherwise bad stuff could happen */
if ((fi = camel_store_get_folder_info (src->parent_store, src->full_name, CAMEL_STORE_FOLDER_INFO_FAST |
CAMEL_STORE_FOLDER_INFO_RECURSIVE, &m->msg.ex))) {
if (!(nfi = camel_store_get_folder_info (m->store, new_name, CAMEL_STORE_FOLDER_INFO_FAST, &m->msg.ex))) {
/* Good. The folder doesn't already exist... */
camel_exception_clear (&m->msg.ex);
emft_drop_folder_rec (m->store, fi, m->full_name, &m->msg.ex);
}
camel_store_free_folder_info (src->parent_store, fi);
}
}
g_free(new_name);
camel_object_unref(src);
}
static gboolean
emft_import_message_rfc822 (CamelFolder *dest, CamelStream *stream, gboolean scan_from, CamelException *ex)
{
CamelMimeParser *mp;
mp = camel_mime_parser_new ();
camel_mime_parser_scan_from (mp, scan_from);
camel_mime_parser_init_with_stream (mp, stream);
while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) {
CamelMessageInfo *info;
CamelMimeMessage *msg;
msg = camel_mime_message_new ();
if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) {
camel_object_unref (msg);
camel_object_unref (mp);
return FALSE;
}
/* append the message to the folder... */
info = g_new0 (CamelMessageInfo, 1);
camel_folder_append_message (dest, msg, info, NULL, ex);
camel_object_unref (msg);
if (camel_exception_is_set (ex)) {
camel_object_unref (mp);
return FALSE;
}
/* skip over the FROM_END state */
camel_mime_parser_step (mp, 0, 0);
}
camel_object_unref (mp);
return TRUE;
}
static void
emft_drop_message_rfc822(struct _DragDataReceivedAsync *m, CamelFolder *dest)
{
gboolean scan_from;
d(printf(" * drop message/rfc822\n"));
scan_from = m->selection.rfc822->buffer->len > 5
&& !strncmp(m->selection.rfc822->buffer->data, "From ", 5);
emft_import_message_rfc822(dest, (CamelStream *)m->selection.rfc822, scan_from, &m->msg.ex);
}
static void
emft_drop_text_uri_list(struct _DragDataReceivedAsync *m, CamelFolder *dest)
{
CamelStream *stream;
CamelURL *url;
int fd, i, go=1;
d(printf(" * drop uri list\n"));
for (i = 0; go && m->selection.urilist[i] != NULL; i++) {
d(printf(" - '%s'\n", (char *)m->selection.urilist[i]));
url = camel_url_new(m->selection.urilist[i], NULL);
if (url == NULL)
continue;
if (strcmp(url->protocol, "file") == 0
&& (fd = open(url->path, O_RDONLY)) != -1) {
stream = camel_stream_fs_new_with_fd(fd);
go = emft_import_message_rfc822(dest, stream, TRUE, &m->msg.ex);
camel_object_unref(stream);
}
camel_url_free(url);
}
}
static char *
emft_drop_async_desc (struct _mail_msg *mm, int done)
{
struct _DragDataReceivedAsync *m = (struct _DragDataReceivedAsync *) mm;
CamelURL *url;
char *buf;
if (m->info == DND_DROP_TYPE_FOLDER) {
url = camel_url_new (m->selection.folder, 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
emft_drop_async_drop (struct _mail_msg *mm)
{
struct _DragDataReceivedAsync *m = (struct _DragDataReceivedAsync *) mm;
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 */
emft_drop_folder(m);
} else if (m->full_name[0] == 0) {
camel_exception_set (&mm->ex, CAMEL_EXCEPTION_SYSTEM,
_("Cannot drop message(s) into toplevel store"));
} else if ((folder = camel_store_get_folder (m->store, m->full_name, 0, &mm->ex))) {
switch (m->info) {
case DND_DROP_TYPE_UID_LIST:
/* import a list of uids from another evo folder */
emft_drop_uid_list(m, folder);
break;
case DND_DROP_TYPE_MESSAGE_RFC822:
/* import a message/rfc822 stream */
emft_drop_message_rfc822(m, folder);
break;
case DND_DROP_TYPE_TEXT_URI_LIST:
/* import an mbox, maildir, or mh folder? */
emft_drop_text_uri_list(m, folder);
break;
default:
abort();
}
camel_object_unref(folder);
}
}
static void
emft_drop_async_done (struct _mail_msg *mm)
{
struct _DragDataReceivedAsync *m = (struct _DragDataReceivedAsync *) mm;
gboolean success, delete;
success = !camel_exception_is_set (&mm->ex);
delete = success && m->move && !m->moved;
gtk_drag_finish (m->context, success, delete, GDK_CURRENT_TIME);
}
static void
emft_drop_async_free (struct _mail_msg *mm)
{
struct _DragDataReceivedAsync *m = (struct _DragDataReceivedAsync *) mm;
g_object_unref(m->context);
camel_object_unref(m->store);
g_free(m->full_name);
switch (m->info) {
case DND_DROP_TYPE_FOLDER:
g_free(m->selection.folder);
break;
case DND_DROP_TYPE_UID_LIST:
g_free(m->selection.uidlist.uri);
em_utils_uids_free(m->selection.uidlist.uids);
break;
case DND_DROP_TYPE_MESSAGE_RFC822:
camel_object_unref(m->selection.rfc822);
break;
case DND_DROP_TYPE_TEXT_URI_LIST:
g_strfreev(m->selection.urilist);
break;
default:
abort();
}
}
static struct _mail_msg_op emft_drop_async_op = {
emft_drop_async_desc,
emft_drop_async_drop,
emft_drop_async_done,
emft_drop_async_free,
};
static void
tree_drag_data_received(GtkWidget *widget, GdkDragContext *context, int x, int y, GtkSelectionData *selection, guint info, guint time, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeViewDropPosition pos;
GtkTreePath *dest_path;
struct _DragDataReceivedAsync *m;
const char *full_name;
CamelStore *store;
GtkTreeIter iter;
char *path, *tmp;
int i;
if (!gtk_tree_view_get_dest_row_at_pos (priv->treeview, x, y, &dest_path, &pos))
return;
/* this means we are receiving no data */
if (!selection->data || selection->length == -1) {
gtk_drag_finish(context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
if (!gtk_tree_model_get_iter((GtkTreeModel *)priv->model, &iter, dest_path)) {
gtk_drag_finish(context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
gtk_tree_model_get((GtkTreeModel *)priv->model, &iter,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_FOLDER_PATH, &path, -1);
/* make sure user isn't try to drop on a placeholder row */
if (path == NULL) {
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
return;
}
full_name = path[0] == '/' ? path + 1 : path;
m = mail_msg_new (&emft_drop_async_op, NULL, sizeof (struct _DragDataReceivedAsync));
m->context = context;
g_object_ref(context);
m->store = store;
camel_object_ref(store);
m->full_name = g_strdup (full_name);
m->move = context->action == GDK_ACTION_MOVE;
m->info = info;
switch (info) {
case DND_DROP_TYPE_FOLDER:
m->selection.folder = g_strdup(selection->data);
break;
case DND_DROP_TYPE_UID_LIST:
em_utils_selection_get_uidlist(selection, &m->selection.uidlist.uri, &m->selection.uidlist.uids);
break;
case DND_DROP_TYPE_MESSAGE_RFC822:
m->selection.rfc822 = (CamelStreamMem *)camel_stream_mem_new_with_buffer(selection->data, selection->length);
break;
case DND_DROP_TYPE_TEXT_URI_LIST:
tmp = g_strndup(selection->data, selection->length);
m->selection.urilist = g_strsplit(tmp, "\n", 0);
g_free(tmp);
for (i=0;m->selection.urilist[i];i++)
g_strstrip(m->selection.urilist[i]);
break;
default:
abort();
}
e_thread_put (mail_thread_new, (EMsg *) m);
}
static gboolean
is_special_local_folder (const char *name)
{
return (!strcmp (name, "Drafts") || !strcmp (name, "Inbox") || !strcmp (name, "Outbox") || !strcmp (name, "Sent"));
}
static GdkAtom
emft_drop_target(EMFolderTree *emft, GdkDragContext *context, GtkTreePath *path)
{
struct _EMFolderTreePrivate *p = emft->priv;
char *uri, *folder_path, *src_uri = NULL;
CamelStore *local, *sstore, *dstore;
gboolean is_store;
GtkTreeIter iter;
GList *targets;
/* This is a bit of a mess, but should handle all the cases properly */
if (!gtk_tree_model_get_iter((GtkTreeModel *)p->model, &iter, path))
return GDK_NONE;
gtk_tree_model_get((GtkTreeModel *)p->model, &iter, COL_BOOL_IS_STORE, &is_store,
COL_STRING_FOLDER_PATH, &folder_path,
COL_POINTER_CAMEL_STORE, &dstore,
COL_STRING_URI, &uri, -1);
local = mail_component_peek_local_store (NULL);
targets = context->targets;
/* Check for special destinations */
if (uri && folder_path) {
folder_path = folder_path[0] == '/' ? folder_path + 1 : folder_path;
#if 0
/* only allow copying/moving folders (not messages) into the local Outbox */
if (dstore == local && !strcmp (folder_path, "Outbox")) {
GdkAtom xfolder;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder)
return xfolder;
targets = targets->next;
}
return GDK_NONE;
}
#endif
/* don't allow copying/moving into the UNMATCHED vfolder */
if (!strncmp (uri, "vfolder:", 8) && !strcmp (folder_path, CAMEL_UNMATCHED_NAME))
return GDK_NONE;
/* don't allow copying/moving into a vTrash/vJunk folder */
if (!strcmp (folder_path, CAMEL_VTRASH_NAME)
|| !strcmp (folder_path, CAMEL_VJUNK_NAME))
return GDK_NONE;
}
if (p->drag_row) {
GtkTreePath *src_path = gtk_tree_row_reference_get_path(p->drag_row);
if (src_path) {
if (gtk_tree_model_get_iter((GtkTreeModel *)p->model, &iter, src_path))
gtk_tree_model_get((GtkTreeModel *)p->model, &iter,
COL_POINTER_CAMEL_STORE, &sstore,
COL_STRING_URI, &src_uri, -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);
return GDK_NONE;
}
gtk_tree_path_free(src_path);
}
}
/* Check for special sources, and vfolder stuff */
if (src_uri) {
CamelURL *url;
char *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);
path = url->fragment?url->fragment:url->path;
if (path && path[0]) {
/* don't allow moving any of the the local special folders */
if (sstore == local && is_special_local_folder (path)) {
GdkAtom xfolder;
camel_url_free (url);
/* TODO: not sure if this is legal, but it works, force copy for special local folders */
context->suggested_action = GDK_ACTION_COPY;
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
while (targets != NULL) {
if (targets->data == (gpointer) xfolder)
return xfolder;
targets = targets->next;
}
return GDK_NONE;
}
/* don't allow copying/moving of the UNMATCHED vfolder */
if (!strcmp (url->protocol, "vfolder") && !strcmp (path, CAMEL_UNMATCHED_NAME)) {
camel_url_free (url);
return GDK_NONE;
}
/* don't allow copying/moving of any vTrash/vJunk folder nor maildir 'inbox' */
if (strcmp(path, CAMEL_VTRASH_NAME) == 0
|| strcmp(path, CAMEL_VJUNK_NAME) == 0
/* Dont allow drag from maildir 'inbox' */
|| strcmp(path, ".") == 0) {
camel_url_free(url);
return GDK_NONE;
}
}
camel_url_free(url);
/* vFolders can only be dropped into other vFolders */
if (strncmp(src_uri, "vfolder:", 8) == 0) {
/* TODO: not sure if this is legal, but it works, force move only for vfolders */
context->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)
return xfolder;
targets = targets->next;
}
}
return GDK_NONE;
}
}
/* can't drag anything but a vfolder into a vfolder */
if (uri && strncmp(uri, "vfolder:", 8) == 0)
return GDK_NONE;
/* 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)
return xfolder;
targets = targets->next;
}
} else {
int i;
while (targets != NULL) {
for (i = 0; i < NUM_DROP_TYPES; i++) {
if (targets->data == (gpointer) drop_atoms[i])
return drop_atoms[i];
}
targets = targets->next;
}
}
return GDK_NONE;
}
static gboolean
tree_drag_drop (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeViewColumn *column;
int cell_x, cell_y;
GtkTreePath *path;
GdkAtom target;
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
if (!gtk_tree_view_get_path_at_pos (priv->treeview, x, y, &path, &column, &cell_x, &cell_y))
return FALSE;
target = emft_drop_target(emft, context, path);
gtk_tree_path_free (path);
if (target == GDK_NONE)
return FALSE;
return TRUE;
}
static void
tree_drag_end (GtkWidget *widget, GdkDragContext *context, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
if (priv->drag_row) {
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 *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
if (priv->autoscroll_id != 0) {
g_source_remove (priv->autoscroll_id);
priv->autoscroll_id = 0;
}
gtk_tree_view_set_drag_dest_row(emft->priv->treeview, NULL, GTK_TREE_VIEW_DROP_BEFORE);
}
#define SCROLL_EDGE_SIZE 15
static gboolean
tree_autoscroll (EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkAdjustment *vadjustment;
GdkRectangle rect;
GdkWindow *window;
int offset, y;
float value;
/* get the y pointer position relative to the treeview */
window = gtk_tree_view_get_bin_window (priv->treeview);
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 (priv->treeview, &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;
}
vadjustment = gtk_tree_view_get_vadjustment (priv->treeview);
value = CLAMP (vadjustment->value + offset, 0.0, vadjustment->upper - vadjustment->page_size);
gtk_adjustment_set_value (vadjustment, value);
return TRUE;
}
static gboolean
tree_drag_motion (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeViewDropPosition pos;
GdkDragAction action = 0;
GtkTreePath *path;
GdkAtom target;
int i;
if (!gtk_tree_view_get_dest_row_at_pos(priv->treeview, x, y, &path, &pos))
return FALSE;
if (priv->autoscroll_id == 0)
priv->autoscroll_id = g_timeout_add (150, (GSourceFunc) tree_autoscroll, emft);
target = emft_drop_target(emft, context, path);
if (target != GDK_NONE) {
for (i=0; i<NUM_DROP_TYPES; i++) {
if (drop_atoms[i] == target) {
switch (i) {
case DND_DROP_TYPE_FOLDER:
action = context->suggested_action;
if (context->actions & GDK_ACTION_MOVE)
action = GDK_ACTION_MOVE;
gtk_tree_view_set_drag_dest_row(priv->treeview, path, GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
break;
case DND_DROP_TYPE_UID_LIST:
action = context->suggested_action;
if (context->actions & GDK_ACTION_MOVE)
action = GDK_ACTION_MOVE;
gtk_tree_view_set_drag_dest_row(priv->treeview, path, GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
break;
default:
gtk_tree_view_set_drag_dest_row(priv->treeview, path, GTK_TREE_VIEW_DROP_INTO_OR_AFTER);
action = context->suggested_action;
break;
}
break;
}
}
}
gtk_tree_path_free(path);
gdk_drag_status(context, action, time);
return action != 0;
}
void
em_folder_tree_enable_drag_and_drop (EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv;
static int setup = 0;
int i;
g_return_if_fail (EM_IS_FOLDER_TREE (emft));
priv = emft->priv;
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((GtkWidget *)priv->treeview, GDK_BUTTON1_MASK, drag_types, NUM_DRAG_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
gtk_drag_dest_set((GtkWidget *)priv->treeview, GTK_DEST_DEFAULT_ALL, drop_types, NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE);
g_signal_connect (priv->treeview, "drag-begin", G_CALLBACK (tree_drag_begin), emft);
g_signal_connect (priv->treeview, "drag-data-delete", G_CALLBACK (tree_drag_data_delete), emft);
g_signal_connect (priv->treeview, "drag-data-get", G_CALLBACK (tree_drag_data_get), emft);
g_signal_connect (priv->treeview, "drag-data-received", G_CALLBACK (tree_drag_data_received), emft);
g_signal_connect (priv->treeview, "drag-drop", G_CALLBACK (tree_drag_drop), emft);
g_signal_connect (priv->treeview, "drag-end", G_CALLBACK (tree_drag_end), emft);
g_signal_connect (priv->treeview, "drag-leave", G_CALLBACK (tree_drag_leave), emft);
g_signal_connect (priv->treeview, "drag-motion", G_CALLBACK (tree_drag_motion), emft);
}
void
em_folder_tree_set_multiselect (EMFolderTree *tree, gboolean mode)
{
GtkTreeSelection *sel = gtk_tree_view_get_selection ((GtkTreeView *) tree->priv->treeview);
tree->priv->do_multiselect = mode;
gtk_tree_selection_set_mode (sel, mode ? GTK_SELECTION_MULTIPLE : GTK_SELECTION_SINGLE);
}
void em_folder_tree_set_excluded(EMFolderTree *emft, guint32 flags)
{
emft->priv->excluded = flags;
}
static void
get_selected_uris_iterate (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
GList **list = (GList **) data;
char *uri;
gtk_tree_model_get (model, iter, /*COL_STRING_FOLDER_PATH, &path,*/
COL_STRING_URI, &uri, -1);
*list = g_list_append (*list, g_strdup (uri));
}
GList *
em_folder_tree_get_selected_uris (EMFolderTree *emft)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection (emft->priv->treeview);
GList *lost = emft->priv->lost_folders;
GList *list = NULL;
/* at first, add lost uris */
while (lost) {
list = g_list_append (list, g_strdup (lost->data));
lost = g_list_next (lost);
}
gtk_tree_selection_selected_foreach (selection, get_selected_uris_iterate, &list);
return list;
}
static void
get_selected_uris_path_iterate (GtkTreeModel *model, GtkTreePath *treepath, GtkTreeIter *iter, gpointer data)
{
GList **list = (GList **) data;
char *path;
gtk_tree_model_get (model, iter, COL_STRING_FOLDER_PATH, &path, -1);
*list = g_list_append (*list, g_strdup (path));
}
GList *
em_folder_tree_get_selected_paths (EMFolderTree *emft)
{
GtkTreeSelection *selection = gtk_tree_view_get_selection (emft->priv->treeview);
GList *list = NULL;
gtk_tree_selection_selected_foreach (selection, get_selected_uris_path_iterate, &list);
return list;
}
void
em_folder_tree_set_selected_list (EMFolderTree *emft, GList *list)
{
struct _EMFolderTreePrivate *priv = emft->priv;
/* clear list of lost uris */
if (priv->lost_folders) {
g_list_foreach (priv->lost_folders, (GFunc)g_free, NULL);
g_list_free (priv->lost_folders);
priv->lost_folders = NULL;
}
while (list) {
em_folder_tree_set_selected (emft, list->data);
list = g_list_next (list);
}
}
#if 0
static void
dump_fi (CamelFolderInfo *fi, int depth)
{
int 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
struct _EMFolderTreeGetFolderInfo {
struct _mail_msg msg;
/* input data */
GtkTreeRowReference *root;
EMFolderTree *emft;
CamelStore *store;
guint32 flags;
char *top;
/* output data */
CamelFolderInfo *fi;
/* uri to select if any after the op is done */
char *select_uri;
};
static void
emft_get_folder_info__get (struct _mail_msg *mm)
{
struct _EMFolderTreeGetFolderInfo *m = (struct _EMFolderTreeGetFolderInfo *) mm;
guint32 flags = m->flags;
if (camel_store_supports_subscriptions (m->store))
flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
m->fi = camel_store_get_folder_info (m->store, m->top, flags, &mm->ex);
}
static void
emft_get_folder_info__got (struct _mail_msg *mm)
{
struct _EMFolderTreeGetFolderInfo *m = (struct _EMFolderTreeGetFolderInfo *) mm;
struct _EMFolderTreePrivate *priv = m->emft->priv;
struct _EMFolderTreeModelStoreInfo *si;
GtkTreeIter root, iter;
CamelFolderInfo *fi;
GtkTreeStore *model;
GtkTreePath *path;
gboolean load;
/* check that we haven't been destroyed */
if (priv->treeview == NULL)
return;
/* check that our parent folder hasn't been deleted/unsubscribed */
if (!gtk_tree_row_reference_valid (m->root))
return;
if (!(si = g_hash_table_lookup (priv->model->store_hash, m->store))) {
/* store has been removed in the interim - do nothing */
return;
}
model = (GtkTreeStore *) gtk_tree_view_get_model (priv->treeview);
path = gtk_tree_row_reference_get_path (m->root);
gtk_tree_model_get_iter ((GtkTreeModel *) model, &root, path);
gtk_tree_path_free (path);
/* make sure we still need to load the tree subfolders... */
gtk_tree_model_get ((GtkTreeModel *) model, &root,
COL_BOOL_LOAD_SUBDIRS, &load,
-1);
if (!load) {
if (priv->do_multiselect && m->select_uri)
priv->lost_folders = g_list_append (priv->lost_folders, g_strdup (m->select_uri));
return;
}
/* get the first child (which will be a dummy node) */
gtk_tree_model_iter_children ((GtkTreeModel *) model, &iter, &root);
/* 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 */
gtk_tree_store_remove (model, &iter);
} else {
do {
em_folder_tree_model_set_folder_info (priv->model, &iter, si, fi);
if ((fi = fi->next) != NULL)
gtk_tree_store_append (model, &iter, &root);
} while (fi != NULL);
}
gtk_tree_store_set (model, &root, COL_BOOL_LOAD_SUBDIRS, FALSE, -1);
if (m->select_uri)
em_folder_tree_set_selected (m->emft, m->select_uri);
emft_queue_save_state (m->emft);
}
static void
emft_get_folder_info__free (struct _mail_msg *mm)
{
struct _EMFolderTreeGetFolderInfo *m = (struct _EMFolderTreeGetFolderInfo *) mm;
camel_store_free_folder_info (m->store, m->fi);
gtk_tree_row_reference_free (m->root);
g_object_unref(m->emft);
camel_object_unref (m->store);
g_free (m->select_uri);
g_free (m->top);
}
static struct _mail_msg_op get_folder_info_op = {
NULL,
emft_get_folder_info__get,
emft_get_folder_info__got,
emft_get_folder_info__free,
};
static void
emft_update_model_expanded_state (struct _EMFolderTreePrivate *priv, GtkTreeIter *iter, gboolean expanded)
{
struct _EMFolderTreeModelStoreInfo *si;
CamelStore *store;
EAccount *account;
char *path, *key;
gtk_tree_model_get ((GtkTreeModel *) priv->model, iter,
COL_STRING_FOLDER_PATH, &path,
COL_POINTER_CAMEL_STORE, &store,
-1);
si = g_hash_table_lookup (priv->model->store_hash, store);
if ((account = mail_config_get_account_by_name (si->display_name))) {
key = g_strdup_printf ("%s%s", account->uid, path);
} else if (CAMEL_IS_VEE_STORE (store)) {
/* vfolder store */
key = g_strdup_printf ("vfolder%s", path);
} else {
/* local store */
key = g_strdup_printf ("local%s", path);
}
em_folder_tree_model_set_expanded (priv->model, key, expanded);
g_free (key);
}
static void
emft_tree_row_expanded (GtkTreeView *treeview, GtkTreeIter *root, GtkTreePath *tree_path, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
struct _EMFolderTreeGetFolderInfo *m;
GtkTreeModel *model;
CamelStore *store;
const char *top;
gboolean load;
char *path;
model = gtk_tree_view_get_model (treeview);
gtk_tree_model_get (model, root,
COL_STRING_FOLDER_PATH, &path,
COL_POINTER_CAMEL_STORE, &store,
COL_BOOL_LOAD_SUBDIRS, &load,
-1);
emft_update_model_expanded_state (priv, root, TRUE);
if (!load) {
emft_queue_save_state (emft);
return;
}
if (!path || !strcmp (path, "/"))
top = NULL;
else
top = path + 1;
m = mail_msg_new (&get_folder_info_op, NULL, sizeof (struct _EMFolderTreeGetFolderInfo));
m->root = gtk_tree_row_reference_new (model, tree_path);
camel_object_ref (store);
m->store = store;
m->emft = emft;
g_object_ref(emft);
m->top = g_strdup (top);
m->flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
m->select_uri = NULL;
e_thread_put (mail_thread_new, (EMsg *) m);
}
static void
emft_tree_row_collapsed (GtkTreeView *treeview, GtkTreeIter *root, GtkTreePath *tree_path, EMFolderTree *emft)
{
emft_update_model_expanded_state (emft->priv, root, FALSE);
emft_queue_save_state (emft);
}
static void
emft_tree_row_activated (GtkTreeView *treeview, GtkTreePath *tree_path, GtkTreeViewColumn *column, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeModel *model = (GtkTreeModel *) priv->model;
GtkTreeIter iter;
char *path, *uri;
if (!emft_select_func(NULL, model, tree_path, FALSE, emft))
return;
if (!gtk_tree_model_get_iter (model, &iter, tree_path))
return;
gtk_tree_model_get (model, &iter, COL_STRING_FOLDER_PATH, &path,
COL_STRING_URI, &uri, -1);
g_free (priv->selected_uri);
priv->selected_uri = g_strdup (uri);
g_free (priv->selected_path);
priv->selected_path = g_strdup (path);
g_signal_emit (emft, signals[FOLDER_SELECTED], 0, path, uri);
g_signal_emit (emft, signals[FOLDER_ACTIVATED], 0, path, uri);
}
#if 0
static void
emft_popup_view (GtkWidget *item, EMFolderTree *emft)
{
}
static void
emft_popup_open_new (GtkWidget *item, EMFolderTree *emft)
{
}
#endif
struct _EMCopyFolders {
struct _mail_msg msg;
/* input data */
CamelStore *fromstore;
CamelStore *tostore;
char *frombase;
char *tobase;
int delete;
};
static void
emft_copy_folders__copy (struct _mail_msg *mm)
{
struct _EMCopyFolders *m = (struct _EMCopyFolders *) mm;
guint32 flags = CAMEL_STORE_FOLDER_INFO_FAST | CAMEL_STORE_FOLDER_INFO_RECURSIVE;
GList *pending = NULL, *deleting = NULL, *l;
GString *fromname, *toname;
CamelFolderInfo *fi;
const char *tmp;
int fromlen;
if (camel_store_supports_subscriptions (m->fromstore))
flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
if (!(fi = camel_store_get_folder_info (m->fromstore, m->frombase, flags, &mm->ex)))
return;
pending = g_list_append (pending, fi);
toname = g_string_new ("");
fromname = g_string_new ("");
tmp = strrchr (m->frombase, '/');
if (tmp == NULL)
fromlen = 0;
else
fromlen = tmp - m->frombase + 1;
d(printf ("top name is '%s'\n", fi->full_name));
while (pending) {
CamelFolderInfo *info = pending->data;
pending = g_list_remove_link (pending, pending);
while (info) {
CamelFolder *fromfolder, *tofolder;
GPtrArray *uids;
int deleted = 0;
if (info->child)
pending = g_list_append (pending, info->child);
if (m->tobase[0])
g_string_printf (toname, "%s/%s", m->tobase, info->full_name + fromlen);
else
g_string_printf (toname, "%s", info->full_name + fromlen);
d(printf ("Copying from '%s' to '%s'\n", info->full_name, toname->str));
/* This makes sure we create the same tree, e.g. from a nonselectable source */
/* Not sure if this is really the 'right thing', e.g. for spool stores, but it makes the ui work */
if ((info->flags & CAMEL_FOLDER_NOSELECT) == 0) {
d(printf ("this folder is selectable\n"));
if (m->tostore == m->fromstore && m->delete) {
camel_store_rename_folder (m->fromstore, info->full_name, toname->str, &mm->ex);
if (camel_exception_is_set (&mm->ex))
goto exception;
/* this folder no longer exists, unsubscribe it */
if (camel_store_supports_subscriptions (m->fromstore))
camel_store_unsubscribe_folder (m->fromstore, info->full_name, NULL);
deleted = 1;
} else {
if (!(fromfolder = camel_store_get_folder (m->fromstore, info->full_name, 0, &mm->ex)))
goto exception;
if (!(tofolder = camel_store_get_folder (m->tostore, toname->str, CAMEL_STORE_FOLDER_CREATE, &mm->ex))) {
camel_object_unref (fromfolder);
goto exception;
}
uids = camel_folder_get_uids (fromfolder);
camel_folder_transfer_messages_to (fromfolder, uids, tofolder, NULL, m->delete, &mm->ex);
camel_folder_free_uids (fromfolder, uids);
if (m->delete)
camel_folder_sync(fromfolder, TRUE, NULL);
camel_object_unref (fromfolder);
camel_object_unref (tofolder);
}
}
if (camel_exception_is_set (&mm->ex))
goto exception;
else if (m->delete && !deleted)
deleting = g_list_prepend (deleting, info);
/* subscribe to the new folder if appropriate */
if (camel_store_supports_subscriptions (m->tostore)
&& !camel_store_folder_subscribed (m->tostore, toname->str))
camel_store_subscribe_folder (m->tostore, toname->str, NULL);
info = info->next;
}
}
/* delete the folders in reverse order from how we copyied them, if we are deleting any */
l = deleting;
while (l) {
CamelFolderInfo *info = l->data;
d(printf ("deleting folder '%s'\n", info->full_name));
/* FIXME: we need to do something with the exception
since otherwise the users sees a failed operation
with no error message or even any warnings */
if (camel_store_supports_subscriptions (m->fromstore))
camel_store_unsubscribe_folder (m->fromstore, info->full_name, NULL);
camel_store_delete_folder (m->fromstore, info->full_name, NULL);
l = l->next;
}
exception:
camel_store_free_folder_info (m->fromstore, fi);
g_list_free (deleting);
g_string_free (toname, TRUE);
g_string_free (fromname, TRUE);
}
static void
emft_copy_folders__free (struct _mail_msg *mm)
{
struct _EMCopyFolders *m = (struct _EMCopyFolders *) mm;
camel_object_unref (m->fromstore);
camel_object_unref (m->tostore);
g_free (m->frombase);
g_free (m->tobase);
}
static struct _mail_msg_op copy_folders_op = {
NULL,
emft_copy_folders__copy,
NULL,
emft_copy_folders__free,
};
static void
emft_copy_folders (CamelStore *tostore, const char *tobase, CamelStore *fromstore, const char *frombase, int delete)
{
struct _EMCopyFolders *m;
m = mail_msg_new (&copy_folders_op, NULL, sizeof (struct _EMCopyFolders));
camel_object_ref (fromstore);
m->fromstore = fromstore;
camel_object_ref (tostore);
m->tostore = tostore;
m->frombase = g_strdup (frombase);
m->tobase = g_strdup (tobase);
m->delete = delete;
e_thread_put (mail_thread_new, (EMsg *) m);
}
struct _copy_folder_data {
EMFolderTree *emft;
gboolean delete;
};
static void
emft_popup_copy_folder_selected (const char *uri, void *data)
{
struct _copy_folder_data *cfd = data;
struct _EMFolderTreePrivate *priv;
CamelStore *fromstore, *tostore;
char *tobase = NULL, *frombase;
GtkWindow *parent;
CamelException ex;
GtkWidget *dialog;
CamelURL *url;
if (uri == NULL) {
g_free (cfd);
return;
}
priv = cfd->emft->priv;
d(printf ("%sing folder '%s' to '%s'\n", cfd->delete ? "move" : "copy", priv->selected_path, uri));
camel_exception_init (&ex);
if (!(fromstore = camel_session_get_store (session, priv->selected_uri, &ex)))
goto exception;
frombase = priv->selected_path + 1;
if (fromstore == mail_component_peek_local_store (NULL) && is_special_local_folder (frombase)) {
if (cfd->delete)
camel_exception_setv (&ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot move folder `%s': illegal operation"), frombase);
camel_object_unref (fromstore);
goto exception;
}
if (!(tostore = camel_session_get_store (session, uri, &ex))) {
camel_object_unref (fromstore);
goto exception;
}
url = camel_url_new (uri, NULL);
if (((CamelService *)tostore)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
tobase = url->fragment;
else if (url->path && url->path[0])
tobase = url->path+1;
if (tobase == NULL)
tobase = "";
emft_copy_folders (tostore, tobase, fromstore, frombase, cfd->delete);
camel_url_free (url);
g_free (cfd);
return;
exception:
parent = (GtkWindow *) gtk_widget_get_ancestor ((GtkWidget *) cfd->emft, GTK_TYPE_WINDOW);
dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("%s"), ex.desc);
g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
camel_exception_clear (&ex);
gtk_widget_show (dialog);
g_free (cfd);
}
static void
emft_popup_copy (GtkWidget *item, EMFolderTree *emft)
{
struct _copy_folder_data *cfd;
cfd = g_malloc (sizeof (*cfd));
cfd->emft = emft;
cfd->delete = FALSE;
em_select_folder (NULL, _("Select folder"),
NULL, emft_popup_copy_folder_selected, cfd);
}
static void
emft_popup_move (GtkWidget *item, EMFolderTree *emft)
{
struct _copy_folder_data *cfd;
cfd = g_malloc (sizeof (*cfd));
cfd->emft = emft;
cfd->delete = TRUE;
em_select_folder (NULL, _("Select folder"),
NULL, emft_popup_copy_folder_selected, cfd);
}
struct _EMCreateFolder {
struct _mail_msg msg;
/* input data */
CamelStore *store;
char *full_name;
char *parent;
char *name;
/* output data */
CamelFolderInfo *fi;
/* callback data */
void (* done) (CamelFolderInfo *fi, void *user_data);
void *user_data;
};
static char *
emft_create_folder__desc (struct _mail_msg *mm, int done)
{
struct _EMCreateFolder *m = (struct _EMCreateFolder *) mm;
return g_strdup_printf (_("Creating folder `%s'"), m->full_name);
}
static void
emft_create_folder__create (struct _mail_msg *mm)
{
struct _EMCreateFolder *m = (struct _EMCreateFolder *) mm;
d(printf ("creating folder parent='%s' name='%s' full_name='%s'\n", m->parent, m->name, m->full_name));
if ((m->fi = camel_store_create_folder (m->store, m->parent, m->name, &mm->ex))) {
if (camel_store_supports_subscriptions (m->store))
camel_store_subscribe_folder (m->store, m->full_name, &mm->ex);
}
}
static void
emft_create_folder__created (struct _mail_msg *mm)
{
struct _EMCreateFolder *m = (struct _EMCreateFolder *) mm;
if (m->done)
m->done (m->fi, m->user_data);
}
static void
emft_create_folder__free (struct _mail_msg *mm)
{
struct _EMCreateFolder *m = (struct _EMCreateFolder *) mm;
camel_store_free_folder_info (m->store, m->fi);
camel_object_unref (m->store);
g_free (m->full_name);
g_free (m->parent);
g_free (m->name);
}
static struct _mail_msg_op create_folder_op = {
emft_create_folder__desc,
emft_create_folder__create,
emft_create_folder__created,
emft_create_folder__free,
};
static int
emft_create_folder (CamelStore *store, const char *path, void (* done) (CamelFolderInfo *fi, void *user_data), void *user_data)
{
const char *parent, *full_name;
char *name, *namebuf = NULL;
struct _EMCreateFolder *m;
int id;
full_name = path[0] == '/' ? path + 1 : path;
namebuf = g_strdup (full_name);
if (!(name = strrchr (namebuf, '/'))) {
name = namebuf;
parent = "";
} else {
*name++ = '\0';
parent = namebuf;
}
m = mail_msg_new (&create_folder_op, NULL, sizeof (struct _EMCreateFolder));
camel_object_ref (store);
m->store = store;
m->full_name = g_strdup (full_name);
m->parent = g_strdup (parent);
m->name = g_strdup (name);
m->user_data = user_data;
m->done = done;
g_free (namebuf);
id = m->msg.seq;
e_thread_put (mail_thread_new, (EMsg *) m);
return id;
}
static void
created_cb (CamelFolderInfo *fi, void *user_data)
{
gboolean *created = user_data;
*created = fi ? TRUE : FALSE;
}
gboolean
em_folder_tree_create_folder (EMFolderTree *emft, const char *path, const char *uri)
{
struct _EMFolderTreePrivate *priv = emft->priv;
struct _EMFolderTreeModelStoreInfo *si;
gboolean created = FALSE;
GtkWindow *window;
GtkWidget *dialog;
CamelStore *store;
CamelException ex;
d(printf ("Creating folder: %s (%s)\n", path, uri));
camel_exception_init (&ex);
if (!(store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex)))
goto exception;
if (!(si = g_hash_table_lookup (priv->model->store_hash, store))) {
abort();
camel_object_unref (store);
goto exception;
}
camel_object_unref (store);
mail_msg_wait (emft_create_folder (si->store, path, created_cb, &created));
return created;
exception:
window = (GtkWindow *) gtk_widget_get_ancestor ((GtkWidget *) emft, GTK_TYPE_WINDOW);
dialog = gtk_message_dialog_new (window, GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("%s"), ex.desc);
g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
camel_exception_clear (&ex);
gtk_widget_show (dialog);
return FALSE;
}
static void
new_folder_created_cb (CamelFolderInfo *fi, void *user_data)
{
EMFolderSelector *emfs = user_data;
if (fi)
gtk_widget_destroy ((GtkWidget *) emfs);
g_object_unref (emfs);
}
static void
emft_popup_new_folder_response (EMFolderSelector *emfs, int response, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
struct _EMFolderTreeModelStoreInfo *si;
const char *uri, *path;
CamelException ex;
CamelStore *store;
if (response != GTK_RESPONSE_OK) {
gtk_widget_destroy ((GtkWidget *) emfs);
return;
}
uri = em_folder_selector_get_selected_uri (emfs);
path = em_folder_selector_get_selected_path (emfs);
d(printf ("Creating new folder: %s (%s)\n", path, uri));
camel_exception_init (&ex);
if (!(store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex))) {
camel_exception_clear (&ex);
return;
}
if (!(si = g_hash_table_lookup (priv->model->store_hash, store))) {
g_assert_not_reached ();
camel_object_unref (store);
return;
}
/* HACK: we need to create vfolders using the vfolder editor */
if (CAMEL_IS_VEE_STORE(store)) {
VfolderRule *rule;
rule = vfolder_rule_new();
filter_rule_set_name((FilterRule *)rule, path);
vfolder_gui_add_rule(rule);
} else {
g_object_ref (emfs);
emft_create_folder (si->store, path, new_folder_created_cb, emfs);
}
camel_object_unref (store);
}
static void
emft_popup_new_folder (GtkWidget *item, EMFolderTree *emft)
{
EMFolderTree *folder_tree;
GtkWidget *dialog;
folder_tree = (EMFolderTree *) em_folder_tree_new_with_model (emft->priv->model);
dialog = em_folder_selector_create_new (folder_tree, 0, _("Create folder"), _("Specify where to create the folder:"));
em_folder_selector_set_selected ((EMFolderSelector *) dialog, emft->priv->selected_uri);
g_signal_connect (dialog, "response", G_CALLBACK (emft_popup_new_folder_response), emft);
gtk_widget_show (dialog);
}
static void
emft_popup_delete_rec (CamelStore *store, CamelFolderInfo *fi, CamelException *ex)
{
while (fi) {
CamelFolder *folder;
if (fi->child) {
emft_popup_delete_rec (store, fi->child, ex);
if (camel_exception_is_set (ex))
return;
}
d(printf ("deleting folder '%s'\n", fi->full_name));
/* shouldn't camel do this itself? */
if (camel_store_supports_subscriptions (store))
camel_store_unsubscribe_folder (store, fi->full_name, NULL);
if (!(folder = camel_store_get_folder (store, fi->full_name, 0, ex)))
return;
if (!CAMEL_IS_VEE_FOLDER (folder)) {
GPtrArray *uids = camel_folder_get_uids (folder);
int i;
camel_folder_freeze (folder);
for (i = 0; i < uids->len; i++)
camel_folder_delete_message (folder, uids->pdata[i]);
camel_folder_free_uids (folder, uids);
camel_folder_sync (folder, TRUE, NULL);
camel_folder_thaw (folder);
}
camel_store_delete_folder (store, fi->full_name, ex);
if (camel_exception_is_set (ex))
return;
fi = fi->next;
}
}
static void
emft_popup_delete_folders (CamelStore *store, const char *path, CamelException *ex)
{
guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_FAST;
const char *full_name;
CamelFolderInfo *fi;
if (camel_store_supports_subscriptions (store))
flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
full_name = path[0] == '/' ? path + 1 : path;
fi = camel_store_get_folder_info (store, full_name, flags, ex);
if (camel_exception_is_set (ex))
return;
emft_popup_delete_rec (store, fi, ex);
camel_store_free_folder_info (store, fi);
}
static void
selfunc (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
{
struct _emft_selection_data *dat = (struct _emft_selection_data *) data;
dat->model = model;
if (!dat->set)
*(dat->iter) = *iter;
dat->set = TRUE;
}
static gboolean
emft_selection_get_selected (GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter)
{
struct _emft_selection_data dat = { NULL, iter, FALSE };
if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_MULTIPLE) {
gtk_tree_selection_selected_foreach (selection, selfunc, &dat);
if (model)
*model = dat.model;
return dat.set;
} else {
return gtk_tree_selection_get_selected (selection, model, iter);
}
}
static void
emft_popup_delete_response (GtkWidget *dialog, guint response, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeSelection *selection;
GtkTreeModel *model;
CamelStore *store;
CamelException ex;
GtkTreeIter iter;
char *path;
gtk_widget_destroy (dialog);
if (response != GTK_RESPONSE_OK)
return;
selection = gtk_tree_view_get_selection (priv->treeview);
if (!emft_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, COL_STRING_FOLDER_PATH, &path,
COL_POINTER_CAMEL_STORE, &store, -1);
camel_exception_init (&ex);
emft_popup_delete_folders (store, path, &ex);
if (camel_exception_is_set (&ex)) {
e_notice (NULL, GTK_MESSAGE_ERROR, _("Could not delete folder: %s"), ex.desc);
camel_exception_clear (&ex);
}
}
static void
emft_popup_delete_folder (GtkWidget *item, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeSelection *selection;
CamelStore *local, *store;
GtkTreeModel *model;
GtkTreeIter iter;
GtkWidget *dialog;
const char *full_name;
char *title, *path;
selection = gtk_tree_view_get_selection (priv->treeview);
if (!emft_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, COL_POINTER_CAMEL_STORE, &store, COL_STRING_FOLDER_PATH, &path, -1);
local = mail_component_peek_local_store (NULL);
full_name = path[0] == '/' ? path + 1 : path;
if (store == local && is_special_local_folder (full_name)) {
e_notice (NULL, GTK_MESSAGE_ERROR, _("Cannot delete local %s folder."), full_name);
return;
}
dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
_("Really delete folder \"%s\" and all of its subfolders?"),
full_name);
gtk_dialog_add_button ((GtkDialog *) dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
gtk_dialog_add_button ((GtkDialog *) dialog, GTK_STOCK_DELETE, GTK_RESPONSE_OK);
gtk_dialog_set_default_response ((GtkDialog *) dialog, GTK_RESPONSE_OK);
gtk_container_set_border_width ((GtkContainer *) dialog, 6);
gtk_box_set_spacing ((GtkBox *) ((GtkDialog *) dialog)->vbox, 6);
title = g_strdup_printf (_("Delete \"%s\""), full_name);
gtk_window_set_title ((GtkWindow *) dialog, title);
g_free (title);
g_signal_connect (dialog, "response", G_CALLBACK (emft_popup_delete_response), emft);
gtk_widget_show (dialog);
}
static void
emft_popup_rename_folder (GtkWidget *item, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
char *prompt, *folder_path, *name, *new_name, *uri;
GtkTreeSelection *selection;
const char *full_name, *p;
CamelStore *local, *store;
gboolean done = FALSE;
GtkTreeModel *model;
GtkTreeIter iter;
size_t base_len;
local = mail_component_peek_local_store (NULL);
selection = gtk_tree_view_get_selection (priv->treeview);
if (!emft_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, COL_STRING_FOLDER_PATH, &folder_path,
COL_STRING_DISPLAY_NAME, &name,
COL_POINTER_CAMEL_STORE, &store,
COL_STRING_URI, &uri, -1);
full_name = folder_path[0] == '/' ? folder_path + 1 : folder_path;
/* don't allow user to rename one of the special local folders */
if (store == local && is_special_local_folder (full_name)) {
e_notice (NULL, GTK_MESSAGE_ERROR, _("Cannot rename local %s folder."), full_name);
return;
}
if ((p = strrchr (full_name, '/')))
base_len = (size_t) (p - full_name);
else
base_len = 0;
prompt = g_strdup_printf (_("Rename the \"%s\" folder to:"), name);
while (!done) {
new_name = e_request_string (NULL, _("Rename Folder"), prompt, name);
if (new_name == NULL || !strcmp (name, new_name)) {
/* old name == new name */
done = TRUE;
} else {
CamelFolderInfo *fi;
CamelException ex;
char *path, *p;
if (base_len > 0) {
path = g_malloc (base_len + strlen (new_name) + 2);
memcpy (path, full_name, base_len);
p = path + base_len;
*p++ = '/';
strcpy (p, new_name);
} else {
path = g_strdup (new_name);
}
camel_exception_init (&ex);
if ((fi = camel_store_get_folder_info (store, path, CAMEL_STORE_FOLDER_INFO_FAST, &ex)) != NULL) {
camel_store_free_folder_info (store, fi);
e_notice (NULL, GTK_MESSAGE_ERROR,
_("A folder named \"%s\" already exists. Please use a different name."),
new_name);
} else {
const char *oldpath, *newpath;
oldpath = full_name;
newpath = path;
d(printf ("renaming %s to %s\n", oldpath, newpath));
camel_exception_clear (&ex);
camel_store_rename_folder (store, oldpath, newpath, &ex);
if (camel_exception_is_set (&ex)) {
e_notice (NULL, GTK_MESSAGE_ERROR, _("Could not rename folder: %s"), ex.desc);
camel_exception_clear (&ex);
}
done = TRUE;
}
g_free (path);
}
g_free (new_name);
}
}
static void
emft_popup_properties (GtkWidget *item, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeSelection *selection;
GtkTreeModel *model;
GtkTreeIter iter;
char *uri;
selection = gtk_tree_view_get_selection (priv->treeview);
if (!emft_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, COL_STRING_URI, &uri, -1);
em_folder_properties_show(NULL, NULL, uri);
}
static EMPopupItem emft_popup_menu[] = {
#if 0
{ EM_POPUP_ITEM, "00.emc.00", N_("_View"), G_CALLBACK (emft_popup_view), NULL, NULL, EM_POPUP_FOLDER_SELECT },
{ EM_POPUP_ITEM, "00.emc.01", N_("Open in _New Window"), G_CALLBACK (emft_popup_open_new), NULL, NULL, EM_POPUP_FOLDER_SELECT },
{ EM_POPUP_BAR, "10.emc" },
#endif
{ EM_POPUP_ITEM, "10.emc.00", N_("_Copy"), G_CALLBACK (emft_popup_copy), NULL, "folder-copy-16.png", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT },
{ EM_POPUP_ITEM, "10.emc.01", N_("_Move"), G_CALLBACK (emft_popup_move), NULL, "folder-move-16.png", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE },
{ EM_POPUP_BAR, "20.emc" },
/* FIXME: need to disable for nochildren folders */
{ EM_POPUP_ITEM, "20.emc.00", N_("_New Folder..."), G_CALLBACK (emft_popup_new_folder), NULL, "folder-mini.png", EM_POPUP_FOLDER_INFERIORS },
/* FIXME: need to disable for undeletable folders */
{ EM_POPUP_ITEM, "20.emc.01", N_("_Delete"), G_CALLBACK (emft_popup_delete_folder), NULL, "evolution-trash-mini.png", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE },
{ EM_POPUP_ITEM, "20.emc.01", N_("_Rename"), G_CALLBACK (emft_popup_rename_folder), NULL, NULL, EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE },
{ EM_POPUP_BAR, "80.emc" },
{ EM_POPUP_ITEM, "80.emc.00", N_("_Properties..."), G_CALLBACK (emft_popup_properties), NULL, "configure_16_folder.xpm", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT },
};
static gboolean
emft_tree_button_press (GtkTreeView *treeview, GdkEventButton *event, EMFolderTree *emft)
{
GtkTreeSelection *selection;
CamelStore *local, *store;
const char *folder_name;
EMPopupTarget *target;
GtkTreePath *tree_path;
GtkTreeModel *model;
GtkTreeIter iter;
GSList *menus = NULL;
guint32 info_flags = 0;
guint32 flags = 0;
gboolean isstore;
char *uri, *path;
GtkMenu *menu;
EMPopup *emp;
int i;
if (event->button != 3 && !(event->button == 1 && event->type == GDK_2BUTTON_PRESS))
return FALSE;
if (!gtk_tree_view_get_path_at_pos (treeview, (int) event->x, (int) event->y, &tree_path, NULL, NULL, NULL))
return FALSE;
/* select/focus the row that was right-clicked or double-clicked */
gtk_tree_view_set_cursor (treeview, tree_path, NULL, FALSE);
if (event->button == 1 && event->type == GDK_2BUTTON_PRESS) {
emft_tree_row_activated (treeview, tree_path, NULL, emft);
gtk_tree_path_free (tree_path);
return TRUE;
}
gtk_tree_path_free (tree_path);
/* FIXME: we really need the folderinfo to build a proper menu */
selection = gtk_tree_view_get_selection (treeview);
if (!emft_selection_get_selected (selection, &model, &iter))
return FALSE;
gtk_tree_model_get (model, &iter, COL_POINTER_CAMEL_STORE, &store,
COL_STRING_URI, &uri, COL_STRING_FOLDER_PATH, &path,
COL_BOOL_IS_STORE, &isstore, -1);
if (path == NULL)
return FALSE;
if (isstore)
flags |= EM_POPUP_FOLDER_STORE;
else
flags |= EM_POPUP_FOLDER_FOLDER;
local = mail_component_peek_local_store (NULL);
folder_name = path[0] == '/' ? path + 1 : path;
/* don't allow deletion of special local folders */
if (!(store == local && is_special_local_folder (folder_name)))
flags |= EM_POPUP_FOLDER_DELETE;
/* hack for vTrash/vJunk */
if (!strcmp (folder_name, CAMEL_VTRASH_NAME) || !strcmp (folder_name, CAMEL_VJUNK_NAME))
info_flags |= CAMEL_FOLDER_VIRTUAL | CAMEL_FOLDER_NOINFERIORS;
/* handle right-click by opening a context menu */
emp = em_popup_new ("com.ximian.mail.storageset.popup.select");
/* FIXME: pass valid fi->flags here */
target = em_popup_target_new_folder (uri, info_flags, flags);
for (i = 0; i < sizeof (emft_popup_menu) / sizeof (emft_popup_menu[0]); i++) {
EMPopupItem *item = &emft_popup_menu[i];
item->activate_data = emft;
menus = g_slist_prepend (menus, item);
}
em_popup_add_items (emp, menus, (GDestroyNotify) g_slist_free);
menu = em_popup_create_menu_once (emp, target, 0, target->mask);
if (event == NULL || event->type == GDK_KEY_PRESS) {
/* FIXME: menu pos function */
gtk_menu_popup (menu, NULL, NULL, NULL, NULL, 0, event->time);
} else {
gtk_menu_popup (menu, NULL, NULL, NULL, NULL, event->button, event->time);
}
return TRUE;
}
static void
emft_tree_selection_changed (GtkTreeSelection *selection, EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
GtkTreeModel *model;
GtkTreeIter iter;
char *path, *uri;
if (!emft_selection_get_selected (selection, &model, &iter))
return;
gtk_tree_model_get (model, &iter, COL_STRING_FOLDER_PATH, &path,
COL_STRING_URI, &uri, -1);
g_free (priv->selected_uri);
priv->selected_uri = g_strdup (uri);
g_free (priv->selected_path);
priv->selected_path = g_strdup (path);
g_signal_emit (emft, signals[FOLDER_SELECTED], 0, path, uri);
}
void
em_folder_tree_set_selected (EMFolderTree *emft, const char *uri)
{
struct _EMFolderTreeModelStoreInfo *si;
struct _EMFolderTreeGetFolderInfo *m;
struct _EMFolderTreePrivate *priv;
GtkTreeRowReference *row = NULL;
GtkTreeSelection *selection;
GtkTreePath *tree_path;
CamelStore *store;
CamelException ex;
const char *top;
char *path, *p;
CamelURL *url;
g_return_if_fail (EM_IS_FOLDER_TREE (emft));
priv = emft->priv;
camel_exception_init (&ex);
if (!(store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex))) {
camel_exception_clear (&ex);
return;
}
if (!(si = g_hash_table_lookup (priv->model->store_hash, store))) {
camel_object_unref (store);
return;
}
if (!(url = camel_url_new (uri, NULL))) {
camel_object_unref (store);
return;
}
if (((CamelService *) store)->provider->url_flags & CAMEL_URL_FRAGMENT_IS_PATH)
path = g_strdup_printf ("/%s", url->fragment ? url->fragment : "");
else
path = g_strdup (url->path ? url->path : "/");
top = path[0] == '/' ? path + 1 : path;
camel_url_free (url);
if (!strcmp (path, "/"))
row = si->row;
if (row || (row = g_hash_table_lookup (si->path_hash, path))) {
/* the folder-info node has already been loaded */
tree_path = gtk_tree_row_reference_get_path (row);
gtk_tree_view_expand_to_path (priv->treeview, tree_path);
selection = gtk_tree_view_get_selection (priv->treeview);
gtk_tree_selection_select_path (selection, tree_path);
gtk_tree_path_free (tree_path);
camel_object_unref (store);
g_free (path);
return;
}
/* look for the first of our parent folders that has already been loaded */
p = path + strlen (path);
while (p > path) {
if (*p == '/') {
*p = '\0';
if ((row = g_hash_table_lookup (si->path_hash, path)))
break;
}
p--;
}
if (row == NULL) {
/* none of the folders of the desired store have been loaded yet */
row = si->row;
top = NULL;
}
/* FIXME: this gets all the subfolders of our first loaded
* parent folder - ideally we'd only get what we needed, but
* it's probably not worth the effort */
m = mail_msg_new (&get_folder_info_op, NULL, sizeof (struct _EMFolderTreeGetFolderInfo));
m->root = gtk_tree_row_reference_copy (row);
m->store = store;
m->emft = emft;
g_object_ref(emft);
m->top = top ? g_strdup (top) : NULL;
m->flags = CAMEL_STORE_FOLDER_INFO_FAST | CAMEL_STORE_FOLDER_INFO_RECURSIVE;
m->select_uri = g_strdup (uri);
g_free (path);
e_thread_put (mail_thread_new, (EMsg *) m);
}
const char *
em_folder_tree_get_selected_uri (EMFolderTree *emft)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (emft), NULL);
return emft->priv->selected_uri;
}
const char *
em_folder_tree_get_selected_path (EMFolderTree *emft)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (emft), NULL);
return emft->priv->selected_path;
}
EMFolderTreeModel *
em_folder_tree_get_model (EMFolderTree *emft)
{
g_return_val_if_fail (EM_IS_FOLDER_TREE (emft), NULL);
return emft->priv->model;
}
static gboolean
emft_save_state (EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
em_folder_tree_model_save_expanded (priv->model);
priv->save_state_id = 0;
return FALSE;
}
static void
emft_queue_save_state (EMFolderTree *emft)
{
struct _EMFolderTreePrivate *priv = emft->priv;
if (priv->save_state_id != 0)
return;
priv->save_state_id = g_timeout_add (1000, (GSourceFunc) emft_save_state, emft);
}