2004-06-29 Not Zed <NotZed@Ximian.com> * em-vfolder-rule.c (source_add): setup exclusion for vfolder sources, noselect folders. Bug #60794. svn path=/trunk/; revision=26544
2893 lines
79 KiB
C
2893 lines
79 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 <camel/camel-stream-fs.h>
|
|
|
|
#include "e-util/e-mktemp.h"
|
|
#include "e-util/e-request.h"
|
|
#include "e-util/e-icon-factory.h"
|
|
|
|
#include "widgets/misc/e-error.h"
|
|
|
|
#include "em-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)
|
|
|
|
struct _EMFolderTreePrivate {
|
|
GtkTreeView *treeview;
|
|
EMFolderTreeModel *model;
|
|
|
|
char *select_uri; /* uri to load when the proper store/etc have been populated */
|
|
|
|
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 autoexpand_id;
|
|
GtkTreeRowReference *autoexpand_row;
|
|
|
|
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_update_model_expanded_state (struct _EMFolderTreePrivate *priv, GtkTreeIter *iter, gboolean expanded);
|
|
|
|
static void emft_tree_row_activated (GtkTreeView *treeview, GtkTreePath *path, GtkTreeViewColumn *column, EMFolderTree *emft);
|
|
static gboolean emft_tree_test_collapse_row (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_UINT,
|
|
G_TYPE_NONE, 3,
|
|
G_TYPE_STRING,
|
|
G_TYPE_STRING,
|
|
G_TYPE_UINT);
|
|
|
|
signals[FOLDER_ACTIVATED] =
|
|
g_signal_new ("folder-activated",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_FIRST,
|
|
G_STRUCT_OFFSET (EMFolderTreeClass, folder_activated),
|
|
NULL, NULL,
|
|
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 *full_name;
|
|
|
|
if (!initialised) {
|
|
folder_icons[FOLDER_ICON_NORMAL] = e_icon_factory_get_icon ("stock_folder", E_ICON_SIZE_MENU);
|
|
folder_icons[FOLDER_ICON_INBOX] = e_icon_factory_get_icon ("stock_inbox", E_ICON_SIZE_MENU);
|
|
folder_icons[FOLDER_ICON_OUTBOX] = e_icon_factory_get_icon ("stock_outbox", E_ICON_SIZE_MENU);
|
|
folder_icons[FOLDER_ICON_TRASH] = e_icon_factory_get_icon ("stock_delete", E_ICON_SIZE_MENU);
|
|
folder_icons[FOLDER_ICON_JUNK] = e_icon_factory_get_icon ("stock_spam", E_ICON_SIZE_MENU);
|
|
initialised = TRUE;
|
|
}
|
|
|
|
gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name,
|
|
COL_BOOL_IS_STORE, &is_store, -1);
|
|
|
|
if (!is_store && full_name != NULL) {
|
|
if (!g_ascii_strcasecmp (full_name, "Inbox"))
|
|
pixbuf = folder_icons[FOLDER_ICON_INBOX];
|
|
else if (!g_ascii_strcasecmp (full_name, "Outbox"))
|
|
pixbuf = folder_icons[FOLDER_ICON_OUTBOX];
|
|
else if (!strcmp (full_name, CAMEL_VTRASH_NAME))
|
|
pixbuf = folder_icons[FOLDER_ICON_TRASH];
|
|
else if (!strcmp (full_name, 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);
|
|
g_free (full_name);
|
|
}
|
|
|
|
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);
|
|
g_free (name);
|
|
} else
|
|
display = name;
|
|
|
|
g_object_set (renderer, "text", display,
|
|
"weight", bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
|
|
NULL);
|
|
|
|
g_free (display);
|
|
}
|
|
|
|
static 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->select_uri);
|
|
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;
|
|
}
|
|
|
|
if (priv->autoexpand_id != 0) {
|
|
gtk_tree_row_reference_free (priv->autoexpand_row);
|
|
priv->autoexpand_row = NULL;
|
|
|
|
g_source_remove (priv->autoexpand_id);
|
|
priv->autoexpand_id = 0;
|
|
}
|
|
|
|
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);
|
|
|
|
gtk_tree_view_set_search_column((GtkTreeView *)tree, COL_STRING_DISPLAY_NAME);
|
|
|
|
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, "test-collapse-row", G_CALLBACK (emft_tree_test_collapse_row), 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->full_hash, p + 1)))
|
|
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;
|
|
gboolean is_store;
|
|
CamelStore *store;
|
|
EAccount *account;
|
|
char *full_name;
|
|
char *key;
|
|
|
|
gtk_tree_model_get ((GtkTreeModel *) model, iter,
|
|
COL_STRING_FULL_NAME, &full_name,
|
|
COL_POINTER_CAMEL_STORE, &store,
|
|
COL_BOOL_IS_STORE, &is_store,
|
|
-1);
|
|
|
|
si = 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, full_name ? full_name : "");
|
|
} else if (CAMEL_IS_VEE_STORE (store)) {
|
|
/* vfolder store */
|
|
key = g_strdup_printf ("vfolder/%s", full_name ? full_name : "");
|
|
} else {
|
|
/* local store */
|
|
key = g_strdup_printf ("local/%s", full_name ? full_name : "");
|
|
}
|
|
|
|
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 (full_name);
|
|
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, (EMFTModelExpandFunc)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;
|
|
char *full_name = NULL;
|
|
GtkTreePath *src_path;
|
|
gboolean is_store;
|
|
CamelStore *store;
|
|
CamelException ex;
|
|
GtkTreeIter iter;
|
|
|
|
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_FULL_NAME, &full_name,
|
|
COL_BOOL_IS_STORE, &is_store, -1);
|
|
|
|
if (is_store)
|
|
goto fail;
|
|
|
|
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);
|
|
g_free (full_name);
|
|
}
|
|
|
|
static void
|
|
tree_drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time, EMFolderTree *emft)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
char *full_name = NULL, *uri = NULL;
|
|
GtkTreePath *src_path;
|
|
CamelFolder *folder;
|
|
CamelStore *store;
|
|
CamelException ex;
|
|
GtkTreeIter iter;
|
|
|
|
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_FULL_NAME, &full_name,
|
|
COL_STRING_URI, &uri, -1);
|
|
|
|
/* make sure user isn't trying to drag on a placeholder row */
|
|
if (full_name == NULL)
|
|
goto fail;
|
|
|
|
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);
|
|
g_free (full_name);
|
|
g_free (uri);
|
|
}
|
|
|
|
/* TODO: Merge the drop handling code/menu's into one spot using a popup target for details */
|
|
/* Drop handling */
|
|
struct _DragDataReceivedAsync {
|
|
struct _mail_msg msg;
|
|
|
|
/* input data */
|
|
GdkDragContext *context;
|
|
|
|
/* Only selection->data and selection->length are valid */
|
|
GtkSelectionData *selection;
|
|
|
|
CamelStore *store;
|
|
char *full_name;
|
|
guint32 action;
|
|
guint info;
|
|
|
|
unsigned int move:1;
|
|
unsigned int moved:1;
|
|
unsigned int aborted:1;
|
|
};
|
|
|
|
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->data, m->full_name));
|
|
|
|
if (!(src = mail_tool_uri_to_folder(m->selection->data, 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 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->data, NULL);
|
|
|
|
if (m->move)
|
|
buf = g_strdup_printf (_("Moving folder %s"), url->fragment ? url->fragment : url->path + 1);
|
|
else
|
|
buf = g_strdup_printf (_("Copying folder %s"), url->fragment ? url->fragment : url->path + 1);
|
|
|
|
camel_url_free (url);
|
|
|
|
return buf;
|
|
} else {
|
|
if (m->move)
|
|
return g_strdup_printf (_("Moving messages into folder %s"), m->full_name);
|
|
else
|
|
return g_strdup_printf (_("Copying messages into folder %s"), m->full_name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
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 */
|
|
em_utils_selection_get_uidlist(m->selection, folder, m->move, &mm->ex);
|
|
m->moved = m->move && !camel_exception_is_set(&mm->ex);
|
|
break;
|
|
case DND_DROP_TYPE_MESSAGE_RFC822:
|
|
/* import a message/rfc822 stream */
|
|
em_utils_selection_get_message(m->selection, folder);
|
|
break;
|
|
case DND_DROP_TYPE_TEXT_URI_LIST:
|
|
/* import an mbox, maildir, or mh folder? */
|
|
em_utils_selection_get_mailbox(m->selection, 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;
|
|
|
|
/* ?? */
|
|
if (m->aborted) {
|
|
success = FALSE;
|
|
delete = FALSE;
|
|
} else {
|
|
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);
|
|
|
|
g_free(m->selection->data);
|
|
g_free(m->selection);
|
|
}
|
|
|
|
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_action(struct _DragDataReceivedAsync *m)
|
|
{
|
|
m->move = m->action == GDK_ACTION_MOVE;
|
|
e_thread_put (mail_thread_new, (EMsg *) m);
|
|
}
|
|
|
|
static void
|
|
emft_drop_popup_copy(GtkWidget *item, struct _DragDataReceivedAsync *m)
|
|
{
|
|
m->action = GDK_ACTION_COPY;
|
|
tree_drag_data_action(m);
|
|
}
|
|
|
|
static void
|
|
emft_drop_popup_move(GtkWidget *item, struct _DragDataReceivedAsync *m)
|
|
{
|
|
m->action = GDK_ACTION_MOVE;
|
|
tree_drag_data_action(m);
|
|
}
|
|
|
|
static void
|
|
emft_drop_popup_cancel(GtkWidget *item, struct _DragDataReceivedAsync *m)
|
|
{
|
|
m->aborted = TRUE;
|
|
mail_msg_free(&m->msg);
|
|
}
|
|
|
|
static EMPopupItem emft_drop_popup_menu[] = {
|
|
{ EM_POPUP_ITEM, "00.emc.00", N_("_Copy to Folder"), G_CALLBACK (emft_drop_popup_copy), NULL, NULL, 1 },
|
|
{ EM_POPUP_ITEM, "00.emc.01", N_("_Move to Folder"), G_CALLBACK (emft_drop_popup_move), NULL, NULL, 1 },
|
|
{ EM_POPUP_ITEM, "00.emc.02", N_("_Copy"), G_CALLBACK (emft_drop_popup_copy), NULL, "stock_folder-copy", 2 },
|
|
{ EM_POPUP_ITEM, "00.emc.03", N_("_Move"), G_CALLBACK (emft_drop_popup_move), NULL, "stock_folder-move", 2 },
|
|
{ EM_POPUP_BAR, "10.emc" },
|
|
{ EM_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), G_CALLBACK (emft_drop_popup_cancel), NULL, "stock_cancel", 0 },
|
|
};
|
|
|
|
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;
|
|
CamelStore *store;
|
|
GtkTreeIter iter;
|
|
char *full_name;
|
|
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_FULL_NAME, &full_name, -1);
|
|
|
|
/* make sure user isn't try to drop on a placeholder row */
|
|
/* FIXME: must allow drop of folders onto a store */
|
|
if (full_name == NULL) {
|
|
gtk_drag_finish (context, FALSE, FALSE, GDK_CURRENT_TIME);
|
|
return;
|
|
}
|
|
|
|
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 = full_name;
|
|
m->action = context->action;
|
|
m->info = info;
|
|
|
|
/* need to copy, goes away once we exit */
|
|
m->selection = g_malloc0(sizeof(*m->selection));
|
|
m->selection->data = g_malloc(selection->length);
|
|
memcpy(m->selection->data, selection->data, selection->length);
|
|
m->selection->length = selection->length;
|
|
|
|
if (context->action == GDK_ACTION_ASK) {
|
|
EMPopup *emp;
|
|
int mask;
|
|
GSList *menus = NULL;
|
|
GtkMenu *menu;
|
|
|
|
emp = em_popup_new("com.ximian.mail.storageset.popup.drop");
|
|
if (info != DND_DROP_TYPE_FOLDER)
|
|
mask = ~1;
|
|
else
|
|
mask = ~2;
|
|
|
|
for (i=0;i<sizeof(emft_drop_popup_menu)/sizeof(emft_drop_popup_menu[0]);i++) {
|
|
EMPopupItem *item = &emft_drop_popup_menu[i];
|
|
|
|
if ((item->mask & mask) == 0) {
|
|
item->activate_data = m;
|
|
menus = g_slist_append(menus, item);
|
|
}
|
|
}
|
|
em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free);
|
|
menu = em_popup_create_menu_once(emp, NULL, mask, mask);
|
|
gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
|
|
} else {
|
|
tree_drag_data_action(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 *full_name = NULL, *uri = NULL, *src_uri = NULL;
|
|
CamelStore *local, *sstore, *dstore;
|
|
GdkAtom atom = GDK_NONE;
|
|
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_FULL_NAME, &full_name,
|
|
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 && full_name) {
|
|
#if 0
|
|
/* only allow copying/moving folders (not messages) into the local Outbox */
|
|
if (dstore == local && !strcmp (full_name, "Outbox")) {
|
|
GdkAtom xfolder;
|
|
|
|
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
|
|
while (targets != NULL) {
|
|
if (targets->data == (gpointer) xfolder) {
|
|
atom = xfolder;
|
|
goto done;
|
|
}
|
|
|
|
targets = targets->next;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
/* don't allow copying/moving into the UNMATCHED vfolder */
|
|
if (!strncmp (uri, "vfolder:", 8) && !strcmp (full_name, CAMEL_UNMATCHED_NAME))
|
|
goto done;
|
|
|
|
/* don't allow copying/moving into a vTrash/vJunk folder */
|
|
if (!strcmp (full_name, CAMEL_VTRASH_NAME)
|
|
|| !strcmp (full_name, CAMEL_VJUNK_NAME))
|
|
goto done;
|
|
}
|
|
|
|
if (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);
|
|
goto done;
|
|
}
|
|
|
|
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) {
|
|
atom = xfolder;
|
|
goto done;
|
|
}
|
|
|
|
targets = targets->next;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* don't allow copying/moving of the UNMATCHED vfolder */
|
|
if (!strcmp (url->protocol, "vfolder") && !strcmp (path, CAMEL_UNMATCHED_NAME)) {
|
|
camel_url_free (url);
|
|
goto done;
|
|
}
|
|
|
|
/* 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);
|
|
goto done;
|
|
}
|
|
}
|
|
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) {
|
|
atom = xfolder;
|
|
goto done;
|
|
}
|
|
|
|
targets = targets->next;
|
|
}
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
/* can't drag anything but a vfolder into a vfolder */
|
|
if (uri && strncmp(uri, "vfolder:", 8) == 0)
|
|
goto done;
|
|
|
|
/* Now we either have a store or a normal folder */
|
|
|
|
if (is_store) {
|
|
GdkAtom xfolder;
|
|
|
|
xfolder = drop_atoms[DND_DROP_TYPE_FOLDER];
|
|
while (targets != NULL) {
|
|
if (targets->data == (gpointer) xfolder) {
|
|
atom = xfolder;
|
|
goto done;
|
|
}
|
|
|
|
targets = targets->next;
|
|
}
|
|
} else {
|
|
int i;
|
|
|
|
while (targets != NULL) {
|
|
for (i = 0; i < NUM_DROP_TYPES; i++) {
|
|
if (targets->data == (gpointer) drop_atoms[i]) {
|
|
atom = drop_atoms[i];
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
targets = targets->next;
|
|
}
|
|
}
|
|
|
|
done:
|
|
|
|
g_free (full_name);
|
|
g_free (uri);
|
|
|
|
return atom;
|
|
}
|
|
|
|
static gboolean
|
|
tree_drag_drop (GtkWidget *widget, GdkDragContext *context, 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 (priv->autoexpand_id != 0) {
|
|
gtk_tree_row_reference_free (priv->autoexpand_row);
|
|
priv->autoexpand_row = NULL;
|
|
|
|
g_source_remove (priv->autoexpand_id);
|
|
priv->autoexpand_id = 0;
|
|
}
|
|
|
|
if (!gtk_tree_view_get_path_at_pos (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;
|
|
}
|
|
|
|
if (priv->autoexpand_id != 0) {
|
|
gtk_tree_row_reference_free (priv->autoexpand_row);
|
|
priv->autoexpand_row = NULL;
|
|
|
|
g_source_remove (priv->autoexpand_id);
|
|
priv->autoexpand_id = 0;
|
|
}
|
|
|
|
gtk_tree_view_set_drag_dest_row(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_autoexpand (EMFolderTree *emft)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
GtkTreePath *path;
|
|
|
|
path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
|
|
gtk_tree_view_expand_row (priv->treeview, path, FALSE);
|
|
gtk_tree_path_free (path);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
tree_drag_motion (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, EMFolderTree *emft)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
GtkTreeModel *model = (GtkTreeModel *) priv->model;
|
|
GtkTreeViewDropPosition pos;
|
|
GdkDragAction action = 0;
|
|
GtkTreePath *path;
|
|
GtkTreeIter iter;
|
|
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);
|
|
|
|
gtk_tree_model_get_iter (model, &iter, path);
|
|
|
|
if (gtk_tree_model_iter_has_child (model, &iter) && !gtk_tree_view_row_expanded (priv->treeview, path)) {
|
|
if (priv->autoexpand_id != 0) {
|
|
GtkTreePath *autoexpand_path;
|
|
|
|
autoexpand_path = gtk_tree_row_reference_get_path (priv->autoexpand_row);
|
|
if (gtk_tree_path_compare (autoexpand_path, path) != 0) {
|
|
/* row changed, restart timer */
|
|
gtk_tree_row_reference_free (priv->autoexpand_row);
|
|
priv->autoexpand_row = gtk_tree_row_reference_new (model, path);
|
|
g_source_remove (priv->autoexpand_id);
|
|
priv->autoexpand_id = g_timeout_add (600, (GSourceFunc) tree_autoexpand, emft);
|
|
}
|
|
|
|
gtk_tree_path_free (autoexpand_path);
|
|
} else {
|
|
priv->autoexpand_id = g_timeout_add (600, (GSourceFunc) tree_autoexpand, emft);
|
|
priv->autoexpand_row = gtk_tree_row_reference_new (model, path);
|
|
}
|
|
} else if (priv->autoexpand_id != 0) {
|
|
gtk_tree_row_reference_free (priv->autoexpand_row);
|
|
priv->autoexpand_row = NULL;
|
|
|
|
g_source_remove (priv->autoexpand_id);
|
|
priv->autoexpand_id = 0;
|
|
}
|
|
|
|
target = 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_UID_LIST:
|
|
case DND_DROP_TYPE_FOLDER:
|
|
action = context->suggested_action;
|
|
if (action == GDK_ACTION_COPY && (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 | GDK_ACTION_ASK);
|
|
gtk_drag_dest_set((GtkWidget *)priv->treeview, GTK_DEST_DEFAULT_ALL, drop_types, NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
|
|
|
|
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;
|
|
}
|
|
|
|
GList *
|
|
em_folder_tree_get_selected_uris (EMFolderTree *emft)
|
|
{
|
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (emft->priv->treeview);
|
|
GList *list = NULL, *rows, *l;
|
|
GtkTreeModel *model;
|
|
|
|
/* at first, add lost uris */
|
|
for (l = emft->priv->lost_folders; l; l = g_list_next(l))
|
|
list = g_list_append (l, g_strdup (l->data));
|
|
|
|
rows = gtk_tree_selection_get_selected_rows(selection, &model);
|
|
for (l=rows; l; l=g_list_next(l)) {
|
|
GtkTreeIter iter;
|
|
GtkTreePath *path = l->data;
|
|
|
|
if (gtk_tree_model_get_iter(model, &iter, path)) {
|
|
char *uri;
|
|
|
|
gtk_tree_model_get(model, &iter, COL_STRING_URI, &uri, -1);
|
|
list = g_list_prepend (list, uri);
|
|
}
|
|
gtk_tree_path_free(path);
|
|
}
|
|
g_list_free(rows);
|
|
|
|
return g_list_reverse (list);
|
|
}
|
|
|
|
static void
|
|
get_selected_uris_path_iterate (GtkTreeModel *model, GtkTreePath *treepath, GtkTreeIter *iter, gpointer data)
|
|
{
|
|
GList **list = (GList **) data;
|
|
char *full_name;
|
|
|
|
gtk_tree_model_get (model, iter, COL_STRING_FULL_NAME, &full_name, -1);
|
|
*list = g_list_append (*list, full_name);
|
|
}
|
|
|
|
GList *
|
|
em_folder_tree_get_selected_paths (EMFolderTree *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 is_store;
|
|
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,
|
|
COL_BOOL_IS_STORE, &is_store,
|
|
-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 */
|
|
emft_update_model_expanded_state (priv, &root, FALSE);
|
|
|
|
if (is_store) {
|
|
path = gtk_tree_model_get_path ((GtkTreeModel *) model, &root);
|
|
gtk_tree_view_collapse_row (priv->treeview, path);
|
|
emft_queue_save_state (m->emft);
|
|
gtk_tree_path_free (path);
|
|
return;
|
|
} else {
|
|
gtk_tree_store_remove (model, &iter);
|
|
}
|
|
} else {
|
|
int fully_loaded = (m->flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? TRUE : FALSE;
|
|
|
|
do {
|
|
em_folder_tree_model_set_folder_info (priv->model, &iter, si, fi, fully_loaded);
|
|
|
|
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);
|
|
} else if (priv->select_uri) {
|
|
char *uri = priv->select_uri;
|
|
|
|
priv->select_uri = NULL;
|
|
em_folder_tree_set_selected (m->emft, uri);
|
|
g_free (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;
|
|
gboolean is_store;
|
|
CamelStore *store;
|
|
EAccount *account;
|
|
char *full_name;
|
|
char *key;
|
|
|
|
gtk_tree_model_get ((GtkTreeModel *) priv->model, iter,
|
|
COL_STRING_FULL_NAME, &full_name,
|
|
COL_POINTER_CAMEL_STORE, &store,
|
|
COL_BOOL_IS_STORE, &is_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, full_name ? full_name : "");
|
|
} else if (CAMEL_IS_VEE_STORE (store)) {
|
|
/* vfolder store */
|
|
key = g_strdup_printf ("vfolder/%s", full_name ? full_name : "");
|
|
} else {
|
|
/* local store */
|
|
key = g_strdup_printf ("local/%s", full_name ? full_name : "");
|
|
}
|
|
|
|
em_folder_tree_model_set_expanded (priv->model, key, expanded);
|
|
g_free (full_name);
|
|
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;
|
|
char *full_name;
|
|
gboolean load;
|
|
|
|
model = gtk_tree_view_get_model (treeview);
|
|
|
|
gtk_tree_model_get (model, root,
|
|
COL_STRING_FULL_NAME, &full_name,
|
|
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);
|
|
g_free (full_name);
|
|
return;
|
|
}
|
|
|
|
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 = full_name;
|
|
m->flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE;
|
|
m->select_uri = NULL;
|
|
|
|
e_thread_put (mail_thread_new, (EMsg *) m);
|
|
}
|
|
|
|
static gboolean
|
|
emft_tree_test_collapse_row (GtkTreeView *treeview, GtkTreeIter *root, GtkTreePath *tree_path, EMFolderTree *emft)
|
|
{
|
|
GtkTreeSelection *selection;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter cursor;
|
|
|
|
selection = gtk_tree_view_get_selection (treeview);
|
|
if (gtk_tree_selection_get_selected (selection, &model, &cursor)) {
|
|
/* select the collapsed node IFF it is a parent of the currently selected folder */
|
|
if (gtk_tree_store_is_ancestor ((GtkTreeStore *) model, root, &cursor))
|
|
gtk_tree_view_set_cursor (treeview, tree_path, NULL, FALSE);
|
|
}
|
|
|
|
emft_update_model_expanded_state (emft->priv, root, FALSE);
|
|
emft_queue_save_state (emft);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
char *full_name, *uri;
|
|
GtkTreeIter iter;
|
|
guint32 flags;
|
|
|
|
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_FULL_NAME, &full_name,
|
|
COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags, -1);
|
|
|
|
g_free (priv->select_uri);
|
|
priv->select_uri = NULL;
|
|
|
|
g_free (priv->selected_uri);
|
|
priv->selected_uri = uri;
|
|
|
|
g_free (priv->selected_path);
|
|
priv->selected_path = full_name;
|
|
|
|
g_signal_emit (emft, signals[FOLDER_SELECTED], 0, full_name, uri, flags);
|
|
g_signal_emit (emft, signals[FOLDER_ACTIVATED], 0, full_name, 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 = NULL, *tostore = NULL;
|
|
char *tobase = NULL, *frombase;
|
|
CamelException ex;
|
|
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);
|
|
frombase = priv->selected_path;
|
|
|
|
if (!(fromstore = camel_session_get_store (session, priv->selected_uri, &ex))) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *) cfd->emft),
|
|
cfd->delete?"mail:no-move-folder-notexist":"mail:no-copy-folder-notexist", frombase, uri, ex.desc, NULL);
|
|
goto fail;
|
|
}
|
|
|
|
if (cfd->delete && fromstore == mail_component_peek_local_store (NULL) && is_special_local_folder (frombase)) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *) cfd->emft),
|
|
"mail:no-rename-special-folder", frombase, NULL);
|
|
goto fail;
|
|
}
|
|
|
|
if (!(tostore = camel_session_get_store (session, uri, &ex))) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *) cfd->emft),
|
|
cfd->delete?"mail:no-move-folder-to-notexist":"mail:no-move-folder-to-notexist", frombase, uri, ex.desc, NULL);
|
|
goto fail;
|
|
}
|
|
|
|
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);
|
|
fail:
|
|
if (fromstore)
|
|
camel_object_unref(fromstore);
|
|
if (tostore)
|
|
camel_object_unref(tostore);
|
|
camel_exception_clear (&ex);
|
|
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"), _("C_opy"),
|
|
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"), _("_Move"),
|
|
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 *full_name, void (* done) (CamelFolderInfo *fi, void *user_data), void *user_data)
|
|
{
|
|
char *name, *namebuf = NULL;
|
|
struct _EMCreateFolder *m;
|
|
const char *parent;
|
|
int id;
|
|
|
|
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 *full_name, const char *uri)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
struct _EMFolderTreeModelStoreInfo *si;
|
|
gboolean created = FALSE;
|
|
CamelStore *store;
|
|
CamelException ex;
|
|
|
|
d(printf ("Creating folder: %s (%s)\n", full_name, uri));
|
|
|
|
camel_exception_init (&ex);
|
|
if (!(store = (CamelStore *) camel_session_get_service (session, uri, CAMEL_PROVIDER_STORE, &ex))) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:no-create-folder-nostore", full_name, ex.desc, NULL);
|
|
goto fail;
|
|
}
|
|
|
|
if (!(si = g_hash_table_lookup (priv->model->store_hash, store))) {
|
|
abort();
|
|
camel_object_unref (store);
|
|
goto fail;
|
|
}
|
|
|
|
camel_object_unref (store);
|
|
|
|
mail_msg_wait (emft_create_folder (si->store, full_name, created_cb, &created));
|
|
fail:
|
|
camel_exception_clear(&ex);
|
|
|
|
return created;
|
|
}
|
|
|
|
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)) {
|
|
EMVFolderRule *rule;
|
|
|
|
rule = em_vfolder_rule_new();
|
|
filter_rule_set_name((FilterRule *)rule, path);
|
|
vfolder_gui_add_rule(rule);
|
|
gtk_widget_destroy((GtkWidget *)emfs);
|
|
} 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 *full_name, CamelException *ex)
|
|
{
|
|
guint32 flags = CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_FAST;
|
|
CamelFolderInfo *fi;
|
|
|
|
if (camel_store_supports_subscriptions (store))
|
|
flags |= CAMEL_STORE_FOLDER_INFO_SUBSCRIBED;
|
|
|
|
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 *full_name;
|
|
|
|
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_FULL_NAME, &full_name,
|
|
COL_POINTER_CAMEL_STORE, &store, -1);
|
|
|
|
camel_exception_init (&ex);
|
|
emft_popup_delete_folders (store, full_name, &ex);
|
|
if (camel_exception_is_set (&ex)) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:no-delete-folder", full_name, ex.desc, NULL);
|
|
camel_exception_clear (&ex);
|
|
}
|
|
|
|
g_free (full_name);
|
|
}
|
|
|
|
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;
|
|
char *full_name;
|
|
|
|
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_FULL_NAME, &full_name, -1);
|
|
|
|
local = mail_component_peek_local_store (NULL);
|
|
|
|
if (store == local && is_special_local_folder (full_name)) {
|
|
e_error_run(NULL, "mail:no-delete-spethal-folder", full_name, NULL);
|
|
return;
|
|
}
|
|
|
|
dialog = e_error_new((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:ask-delete-folder", full_name, NULL);
|
|
g_signal_connect (dialog, "response", G_CALLBACK (emft_popup_delete_response), emft);
|
|
gtk_widget_show (dialog);
|
|
g_free (full_name);
|
|
}
|
|
|
|
static void
|
|
emft_popup_rename_folder (GtkWidget *item, EMFolderTree *emft)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
char *prompt, *full_name, *name, *new_name, *uri;
|
|
GtkTreeSelection *selection;
|
|
const char *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_FULL_NAME, &full_name,
|
|
COL_STRING_DISPLAY_NAME, &name,
|
|
COL_POINTER_CAMEL_STORE, &store,
|
|
COL_STRING_URI, &uri, -1);
|
|
|
|
/* don't allow user to rename one of the special local folders */
|
|
if (store == local && is_special_local_folder (full_name)) {
|
|
e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:no-rename-spethal-folder", full_name, NULL);
|
|
g_free (full_name);
|
|
g_free (name);
|
|
g_free (uri);
|
|
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_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:no-rename-folder-exists", name, new_name, NULL);
|
|
} 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_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft),
|
|
"mail:no-rename-folder", oldpath, newpath, ex.desc, NULL);
|
|
camel_exception_clear (&ex);
|
|
}
|
|
|
|
done = TRUE;
|
|
}
|
|
|
|
g_free (path);
|
|
}
|
|
|
|
g_free (new_name);
|
|
}
|
|
|
|
g_free (full_name);
|
|
g_free (name);
|
|
g_free (uri);
|
|
}
|
|
|
|
|
|
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);
|
|
g_free (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, "stock_folder-copy", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT },
|
|
{ EM_POPUP_ITEM, "10.emc.01", N_("_Move..."), G_CALLBACK (emft_popup_move), NULL, "stock_folder-move", 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, "stock_folder", 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, "stock_delete", 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, "stock_folder-properties", 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;
|
|
EMPopupTarget *target;
|
|
GtkTreePath *tree_path;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
GSList *menus = NULL;
|
|
guint32 info_flags = 0;
|
|
guint32 flags = 0;
|
|
gboolean isstore;
|
|
char *uri, *full_name;
|
|
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_FULL_NAME, &full_name,
|
|
COL_BOOL_IS_STORE, &isstore, -1);
|
|
|
|
/* Stores have full_name == NULL, otherwise its just a placeholder */
|
|
/* NB: This is kind of messy */
|
|
if (!isstore && full_name == NULL) {
|
|
g_free (uri);
|
|
return FALSE;
|
|
}
|
|
|
|
/* TODO: em_popup_target_folder_new? */
|
|
if (isstore) {
|
|
flags |= EM_POPUP_FOLDER_STORE;
|
|
} else {
|
|
flags |= EM_POPUP_FOLDER_FOLDER;
|
|
|
|
local = mail_component_peek_local_store (NULL);
|
|
|
|
/* don't allow deletion of special local folders */
|
|
if (!(store == local && is_special_local_folder (full_name)))
|
|
flags |= EM_POPUP_FOLDER_DELETE;
|
|
|
|
/* hack for vTrash/vJunk */
|
|
if (!strcmp (full_name, CAMEL_VTRASH_NAME) || !strcmp (full_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);
|
|
}
|
|
|
|
g_free (full_name);
|
|
g_free (uri);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static void
|
|
emft_tree_selection_changed (GtkTreeSelection *selection, EMFolderTree *emft)
|
|
{
|
|
struct _EMFolderTreePrivate *priv = emft->priv;
|
|
char *full_name, *uri;
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
guint32 flags;
|
|
|
|
if (!emft_selection_get_selected (selection, &model, &iter))
|
|
return;
|
|
|
|
gtk_tree_model_get (model, &iter, COL_STRING_FULL_NAME, &full_name,
|
|
COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags, -1);
|
|
|
|
g_free (priv->select_uri);
|
|
priv->select_uri = NULL;
|
|
|
|
g_free (priv->selected_uri);
|
|
priv->selected_uri = uri;
|
|
|
|
g_free (priv->selected_path);
|
|
priv->selected_path = full_name;
|
|
|
|
g_signal_emit (emft, signals[FOLDER_SELECTED], 0, full_name, uri, flags);
|
|
}
|
|
|
|
|
|
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;
|
|
char *path, *p;
|
|
CamelURL *url;
|
|
|
|
g_return_if_fail (EM_IS_FOLDER_TREE (emft));
|
|
|
|
priv = emft->priv;
|
|
g_free (priv->select_uri);
|
|
priv->select_uri = NULL;
|
|
|
|
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))) {
|
|
priv->select_uri = g_strdup (uri);
|
|
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 = url->fragment;
|
|
else
|
|
path = url->path && url->path[0]=='/' ? url->path+1:url->path;
|
|
path = g_strdup(path?path:"");
|
|
camel_url_free (url);
|
|
|
|
if (path[0] == 0)
|
|
row = si->row;
|
|
|
|
if (row || (row = g_hash_table_lookup (si->full_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_view_set_cursor (priv->treeview, tree_path, NULL, FALSE);
|
|
gtk_tree_view_scroll_to_cell (priv->treeview, tree_path, NULL, TRUE, 0.8f, 0.0f);
|
|
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->full_hash, path)))
|
|
break;
|
|
}
|
|
|
|
p--;
|
|
}
|
|
|
|
/* 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->store = store;
|
|
m->emft = emft;
|
|
g_object_ref(emft);
|
|
if (row) {
|
|
m->top = path;
|
|
m->root = gtk_tree_row_reference_copy(row);
|
|
} else {
|
|
g_free(path);
|
|
m->root = gtk_tree_row_reference_copy(si->row);
|
|
}
|
|
|
|
m->flags = CAMEL_STORE_FOLDER_INFO_FAST | CAMEL_STORE_FOLDER_INFO_RECURSIVE;
|
|
m->select_uri = g_strdup (uri);
|
|
|
|
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_state (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);
|
|
}
|