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
2838 lines
77 KiB
C
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 (©_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);
|
|
}
|