/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Jeffrey Stedfast * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 "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 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 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_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 *path; 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, &path, COL_BOOL_IS_STORE, &is_store, -1); if (!is_store && path != NULL) { if (!g_ascii_strcasecmp (path, "Inbox")) pixbuf = folder_icons[FOLDER_ICON_INBOX]; else if (!g_ascii_strcasecmp (path, "Outbox")) pixbuf = folder_icons[FOLDER_ICON_OUTBOX]; else if (!strcmp (path, CAMEL_VTRASH_NAME)) pixbuf = folder_icons[FOLDER_ICON_TRASH]; else if (!strcmp(path, 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; } 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); 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->full_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_FULL_NAME, &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, (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; GtkTreePath *src_path; gboolean is_store; CamelStore *store; CamelException ex; GtkTreeIter iter; char *full_name; 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); } 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; CamelFolder *folder; CamelStore *store; CamelException ex; GtkTreeIter iter; char *full_name, *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_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); } /* 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 */ 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 = g_strdup (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;imask & 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 *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_FULL_NAME, &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) { #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 (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; isuggested_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; itreeview, 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_append(list, g_strdup(uri)); } gtk_tree_path_free(path); } g_list_free(rows); 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_FULL_NAME, &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 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); 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_FULL_NAME, &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; gboolean load; char *path; model = gtk_tree_view_get_model (treeview); gtk_tree_model_get (model, root, COL_STRING_FULL_NAME, &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; } 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 (path); 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) { gtk_tree_view_set_cursor (treeview, tree_path, NULL, FALSE); 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; 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, &path, COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags, -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, flags); 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 = 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 + 1; 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 *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; 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))) { e_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft), "mail:no-create-folder-nostore", path, 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, path, 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)) { 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 *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 *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_FULL_NAME, &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_error_run((GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emft), "mail:no-delete-folder", path, ex.desc, NULL); 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; 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); } 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); 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); } } 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, "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); if (full_name == NULL) return FALSE; 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); } return TRUE; } static void emft_tree_selection_changed (GtkTreeSelection *selection, EMFolderTree *emft) { struct _EMFolderTreePrivate *priv = emft->priv; GtkTreeModel *model; GtkTreeIter iter; char *path, *uri; guint32 flags; if (!emft_selection_get_selected (selection, &model, &iter)) return; gtk_tree_model_get (model, &iter, COL_STRING_FULL_NAME, &path, COL_STRING_URI, &uri, COL_UINT_FLAGS, &flags, -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, 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; 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 = 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_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_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); }