From 2829122e95b3f123487b7928975fa3049af898b5 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Fri, 23 Jan 2004 22:26:01 +0000 Subject: Removed the drag & drop interfaces, apparently these aren't good enough 2004-01-23 Jeffrey Stedfast * em-folder-tree-model.c (em_folder_tree_model_get_type): Removed the drag & drop interfaces, apparently these aren't good enough for what we need. Gotta implement this all the Hard Way (tm). (em_folder_tree_model_drag_data_received): Helper function called by the drag_data_received signal callback in em-folder-tree.c (em_folder_tree_model_row_drop_possible): Same idea. (em_folder_tree_model_row_drop_target): Again. (em_folder_tree_model_row_draggable): You get the idea... (em_folder_tree_model_drag_data_get): And again. (em_folder_tree_model_drag_data_delete): Same. (em_folder_tree_model_set_drag_drop_types): Setup the drag & drop types on the widget (since the target drag & drop types are now internal to the model code rathr than the tree code). * em-folder-tree.c (em_folder_tree_enable_drag_and_drop): Rewritten. Connect to all the drag & drop signals and implemnent them. svn path=/trunk/; revision=24393 --- mail/em-folder-tree-model.c | 1699 ++++++++++++++++++++++--------------------- 1 file changed, 859 insertions(+), 840 deletions(-) (limited to 'mail/em-folder-tree-model.c') diff --git a/mail/em-folder-tree-model.c b/mail/em-folder-tree-model.c index fc29c5e761..7d8bdf7946 100644 --- a/mail/em-folder-tree-model.c +++ b/mail/em-folder-tree-model.c @@ -60,6 +60,36 @@ static GType col_types[] = { }; +/* 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 drop_atoms[NUM_DROP_TYPES]; + + /* GObject virtual method overrides */ static void em_folder_tree_model_class_init (EMFolderTreeModelClass *klass); static void em_folder_tree_model_init (EMFolderTreeModel *model); @@ -68,39 +98,17 @@ static void em_folder_tree_model_finalize (GObject *obj); /* interface init methods */ static void tree_model_iface_init (GtkTreeModelIface *iface); static void tree_sortable_iface_init (GtkTreeSortableIface *iface); -static void tree_drag_dest_iface_init (GtkTreeDragDestIface *iface); -static void tree_drag_source_iface_init (GtkTreeDragSourceIface *iface); - -/* drag & drop iface methods */ -static gboolean model_drag_data_received (GtkTreeDragDest *drag_dest, - GtkTreePath *dest_path, - GtkSelectionData *selection); -static gboolean model_row_drop_possible (GtkTreeDragDest *drag_dest, - GtkTreePath *dest_path, - GtkSelectionData *selection); -static gboolean model_row_draggable (GtkTreeDragSource *drag_source, - GtkTreePath *src_path); -static gboolean model_drag_data_get (GtkTreeDragSource *drag_source, - GtkTreePath *src_path, - GtkSelectionData *selection); -static gboolean model_drag_data_delete (GtkTreeDragSource *drag_source, - GtkTreePath *src_path); enum { LOADING_ROW, - DRAG_DATA_RECEIVED, - ROW_DROP_POSSIBLE, - ROW_DRAGGABLE, - DRAG_DATA_GET, - DRAG_DATA_DELETE, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0, }; -static GtkTreeStore *parent_class = NULL; +static GtkTreeStoreClass *parent_class = NULL; GType @@ -130,16 +138,6 @@ em_folder_tree_model_get_type (void) NULL, NULL }; - static const GInterfaceInfo drag_dest_info = { - (GInterfaceInitFunc) tree_drag_dest_iface_init, - NULL, - NULL - }; - static const GInterfaceInfo drag_source_info = { - (GInterfaceInitFunc) tree_drag_source_iface_init, - NULL, - NULL - }; type = g_type_register_static (GTK_TYPE_TREE_STORE, "EMFolderTreeModel", &info, 0); @@ -147,10 +145,6 @@ em_folder_tree_model_get_type (void) &tree_model_info); g_type_add_interface_static (type, GTK_TYPE_TREE_SORTABLE, &sortable_info); - g_type_add_interface_static (type, GTK_TYPE_TREE_DRAG_DEST, - &drag_dest_info); - g_type_add_interface_static (type, GTK_TYPE_TREE_DRAG_SOURCE, - &drag_source_info); } return type; @@ -161,6 +155,7 @@ static void em_folder_tree_model_class_init (EMFolderTreeModelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + int i; parent_class = g_type_class_ref (GTK_TYPE_TREE_STORE); @@ -177,6 +172,9 @@ em_folder_tree_model_class_init (EMFolderTreeModelClass *klass) G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); + + for (i = 0; i < NUM_DROP_TYPES; i++) + drop_atoms[i] = gdk_atom_intern (drop_types[i].target, FALSE); } static int @@ -338,1064 +336,1085 @@ tree_sortable_iface_init (GtkTreeSortableIface *iface) ; } -static void -tree_drag_dest_iface_init (GtkTreeDragDestIface *iface) -{ - iface->drag_data_received = model_drag_data_received; - iface->row_drop_possible = model_row_drop_possible; -} static void -tree_drag_source_iface_init (GtkTreeDragSourceIface *iface) +em_folder_tree_model_load_state (EMFolderTreeModel *model, const char *filename) { - iface->row_draggable = model_row_draggable; - iface->drag_data_get = model_drag_data_get; - iface->drag_data_delete = model_drag_data_delete; + char *node; + FILE *fp; + + g_hash_table_foreach_remove (model->expanded, expanded_free, NULL); + + if ((fp = fopen (filename, "r")) == NULL) + return; + + while (camel_file_util_decode_string (fp, &node) != -1) + g_hash_table_insert (model->expanded, node, GINT_TO_POINTER (TRUE)); + + fclose (fp); } -static void -drop_uid_list (CamelFolder *dest, gboolean move, GtkSelectionData *selection, CamelException *ex) +EMFolderTreeModel * +em_folder_tree_model_new (const char *evolution_dir) { - CamelFolder *src; - GPtrArray *uids; - char *src_uri; - - em_utils_selection_get_uidlist (selection, &src_uri, &uids); + EMFolderTreeModel *model; + char *filename; - if (!(src = mail_tool_uri_to_folder (src_uri, 0, ex))) { - em_utils_uids_free (uids); - g_free (src_uri); - return; - } + model = g_object_new (EM_TYPE_FOLDER_TREE_MODEL, NULL); + gtk_tree_store_set_column_types ((GtkTreeStore *) model, NUM_COLUMNS, col_types); + gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *) model, + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, + GTK_SORT_ASCENDING); - g_free (src_uri); + filename = g_build_filename (evolution_dir, "mail", "config", "folder-tree.state", NULL); + em_folder_tree_model_load_state (model, filename); + model->filename = filename; - camel_folder_transfer_messages_to (src, uids, dest, NULL, move, ex); - em_utils_uids_free (uids); - camel_object_unref (src); + return model; } -static void -drop_folder (CamelFolder *dest, gboolean move, GtkSelectionData *selection, CamelException *ex) + +void +em_folder_tree_model_set_folder_info (EMFolderTreeModel *model, GtkTreeIter *iter, + struct _EMFolderTreeModelStoreInfo *si, + CamelFolderInfo *fi) { - CamelFolder *src; + GtkTreeRowReference *uri_row, *path_row; + unsigned int unread; + GtkTreePath *path; + GtkTreeIter sub; + gboolean load; - /* get the folder being dragged */ - if (!(src = mail_tool_uri_to_folder (selection->data, 0, ex))) - return; + load = fi->child == NULL && !(fi->flags & (CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_NOINFERIORS)); - if (src->parent_store == dest->parent_store && move) { - /* simple rename() action */ - char *old_name, *new_name; - - old_name = g_strdup (src->full_name); - new_name = g_strdup_printf ("%s/%s", dest->full_name, src->name); - - camel_store_rename_folder (dest->parent_store, old_name, new_name, ex); - - g_free (old_name); - g_free (new_name); - } else { - /* copy the folder to the new location */ - CamelFolder *folder; - char *path; + path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter); + uri_row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path); + path_row = gtk_tree_row_reference_copy (uri_row); + gtk_tree_path_free (path); + + g_hash_table_insert (model->uri_hash, g_strdup (fi->url), uri_row); + g_hash_table_insert (si->path_hash, g_strdup (fi->path), path_row); + printf ("added '%s' to path_hash\n", fi->path); + + unread = fi->unread_message_count == -1 ? 0 : fi->unread_message_count; + + gtk_tree_store_set ((GtkTreeStore *) model, iter, + COL_STRING_DISPLAY_NAME, fi->name, + COL_POINTER_CAMEL_STORE, si->store, + COL_STRING_FOLDER_PATH, fi->path, + COL_STRING_URI, fi->url, + COL_UINT_UNREAD, unread, + COL_BOOL_IS_STORE, FALSE, + COL_BOOL_LOAD_SUBDIRS, load, + -1); + + if (fi->child) { + fi = fi->child; - path = g_strdup_printf ("%s/%s", dest->full_name, src->name); - if ((folder = camel_store_get_folder (dest->parent_store, path, CAMEL_STORE_FOLDER_CREATE, ex))) { - GPtrArray *uids; - - uids = camel_folder_get_uids (src); - camel_folder_transfer_messages_to (src, uids, folder, NULL, FALSE, ex); - camel_folder_free_uids (src, uids); - - camel_object_unref (folder); - } + do { + gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter); + em_folder_tree_model_set_folder_info (model, &sub, si, fi); + fi = fi->sibling; + } while (fi); + } else if (load) { + /* create a placeholder node for our subfolders... */ + gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter); + gtk_tree_store_set ((GtkTreeStore *) model, &sub, + COL_STRING_DISPLAY_NAME, _("Loading..."), + COL_POINTER_CAMEL_STORE, NULL, + COL_STRING_FOLDER_PATH, NULL, + COL_BOOL_LOAD_SUBDIRS, FALSE, + COL_BOOL_IS_STORE, FALSE, + COL_STRING_URI, NULL, + COL_UINT_UNREAD, 0, + -1); - g_free (path); + path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter); + g_signal_emit (model, signals[LOADING_ROW], 0, path, iter); + gtk_tree_path_free (path); } - - camel_object_unref (src); } -static gboolean -import_message_rfc822 (CamelFolder *dest, CamelStream *stream, gboolean scan_from, CamelException *ex) + +static void +folder_subscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model) { - CamelMimeParser *mp; + struct _EMFolderTreeModelStoreInfo *si; + GtkTreeRowReference *row; + GtkTreeIter parent, iter; + GtkTreePath *path; + gboolean load; + char *dirname; - mp = camel_mime_parser_new (); - camel_mime_parser_scan_from (mp, scan_from); - camel_mime_parser_init_with_stream (mp, stream); + if (!(si = g_hash_table_lookup (model->store_hash, store))) + goto done; - while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) { - CamelMessageInfo *info; - CamelMimeMessage *msg; - - msg = camel_mime_message_new (); - if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) { - camel_object_unref (msg); - camel_object_unref (mp); - return FALSE; - } - - /* append the message to the folder... */ - info = g_new0 (CamelMessageInfo, 1); - camel_folder_append_message (dest, msg, info, NULL, ex); - camel_object_unref (msg); - - if (camel_exception_is_set (ex)) { - camel_object_unref (mp); - return FALSE; - } + /* make sure we don't already know about it? */ + if (g_hash_table_lookup (si->path_hash, fi->path)) + goto done; + + /* get our parent folder's path */ + if (!(dirname = g_path_get_dirname (fi->path))) + goto done; + + if (!strcmp (dirname, "/")) { + /* user subscribed to a toplevel folder */ + row = si->row; + g_free (dirname); + } else { + row = g_hash_table_lookup (si->path_hash, dirname); + g_free (dirname); - /* skip over the FROM_END state */ - camel_mime_parser_step (mp, 0, 0); + /* if row is NULL, don't bother adding to the tree, + * when the user expands enough nodes - it will be + * added auto-magically */ + if (row == NULL) + goto done; } - camel_object_unref (mp); + path = gtk_tree_row_reference_get_path (row); + if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &parent, path))) { + gtk_tree_path_free (path); + goto done; + } - return TRUE; + gtk_tree_path_free (path); + + /* make sure parent's subfolders have already been loaded */ + gtk_tree_model_get ((GtkTreeModel *) model, &parent, COL_BOOL_LOAD_SUBDIRS, &load, -1); + if (load) + goto done; + + /* append a new node */ + gtk_tree_store_append ((GtkTreeStore *) model, &iter, &parent); + + em_folder_tree_model_set_folder_info (model, &iter, si, fi); + + done: + + camel_object_unref (store); + camel_folder_info_free (fi); } static void -drop_message_rfc822 (CamelFolder *dest, GtkSelectionData *selection, CamelException *ex) +folder_subscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) { - CamelStream *stream; - gboolean scan_from; - - scan_from = selection->length > 5 && !strncmp (selection->data, "From ", 5); - stream = camel_stream_mem_new_with_buffer (selection->data, selection->length); - - import_message_rfc822 (dest, stream, scan_from, ex); + CamelFolderInfo *fi; - camel_object_unref (stream); + camel_object_ref (store); + fi = camel_folder_info_clone (event_data); + mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed, store, fi, model); } static void -drop_text_uri_list (CamelFolder *dest, GtkSelectionData *selection, CamelException *ex) +folder_unsubscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model) { - char **urls, *url, *tmp; - CamelStream *stream; - CamelURL *uri; - int fd, i; + struct _EMFolderTreeModelStoreInfo *si; + GtkTreeRowReference *row; + GtkTreePath *path; + GtkTreeIter iter; - tmp = g_strndup (selection->data, selection->length); - urls = g_strsplit (tmp, "\n", 0); - g_free (tmp); - - for (i = 0; urls[i] != NULL; i++) { - /* get the path component */ - url = g_strstrip (urls[i]); - uri = camel_url_new (url, NULL); - g_free (url); - - if (!uri || strcmp (uri->protocol, "file") != 0) { - camel_url_free (uri); - continue; - } - - url = uri->path; - uri->path = NULL; - camel_url_free (uri); - - if ((fd = open (url, O_RDONLY)) == -1) { - g_free (url); - continue; - } - - stream = camel_stream_fs_new_with_fd (fd); - if (!import_message_rfc822 (dest, stream, TRUE, ex)) { - /* FIXME: should we abort now? or continue? */ - /* for now lets just continue... */ - camel_exception_clear (ex); - } - - camel_object_unref (stream); - g_free (url); - } - - g_free (urls); -} - - -static gboolean -model_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection) -{ - EMFolderTreeModel *model = (EMFolderTreeModel *) drag_dest; - const char *full_name; - CamelFolder *folder; - CamelStore *store; - CamelException ex; - GtkTreeIter iter; - char *path; - - d(printf ("model_drag_data_received\n")); - - /* this means we are receiving no data */ - if (!selection->data || selection->length == -1) - return FALSE; - - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, dest_path)) { - d(printf ("\tfailed to get row\n")); - return FALSE; - } + if (!(si = g_hash_table_lookup (model->store_hash, store))) + goto done; - gtk_tree_model_get ((GtkTreeModel *) model, &iter, - COL_POINTER_CAMEL_STORE, &store, - COL_STRING_FOLDER_PATH, &path, -1); + if (!(row = g_hash_table_lookup (si->path_hash, fi->path))) + goto done; - /* make sure user isn't try to drop on a placeholder row */ - if (path == NULL) { - d(printf ("\tdropped on a placeholder row?\n")); - return FALSE; + path = gtk_tree_row_reference_get_path (row); + if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) { + gtk_tree_path_free (path); + goto done; } - full_name = path[0] == '/' ? path + 1 : path; - - camel_exception_init (&ex); - if ((folder = camel_store_get_folder (store, full_name, 0, &ex))) { - /* FIXME: would have been nicer if we could 'move' - * messages and/or folders. but alas, gtktreeview - * drag&drop doesn't give us the context->action to - * check for GDK_ACTION_MOVE, so we can't. Yay. */ - gboolean move = FALSE; - - if (selection->target == gdk_atom_intern ("x-uid-list", FALSE)) { - /* import a list of uids from another evo folder */ - drop_uid_list (folder, move, selection, &ex); - d(printf ("\t* dropped a x-uid-list\n")); - } else if (selection->target == gdk_atom_intern ("x-folder", FALSE)) { - /* copy or move (aka rename) a folder */ - drop_folder (folder, move, selection, &ex); - d(printf ("\t* dropped a x-folder\n")); - } else if (selection->target == gdk_atom_intern ("message/rfc822", FALSE)) { - /* import a message/rfc822 stream */ - drop_message_rfc822 (folder, selection, &ex); - d(printf ("\t* dropped a message/rfc822\n")); - } else if (selection->target == gdk_atom_intern ("text/uri-list", FALSE)) { - /* import an mbox, maildir, or mh folder? */ - drop_text_uri_list (folder, selection, &ex); - d(printf ("\t* dropped a text/uri-list\n")); - } else { - g_assert_not_reached (); - } - } + em_folder_tree_model_remove_folders (model, si, &iter); - if (camel_exception_is_set (&ex)) { - /* FIXME: error dialog? */ - camel_exception_clear (&ex); - return FALSE; - } + done: - return TRUE; + camel_object_unref (store); + camel_folder_info_free (fi); } -static gboolean -model_row_drop_possible (GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection) +static void +folder_unsubscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) { - EMFolderTreeModel *model = (EMFolderTreeModel *) drag_dest; - gboolean is_store; - GtkTreeIter iter; - - d(printf ("model_row_drop_possible\n")); + CamelFolderInfo *fi; - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, dest_path)) - return FALSE; + camel_object_ref (store); + fi = camel_folder_info_clone (event_data); + mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed, store, fi, model); +} + +static void +folder_created_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) +{ + CamelFolderInfo *fi; - gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_BOOL_IS_STORE, &is_store, -1); + /* we only want created events to do more work if we don't support subscriptions */ + if (camel_store_supports_subscriptions (store)) + return; - if (selection->target == gdk_atom_intern ("x-uid-list", FALSE)) { - if (is_store) { - d(printf ("\tcan't drop x-uid-list on a store\n")); - return FALSE; - } - - d(printf ("\tcan drop x-uid-list\n")); - return TRUE; - } else if (selection->target == gdk_atom_intern ("x-folder", FALSE)) { - d(printf ("\tcan drop x-folder\n")); - return TRUE; - } else if (selection->target == gdk_atom_intern ("message/rfc822", FALSE)) { - if (is_store) { - d(printf ("\tcan't drop message/rfc822 on a store\n")); - return FALSE; - } - - d(printf ("\tcan drop message/rfc822\n")); - return TRUE; - } else if (selection->target == gdk_atom_intern ("text/uri-list", FALSE)) { - if (is_store) { - d(printf ("\tcan't drop text/uri-list on a store\n")); - return FALSE; - } - - d(printf ("\tcan drop text/uri-list\n")); - return TRUE; - } else { - g_assert_not_reached (); - return FALSE; - } + camel_object_ref (store); + fi = camel_folder_info_clone (event_data); + mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed_cb, store, fi, model); } -static gboolean -model_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *src_path) +static void +folder_deleted_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) { - EMFolderTreeModel *model = (EMFolderTreeModel *) drag_source; - gboolean is_store; - GtkTreeIter iter; - - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, src_path)) - return FALSE; + CamelFolderInfo *fi; - gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_BOOL_IS_STORE, &is_store, -1); + /* we only want deleted events to do more work if we don't support subscriptions */ + if (camel_store_supports_subscriptions (store)) + return; - return !is_store; + camel_object_ref (store); + fi = camel_folder_info_clone (event_data); + mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed_cb, store, fi, model); } static void -drag_text_uri_list (CamelFolder *src, GtkSelectionData *selection, CamelException *ex) +folder_renamed (CamelStore *store, CamelRenameInfo *info, EMFolderTreeModel *model) { - CamelFolder *dest; - const char *tmpdir; - CamelStore *store; - GPtrArray *uids; - GString *url; + struct _EMFolderTreeModelStoreInfo *si; + GtkTreeRowReference *row; + GtkTreeIter root, iter; + GtkTreePath *path; + char *parent, *p; - if (!(tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX"))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not create temporary directory: %s"), - g_strerror (errno)); - return; - } + if (!(si = g_hash_table_lookup (model->store_hash, store))) + goto done; - url = g_string_new ("mbox:"); - g_string_append (url, tmpdir); - if (!(store = camel_session_get_store (session, url->str, ex))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not create temporary mbox store: %s"), - camel_exception_get_description (ex)); - g_string_free (url, TRUE); - - return; + parent = g_strdup_printf ("/%s", info->old_base); + if (!(row = g_hash_table_lookup (si->path_hash, parent))) { + g_free (parent); + goto done; } + g_free (parent); - if (!(dest = camel_store_get_folder (store, "mbox", CAMEL_STORE_FOLDER_CREATE, ex))) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not create temporary mbox folder: %s"), - camel_exception_get_description (ex)); - - camel_object_unref (store); - g_string_free (url, TRUE); - - return; + path = gtk_tree_row_reference_get_path (row); + if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) { + gtk_tree_path_free (path); + goto done; } - camel_object_unref (store); - uids = camel_folder_get_uids (src); + em_folder_tree_model_remove_folders (model, si, &iter); - camel_folder_transfer_messages_to (src, uids, dest, NULL, FALSE, ex); - if (camel_exception_is_set (ex)) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not copy messages to temporary mbox folder: %s"), - camel_exception_get_description (ex)); + parent = g_strdup (info->new->path); + if ((p = strrchr (parent + 1, '/'))) + *p = '\0'; + + if (!strcmp (parent, "/")) { + /* renamed to a toplevel folder on the store */ + path = gtk_tree_row_reference_get_path (si->row); } else { - /* replace "mbox:" with "file:" */ - memcpy (url->str, "file", 4); - g_string_append (url, "\r\n"); - gtk_selection_data_set (selection, selection->target, 8, url->str, url->len); + if (!(row = g_hash_table_lookup (si->path_hash, parent))) { + /* NOTE: this should never happen, but I + * suppose if it does in reality, we can add + * code here to add the missing nodes to the + * tree */ + g_assert_not_reached (); + g_free (parent); + goto done; + } + + path = gtk_tree_row_reference_get_path (row); } - camel_folder_free_uids (src, uids); - camel_object_unref (dest); - g_string_free (url, TRUE); -} - -static gboolean -model_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *src_path, GtkSelectionData *selection) -{ - EMFolderTreeModel *model = (EMFolderTreeModel *) drag_source; - const char *full_name; - CamelFolder *folder; - CamelStore *store; - CamelException ex; - GtkTreeIter iter; - char *path, *uri; + g_free (parent); - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, src_path)) - return FALSE; + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &root, path)) { + gtk_tree_path_free (path); + g_assert_not_reached (); + goto done; + } - gtk_tree_model_get ((GtkTreeModel *) model, &iter, - COL_POINTER_CAMEL_STORE, &store, - COL_STRING_FOLDER_PATH, &path, - COL_STRING_URI, &uri, -1); + gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root); + em_folder_tree_model_set_folder_info (model, &iter, si, info->new); - /* make sure user isn't try to drag on a placeholder row */ - if (path == NULL) - return FALSE; + done: - full_name = path[0] == '/' ? path + 1 : path; + camel_object_unref (store); - camel_exception_init (&ex); + g_free (info->old_base); + camel_folder_info_free (info->new); + g_free (info); +} + +static void +folder_renamed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) +{ + CamelRenameInfo *rinfo, *info = event_data; - if (selection->target == gdk_atom_intern ("x-folder", FALSE)) { - /* dragging to a new location in the folder tree */ - gtk_selection_data_set (selection, selection->target, 8, uri, strlen (uri) + 1); - } else if (selection->target == gdk_atom_intern ("text/uri-list", FALSE)) { - /* dragging to nautilus or something, probably */ - if ((folder = camel_store_get_folder (store, full_name, 0, &ex))) { - drag_text_uri_list (folder, selection, &ex); - camel_object_unref (folder); - } - } else { - g_assert_not_reached (); - } + camel_object_ref (store); - if (camel_exception_is_set (&ex)) { - /* FIXME: error dialog? */ - camel_exception_clear (&ex); - return FALSE; - } + rinfo = g_new0 (CamelRenameInfo, 1); + rinfo->old_base = g_strdup (info->old_base); + rinfo->new = camel_folder_info_clone (info->new); - return TRUE; + mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_renamed, store, rinfo, model); } -static gboolean -model_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *src_path) +void +em_folder_tree_model_add_store (EMFolderTreeModel *model, CamelStore *store, const char *display_name) { - EMFolderTreeModel *model = (EMFolderTreeModel *) drag_source; - const char *full_name; - gboolean is_store; - CamelStore *store; - CamelException ex; - GtkTreeIter iter; - char *path; + struct _EMFolderTreeModelStoreInfo *si; + GtkTreeRowReference *row; + GtkTreeIter root, iter; + GtkTreePath *path; + char *uri; - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, src_path)) - return FALSE; + g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); + g_return_if_fail (CAMEL_IS_STORE (store)); + g_return_if_fail (display_name != NULL); - gtk_tree_model_get ((GtkTreeModel *) model, &iter, - COL_POINTER_CAMEL_STORE, &store, - COL_STRING_FOLDER_PATH, &path, - COL_BOOL_IS_STORE, &is_store, -1); + if ((si = g_hash_table_lookup (model->store_hash, store))) { + const char *name; + + path = gtk_tree_row_reference_get_path (si->row); + gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_STRING_DISPLAY_NAME, (char **) &name, -1); + + g_warning ("the store `%s' is already in the folder tree as `%s'", + display_name, name); + + return; + } - if (is_store) - return FALSE; + uri = camel_url_to_string (((CamelService *) store)->url, CAMEL_URL_HIDE_ALL); - full_name = path[0] == '/' ? path + 1 : path; + /* add the store to the tree */ + gtk_tree_store_append ((GtkTreeStore *) model, &iter, NULL); + gtk_tree_store_set ((GtkTreeStore *) model, &iter, + COL_STRING_DISPLAY_NAME, display_name, + COL_POINTER_CAMEL_STORE, store, + COL_STRING_FOLDER_PATH, "/", + COL_BOOL_LOAD_SUBDIRS, TRUE, + COL_BOOL_IS_STORE, TRUE, + COL_STRING_URI, uri, -1); - camel_exception_init (&ex); - camel_store_delete_folder (store, full_name, &ex); - if (camel_exception_is_set (&ex)) { - /* FIXME: error dialog? */ - camel_exception_clear (&ex); - return FALSE; - } + path = gtk_tree_model_get_path ((GtkTreeModel *) model, &iter); + row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path); + gtk_tree_path_free (path); - return TRUE; + si = g_new (struct _EMFolderTreeModelStoreInfo, 1); + si->display_name = g_strdup (display_name); + camel_object_ref (store); + si->store = store; + si->row = row; + si->path_hash = g_hash_table_new (g_str_hash, g_str_equal); + g_hash_table_insert (model->store_hash, store, si); + + /* each store has folders... but we don't load them until the user demands them */ + root = iter; + gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root); + gtk_tree_store_set ((GtkTreeStore *) model, &iter, + COL_STRING_DISPLAY_NAME, _("Loading..."), + COL_POINTER_CAMEL_STORE, NULL, + COL_STRING_FOLDER_PATH, NULL, + COL_BOOL_LOAD_SUBDIRS, FALSE, + COL_BOOL_IS_STORE, FALSE, + COL_STRING_URI, NULL, + COL_UINT_UNREAD, 0, + -1); + + g_free (uri); + + /* listen to store events */ +#define CAMEL_CALLBACK(func) ((CamelObjectEventHookFunc) func) + si->created_id = camel_object_hook_event (store, "folder_created", CAMEL_CALLBACK (folder_created_cb), model); + si->deleted_id = camel_object_hook_event (store, "folder_deleted", CAMEL_CALLBACK (folder_deleted_cb), model); + si->renamed_id = camel_object_hook_event (store, "folder_renamed", CAMEL_CALLBACK (folder_renamed_cb), model); + si->subscribed_id = camel_object_hook_event (store, "folder_subscribed", CAMEL_CALLBACK (folder_subscribed_cb), model); + si->unsubscribed_id = camel_object_hook_event (store, "folder_unsubscribed", CAMEL_CALLBACK (folder_unsubscribed_cb), model); } static void -em_folder_tree_model_load_state (EMFolderTreeModel *model, const char *filename) +em_folder_tree_model_remove_uri (EMFolderTreeModel *model, const char *uri) { - char *node; - FILE *fp; - - g_hash_table_foreach_remove (model->expanded, expanded_free, NULL); - - if ((fp = fopen (filename, "r")) == NULL) - return; + GtkTreeRowReference *row; - while (camel_file_util_decode_string (fp, &node) != -1) - g_hash_table_insert (model->expanded, node, GINT_TO_POINTER (TRUE)); + g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); + g_return_if_fail (uri != NULL); - fclose (fp); + if ((row = g_hash_table_lookup (model->uri_hash, uri))) { + g_hash_table_remove (model->uri_hash, uri); + gtk_tree_row_reference_free (row); + } } -EMFolderTreeModel * -em_folder_tree_model_new (const char *evolution_dir) +static void +em_folder_tree_model_remove_store_info (EMFolderTreeModel *model, CamelStore *store) { - EMFolderTreeModel *model; - char *filename; + struct _EMFolderTreeModelStoreInfo *si; - model = g_object_new (EM_TYPE_FOLDER_TREE_MODEL, NULL); - gtk_tree_store_set_column_types ((GtkTreeStore *) model, NUM_COLUMNS, col_types); - gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *) model, - GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, - GTK_SORT_ASCENDING); + g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); + g_return_if_fail (CAMEL_IS_STORE (store)); - filename = g_build_filename (evolution_dir, "mail", "config", "folder-tree.state", NULL); - em_folder_tree_model_load_state (model, filename); - model->filename = filename; + if (!(si = g_hash_table_lookup (model->store_hash, store))) + return; - return model; + g_hash_table_remove (model->store_hash, si->store); + store_info_free (si); } void -em_folder_tree_model_set_folder_info (EMFolderTreeModel *model, GtkTreeIter *iter, - struct _EMFolderTreeModelStoreInfo *si, - CamelFolderInfo *fi) +em_folder_tree_model_remove_folders (EMFolderTreeModel *model, struct _EMFolderTreeModelStoreInfo *si, GtkTreeIter *toplevel) { - GtkTreeRowReference *uri_row, *path_row; - unsigned int unread; - GtkTreePath *path; - GtkTreeIter sub; - gboolean load; - - load = fi->child == NULL && !(fi->flags & (CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_NOINFERIORS)); - - path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter); - uri_row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path); - path_row = gtk_tree_row_reference_copy (uri_row); - gtk_tree_path_free (path); - - g_hash_table_insert (model->uri_hash, g_strdup (fi->url), uri_row); - g_hash_table_insert (si->path_hash, g_strdup (fi->path), path_row); - - unread = fi->unread_message_count == -1 ? 0 : fi->unread_message_count; - - gtk_tree_store_set ((GtkTreeStore *) model, iter, - COL_STRING_DISPLAY_NAME, fi->name, - COL_POINTER_CAMEL_STORE, si->store, - COL_STRING_FOLDER_PATH, fi->path, - COL_STRING_URI, fi->url, - COL_UINT_UNREAD, unread, - COL_BOOL_IS_STORE, FALSE, - COL_BOOL_LOAD_SUBDIRS, load, - -1); + GtkTreeRowReference *row; + char *uri, *folder_path; + gboolean is_store, go; + GtkTreeIter iter; - if (fi->child) { - fi = fi->child; - + if (gtk_tree_model_iter_children ((GtkTreeModel *) model, &iter, toplevel)) { do { - gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter); - em_folder_tree_model_set_folder_info (model, &sub, si, fi); - fi = fi->sibling; - } while (fi); - } else if (load) { - /* create a placeholder node for our subfolders... */ - gtk_tree_store_append ((GtkTreeStore *) model, &sub, iter); - gtk_tree_store_set ((GtkTreeStore *) model, &sub, - COL_STRING_DISPLAY_NAME, _("Loading..."), - COL_POINTER_CAMEL_STORE, NULL, - COL_STRING_FOLDER_PATH, NULL, - COL_BOOL_LOAD_SUBDIRS, FALSE, - COL_BOOL_IS_STORE, FALSE, - COL_STRING_URI, NULL, - COL_UINT_UNREAD, 0, - -1); - - path = gtk_tree_model_get_path ((GtkTreeModel *) model, iter); - g_signal_emit (model, signals[LOADING_ROW], 0, path, iter); - gtk_tree_path_free (path); + GtkTreeIter next = iter; + + go = gtk_tree_model_iter_next ((GtkTreeModel *) model, &next); + em_folder_tree_model_remove_folders (model, si, &iter); + iter = next; + } while (go); + } + + gtk_tree_model_get ((GtkTreeModel *) model, toplevel, COL_STRING_URI, &uri, + COL_STRING_FOLDER_PATH, &folder_path, + COL_BOOL_IS_STORE, &is_store, -1); + + if (folder_path && (row = g_hash_table_lookup (si->path_hash, folder_path))) { + g_hash_table_remove (si->path_hash, folder_path); + gtk_tree_row_reference_free (row); } + + em_folder_tree_model_remove_uri (model, uri); + + gtk_tree_store_remove ((GtkTreeStore *) model, toplevel); + + if (is_store) + em_folder_tree_model_remove_store_info (model, si->store); } -static void -folder_subscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model) +void +em_folder_tree_model_remove_store (EMFolderTreeModel *model, CamelStore *store) { struct _EMFolderTreeModelStoreInfo *si; - GtkTreeRowReference *row; - GtkTreeIter parent, iter; GtkTreePath *path; - gboolean load; - char *dirname; - - if (!(si = g_hash_table_lookup (model->store_hash, store))) - goto done; - - /* make sure we don't already know about it? */ - if (g_hash_table_lookup (si->path_hash, fi->path)) - goto done; + GtkTreeIter iter; - /* get our parent folder's path */ - if (!(dirname = g_path_get_dirname (fi->path))) - goto done; + g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); + g_return_if_fail (CAMEL_IS_STORE (store)); - if (!strcmp (dirname, "/")) { - /* user subscribed to a toplevel folder */ - row = si->row; - g_free (dirname); - } else { - row = g_hash_table_lookup (si->path_hash, dirname); - g_free (dirname); + if (!(si = g_hash_table_lookup (model->store_hash, store))) { + g_warning ("the store `%s' is not in the folder tree", si->display_name); - /* if row is NULL, don't bother adding to the tree, - * when the user expands enough nodes - it will be - * added auto-magically */ - if (row == NULL) - goto done; - } - - path = gtk_tree_row_reference_get_path (row); - if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &parent, path))) { - gtk_tree_path_free (path); - goto done; + return; } + path = gtk_tree_row_reference_get_path (si->row); + gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path); gtk_tree_path_free (path); - /* make sure parent's subfolders have already been loaded */ - gtk_tree_model_get ((GtkTreeModel *) model, &parent, COL_BOOL_LOAD_SUBDIRS, &load, -1); - if (load) - goto done; - - /* append a new node */ - gtk_tree_store_append ((GtkTreeStore *) model, &iter, &parent); - - em_folder_tree_model_set_folder_info (model, &iter, si, fi); - - done: - - camel_object_unref (store); - camel_folder_info_free (fi); + /* recursively remove subfolders and finally the toplevel store */ + em_folder_tree_model_remove_folders (model, si, &iter); } -static void -folder_subscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) + +gboolean +em_folder_tree_model_get_expanded (EMFolderTreeModel *model, const char *key) { - CamelFolderInfo *fi; + if (g_hash_table_lookup (model->expanded, key)) + return TRUE; - camel_object_ref (store); - fi = camel_folder_info_clone (event_data); - mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed, store, fi, model); + return FALSE; } -static void -folder_unsubscribed (CamelStore *store, CamelFolderInfo *fi, EMFolderTreeModel *model) + +void +em_folder_tree_model_set_expanded (EMFolderTreeModel *model, const char *key, gboolean expanded) { - struct _EMFolderTreeModelStoreInfo *si; - GtkTreeRowReference *row; - GtkTreePath *path; - GtkTreeIter iter; - - if (!(si = g_hash_table_lookup (model->store_hash, store))) - goto done; - - if (!(row = g_hash_table_lookup (si->path_hash, fi->path))) - goto done; + gpointer okey, oval; - path = gtk_tree_row_reference_get_path (row); - if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) { - gtk_tree_path_free (path); - goto done; + if (g_hash_table_lookup_extended (model->expanded, key, &okey, &oval)) { + g_hash_table_remove (model->expanded, okey); + g_free (okey); } - em_folder_tree_model_remove_folders (model, si, &iter); - - done: - - camel_object_unref (store); - camel_folder_info_free (fi); + if (expanded) + g_hash_table_insert (model->expanded, g_strdup (key), GINT_TO_POINTER (TRUE)); } + static void -folder_unsubscribed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) +expanded_save (gpointer key, gpointer value, FILE *fp) { - CamelFolderInfo *fi; + /* FIXME: don't save stale entries */ + if (!GPOINTER_TO_INT (value)) + return; - camel_object_ref (store); - fi = camel_folder_info_clone (event_data); - mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed, store, fi, model); + camel_file_util_encode_string (fp, key); } -static void -folder_created_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) +void +em_folder_tree_model_save_expanded (EMFolderTreeModel *model) { - CamelFolderInfo *fi; + char *dirname, *tmpname; + FILE *fp; + int fd; - /* we only want created events to do more work if we don't support subscriptions */ - if (camel_store_supports_subscriptions (store)) + dirname = g_path_get_dirname (model->filename); + if (camel_mkdir (dirname, 0777) == -1 && errno != EEXIST) { + g_free (dirname); return; + } - camel_object_ref (store); - fi = camel_folder_info_clone (event_data); - mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_subscribed_cb, store, fi, model); -} - -static void -folder_deleted_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) -{ - CamelFolderInfo *fi; + g_free (dirname); + tmpname = g_strdup_printf ("%s~", model->filename); - /* we only want deleted events to do more work if we don't support subscriptions */ - if (camel_store_supports_subscriptions (store)) + if (!(fp = fopen (tmpname, "w+"))) { + g_free (tmpname); return; + } - camel_object_ref (store); - fi = camel_folder_info_clone (event_data); - mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_unsubscribed_cb, store, fi, model); + g_hash_table_foreach (model->expanded, (GHFunc) expanded_save, fp); + + if (fflush (fp) != 0) + goto exception; + + if ((fd = fileno (fp)) == -1) + goto exception; + + if (fsync (fd) == -1) + goto exception; + + fclose (fp); + fp = NULL; + + if (rename (tmpname, model->filename) == -1) + goto exception; + + g_free (tmpname); + + return; + + exception: + + if (fp != NULL) + fclose (fp); + + unlink (tmpname); + g_free (tmpname); } -static void -folder_renamed (CamelStore *store, CamelRenameInfo *info, EMFolderTreeModel *model) + +void +em_folder_tree_model_set_unread_count (EMFolderTreeModel *model, CamelStore *store, const char *path, int unread) { struct _EMFolderTreeModelStoreInfo *si; GtkTreeRowReference *row; - GtkTreeIter root, iter; - GtkTreePath *path; - char *parent, *p; + GtkTreePath *tree_path; + GtkTreeIter iter; + + g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); + g_return_if_fail (CAMEL_IS_STORE (store)); + g_return_if_fail (path != NULL); + + if (unread < 0) + unread = 0; if (!(si = g_hash_table_lookup (model->store_hash, store))) - goto done; + return; - parent = g_strdup_printf ("/%s", info->old_base); - if (!(row = g_hash_table_lookup (si->path_hash, parent))) { - g_free (parent); - goto done; + if (!(row = g_hash_table_lookup (si->path_hash, path))) { + printf ("can't set unread count for '%s' - path unknown.\n", path); + return; } - g_free (parent); - path = gtk_tree_row_reference_get_path (row); - if (!(gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path))) { - gtk_tree_path_free (path); - goto done; + tree_path = gtk_tree_row_reference_get_path (row); + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, tree_path)) { + gtk_tree_path_free (tree_path); + return; } - em_folder_tree_model_remove_folders (model, si, &iter); - - parent = g_strdup (info->new->path); - if ((p = strrchr (parent + 1, '/'))) - *p = '\0'; + gtk_tree_path_free (tree_path); - if (!strcmp (parent, "/")) { - /* renamed to a toplevel folder on the store */ - path = gtk_tree_row_reference_get_path (si->row); - } else { - if (!(row = g_hash_table_lookup (si->path_hash, parent))) { - /* NOTE: this should never happen, but I - * suppose if it does in reality, we can add - * code here to add the missing nodes to the - * tree */ - g_assert_not_reached (); - g_free (parent); - goto done; - } - - path = gtk_tree_row_reference_get_path (row); - } + gtk_tree_store_set ((GtkTreeStore *) model, &iter, COL_UINT_UNREAD, unread, -1); +} + + +/* Drag & Drop methods */ +static void +drop_uid_list (CamelFolder *dest, GtkSelectionData *selection, gboolean move, CamelException *ex) +{ + CamelFolder *src; + GPtrArray *uids; + char *src_uri; - g_free (parent); + em_utils_selection_get_uidlist (selection, &src_uri, &uids); - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &root, path)) { - gtk_tree_path_free (path); - g_assert_not_reached (); - goto done; + if (!(src = mail_tool_uri_to_folder (src_uri, 0, ex))) { + em_utils_uids_free (uids); + g_free (src_uri); + return; } - gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root); - em_folder_tree_model_set_folder_info (model, &iter, si, info->new); - - done: - - camel_object_unref (store); + g_free (src_uri); - g_free (info->old_base); - camel_folder_info_free (info->new); - g_free (info); + camel_folder_transfer_messages_to (src, uids, dest, NULL, move, ex); + em_utils_uids_free (uids); + camel_object_unref (src); } static void -folder_renamed_cb (CamelStore *store, void *event_data, EMFolderTreeModel *model) +drop_folder (CamelFolder *dest, GtkSelectionData *selection, gboolean move, CamelException *ex) { - CamelRenameInfo *rinfo, *info = event_data; + CamelFolder *src; - camel_object_ref (store); + /* get the folder being dragged */ + if (!(src = mail_tool_uri_to_folder (selection->data, 0, ex))) + return; - rinfo = g_new0 (CamelRenameInfo, 1); - rinfo->old_base = g_strdup (info->old_base); - rinfo->new = camel_folder_info_clone (info->new); + if (src->parent_store == dest->parent_store && move) { + /* simple rename() action */ + char *old_name, *new_name; + + old_name = g_strdup (src->full_name); + new_name = g_strdup_printf ("%s/%s", dest->full_name, src->name); + + camel_store_rename_folder (dest->parent_store, old_name, new_name, ex); + + g_free (old_name); + g_free (new_name); + } else { + /* copy the folder to the new location */ + CamelFolder *folder; + char *path; + + path = g_strdup_printf ("%s/%s", dest->full_name, src->name); + if ((folder = camel_store_get_folder (dest->parent_store, path, CAMEL_STORE_FOLDER_CREATE, ex))) { + GPtrArray *uids; + + uids = camel_folder_get_uids (src); + camel_folder_transfer_messages_to (src, uids, folder, NULL, FALSE, ex); + camel_folder_free_uids (src, uids); + + camel_object_unref (folder); + } + + g_free (path); + } - mail_async_event_emit (mail_async_event, MAIL_ASYNC_GUI, (MailAsyncFunc) folder_renamed, store, rinfo, model); + camel_object_unref (src); } -void -em_folder_tree_model_add_store (EMFolderTreeModel *model, CamelStore *store, const char *display_name) +static gboolean +import_message_rfc822 (CamelFolder *dest, CamelStream *stream, gboolean scan_from, CamelException *ex) { - struct _EMFolderTreeModelStoreInfo *si; - GtkTreeRowReference *row; - GtkTreeIter root, iter; - GtkTreePath *path; - char *uri; + CamelMimeParser *mp; - g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); - g_return_if_fail (CAMEL_IS_STORE (store)); - g_return_if_fail (display_name != NULL); + mp = camel_mime_parser_new (); + camel_mime_parser_scan_from (mp, scan_from); + camel_mime_parser_init_with_stream (mp, stream); - if ((si = g_hash_table_lookup (model->store_hash, store))) { - const char *name; + while (camel_mime_parser_step (mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM) { + CamelMessageInfo *info; + CamelMimeMessage *msg; - path = gtk_tree_row_reference_get_path (si->row); - gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path); - gtk_tree_path_free (path); + msg = camel_mime_message_new (); + if (camel_mime_part_construct_from_parser (CAMEL_MIME_PART (msg), mp) == -1) { + camel_object_unref (msg); + camel_object_unref (mp); + return FALSE; + } - gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_STRING_DISPLAY_NAME, (char **) &name, -1); + /* append the message to the folder... */ + info = g_new0 (CamelMessageInfo, 1); + camel_folder_append_message (dest, msg, info, NULL, ex); + camel_object_unref (msg); - g_warning ("the store `%s' is already in the folder tree as `%s'", - display_name, name); + if (camel_exception_is_set (ex)) { + camel_object_unref (mp); + return FALSE; + } - return; + /* skip over the FROM_END state */ + camel_mime_parser_step (mp, 0, 0); } - uri = camel_url_to_string (((CamelService *) store)->url, CAMEL_URL_HIDE_ALL); - - /* add the store to the tree */ - gtk_tree_store_append ((GtkTreeStore *) model, &iter, NULL); - gtk_tree_store_set ((GtkTreeStore *) model, &iter, - COL_STRING_DISPLAY_NAME, display_name, - COL_POINTER_CAMEL_STORE, store, - COL_STRING_FOLDER_PATH, "/", - COL_BOOL_LOAD_SUBDIRS, TRUE, - COL_BOOL_IS_STORE, TRUE, - COL_STRING_URI, uri, -1); - - path = gtk_tree_model_get_path ((GtkTreeModel *) model, &iter); - row = gtk_tree_row_reference_new ((GtkTreeModel *) model, path); - gtk_tree_path_free (path); + camel_object_unref (mp); - si = g_new (struct _EMFolderTreeModelStoreInfo, 1); - si->display_name = g_strdup (display_name); - camel_object_ref (store); - si->store = store; - si->row = row; - si->path_hash = g_hash_table_new (g_str_hash, g_str_equal); - g_hash_table_insert (model->store_hash, store, si); + return TRUE; +} + +static void +drop_message_rfc822 (CamelFolder *dest, GtkSelectionData *selection, CamelException *ex) +{ + CamelStream *stream; + gboolean scan_from; - /* each store has folders... but we don't load them until the user demands them */ - root = iter; - gtk_tree_store_append ((GtkTreeStore *) model, &iter, &root); - gtk_tree_store_set ((GtkTreeStore *) model, &iter, - COL_STRING_DISPLAY_NAME, _("Loading..."), - COL_POINTER_CAMEL_STORE, NULL, - COL_STRING_FOLDER_PATH, NULL, - COL_BOOL_LOAD_SUBDIRS, FALSE, - COL_BOOL_IS_STORE, FALSE, - COL_STRING_URI, NULL, - COL_UINT_UNREAD, 0, - -1); + scan_from = selection->length > 5 && !strncmp (selection->data, "From ", 5); + stream = camel_stream_mem_new_with_buffer (selection->data, selection->length); - g_free (uri); + import_message_rfc822 (dest, stream, scan_from, ex); - /* listen to store events */ -#define CAMEL_CALLBACK(func) ((CamelObjectEventHookFunc) func) - si->created_id = camel_object_hook_event (store, "folder_created", CAMEL_CALLBACK (folder_created_cb), model); - si->deleted_id = camel_object_hook_event (store, "folder_deleted", CAMEL_CALLBACK (folder_deleted_cb), model); - si->renamed_id = camel_object_hook_event (store, "folder_renamed", CAMEL_CALLBACK (folder_renamed_cb), model); - si->subscribed_id = camel_object_hook_event (store, "folder_subscribed", CAMEL_CALLBACK (folder_subscribed_cb), model); - si->unsubscribed_id = camel_object_hook_event (store, "folder_unsubscribed", CAMEL_CALLBACK (folder_unsubscribed_cb), model); + camel_object_unref (stream); } - static void -em_folder_tree_model_remove_uri (EMFolderTreeModel *model, const char *uri) +drop_text_uri_list (CamelFolder *dest, GtkSelectionData *selection, CamelException *ex) { - GtkTreeRowReference *row; + char **urls, *url, *tmp; + CamelStream *stream; + CamelURL *uri; + int fd, i; - g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); - g_return_if_fail (uri != NULL); + tmp = g_strndup (selection->data, selection->length); + urls = g_strsplit (tmp, "\n", 0); + g_free (tmp); - if ((row = g_hash_table_lookup (model->uri_hash, uri))) { - g_hash_table_remove (model->uri_hash, uri); - gtk_tree_row_reference_free (row); + for (i = 0; urls[i] != NULL; i++) { + /* get the path component */ + url = g_strstrip (urls[i]); + uri = camel_url_new (url, NULL); + g_free (url); + + if (!uri || strcmp (uri->protocol, "file") != 0) { + camel_url_free (uri); + continue; + } + + url = uri->path; + uri->path = NULL; + camel_url_free (uri); + + if ((fd = open (url, O_RDONLY)) == -1) { + g_free (url); + continue; + } + + stream = camel_stream_fs_new_with_fd (fd); + if (!import_message_rfc822 (dest, stream, TRUE, ex)) { + /* FIXME: should we abort now? or continue? */ + /* for now lets just continue... */ + camel_exception_clear (ex); + } + + camel_object_unref (stream); + g_free (url); } + + g_free (urls); } -static void -em_folder_tree_model_remove_store_info (EMFolderTreeModel *model, CamelStore *store) +gboolean +em_folder_tree_model_drag_data_received (EMFolderTreeModel *model, GtkTreePath *dest_path, GtkSelectionData *selection, + guint info, gboolean move, gboolean *moved) { - struct _EMFolderTreeModelStoreInfo *si; + const char *full_name; + CamelFolder *folder; + CamelStore *store; + CamelException ex; + GtkTreeIter iter; + char *path; - g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); - g_return_if_fail (CAMEL_IS_STORE (store)); + *moved = FALSE; + + /* this means we are receiving no data */ + if (!selection->data || selection->length == -1) + return FALSE; + + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, dest_path)) { + d(printf ("\tfailed to get row\n")); + return FALSE; + } + + gtk_tree_model_get ((GtkTreeModel *) model, &iter, + COL_POINTER_CAMEL_STORE, &store, + COL_STRING_FOLDER_PATH, &path, -1); + + /* make sure user isn't try to drop on a placeholder row */ + if (path == NULL) { + d(printf ("\tdropped on a placeholder row?\n")); + return FALSE; + } + + full_name = path[0] == '/' ? path + 1 : path; + + camel_exception_init (&ex); + if ((folder = camel_store_get_folder (store, full_name, 0, &ex))) { + switch (info) { + case DND_DROP_TYPE_UID_LIST: + /* import a list of uids from another evo folder */ + drop_uid_list (folder, selection, move, &ex); + *moved = move; + d(printf ("\t* dropped a x-uid-list\n")); + break; + case DND_DROP_TYPE_FOLDER: + /* copy or move (aka rename) a folder */ + drop_folder (folder, selection, move, &ex); + *moved = move; + d(printf ("\t* dropped a x-folder\n")); + break; + case DND_DROP_TYPE_MESSAGE_RFC822: + /* import a message/rfc822 stream */ + drop_message_rfc822 (folder, selection, &ex); + d(printf ("\t* dropped a message/rfc822\n")); + break; + case DND_DROP_TYPE_TEXT_URI_LIST: + /* import an mbox, maildir, or mh folder? */ + drop_text_uri_list (folder, selection, &ex); + d(printf ("\t* dropped a text/uri-list\n")); + break; + default: + g_assert_not_reached (); + } + } - if (!(si = g_hash_table_lookup (model->store_hash, store))) - return; + if (camel_exception_is_set (&ex)) { + camel_exception_clear (&ex); + *moved = FALSE; + return FALSE; + } - g_hash_table_remove (model->store_hash, si->store); - store_info_free (si); + return TRUE; } -void -em_folder_tree_model_remove_folders (EMFolderTreeModel *model, struct _EMFolderTreeModelStoreInfo *si, GtkTreeIter *toplevel) +GdkDragAction +em_folder_tree_model_row_drop_possible (EMFolderTreeModel *model, GtkTreePath *path, GList *targets) { - GtkTreeRowReference *row; - char *uri, *folder_path; - gboolean is_store, go; - GtkTreeIter iter; - - if (gtk_tree_model_iter_children ((GtkTreeModel *) model, &iter, toplevel)) { - do { - GtkTreeIter next = iter; - - go = gtk_tree_model_iter_next ((GtkTreeModel *) model, &next); - em_folder_tree_model_remove_folders (model, si, &iter); - iter = next; - } while (go); - } - - gtk_tree_model_get ((GtkTreeModel *) model, toplevel, COL_STRING_URI, &uri, - COL_STRING_FOLDER_PATH, &folder_path, - COL_BOOL_IS_STORE, &is_store, -1); - - if (folder_path && (row = g_hash_table_lookup (si->path_hash, folder_path))) { - g_hash_table_remove (si->path_hash, folder_path); - gtk_tree_row_reference_free (row); + GdkAtom target; + int i; + + target = em_folder_tree_model_row_drop_target (model, path, targets); + if (target == GDK_NONE) + return 0; + + for (i = 0; i < NUM_DROP_TYPES; i++) { + if (drop_atoms[i] == target) { + switch (i) { + case DND_DROP_TYPE_FOLDER: + return GDK_ACTION_MOVE; + default: + return GDK_ACTION_COPY; + } + } } - em_folder_tree_model_remove_uri (model, uri); - - gtk_tree_store_remove ((GtkTreeStore *) model, toplevel); - - if (is_store) - em_folder_tree_model_remove_store_info (model, si->store); + return 0; } -void -em_folder_tree_model_remove_store (EMFolderTreeModel *model, CamelStore *store) +GdkAtom +em_folder_tree_model_row_drop_target (EMFolderTreeModel *model, GtkTreePath *path, GList *targets) { - struct _EMFolderTreeModelStoreInfo *si; - GtkTreePath *path; + gboolean is_store; GtkTreeIter iter; - g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); - g_return_if_fail (CAMEL_IS_STORE (store)); + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path)) + return GDK_NONE; - if (!(si = g_hash_table_lookup (model->store_hash, store))) { - g_warning ("the store `%s' is not in the folder tree", si->display_name); + gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_BOOL_IS_STORE, &is_store, -1); + + if (is_store) { + /* can only drop x-folder into a store */ + GdkAtom xfolder; - return; + xfolder = drop_atoms[DND_DROP_TYPE_FOLDER]; + while (targets != NULL) { + if (targets->data == (gpointer) xfolder) + return xfolder; + + targets = targets->next; + } + } else { + /* can drop anything into a folder */ + 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; + } } - path = gtk_tree_row_reference_get_path (si->row); - gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path); - gtk_tree_path_free (path); - - /* recursively remove subfolders and finally the toplevel store */ - em_folder_tree_model_remove_folders (model, si, &iter); + return GDK_NONE; } gboolean -em_folder_tree_model_get_expanded (EMFolderTreeModel *model, const char *key) +em_folder_tree_model_row_draggable (EMFolderTreeModel *model, GtkTreePath *path) { - if (g_hash_table_lookup (model->expanded, key)) - return TRUE; + gboolean is_store; + GtkTreeIter iter; - return FALSE; -} - - -void -em_folder_tree_model_set_expanded (EMFolderTreeModel *model, const char *key, gboolean expanded) -{ - gpointer okey, oval; + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, path)) + return FALSE; - if (g_hash_table_lookup_extended (model->expanded, key, &okey, &oval)) { - g_hash_table_remove (model->expanded, okey); - g_free (okey); - } + gtk_tree_model_get ((GtkTreeModel *) model, &iter, COL_BOOL_IS_STORE, &is_store, -1); - if (expanded) - g_hash_table_insert (model->expanded, g_strdup (key), GINT_TO_POINTER (TRUE)); + return !is_store; } static void -expanded_save (gpointer key, gpointer value, FILE *fp) -{ - /* FIXME: don't save stale entries */ - if (!GPOINTER_TO_INT (value)) - return; - - camel_file_util_encode_string (fp, key); -} - -void -em_folder_tree_model_save_expanded (EMFolderTreeModel *model) +drag_text_uri_list (CamelFolder *src, GtkSelectionData *selection, CamelException *ex) { - char *dirname, *tmpname; - FILE *fp; - int fd; + CamelFolder *dest; + const char *tmpdir; + CamelStore *store; + GPtrArray *uids; + GString *url; - dirname = g_path_get_dirname (model->filename); - if (camel_mkdir (dirname, 0777) == -1 && errno != EEXIST) { - g_free (dirname); + if (!(tmpdir = e_mkdtemp ("drag-n-drop-XXXXXX"))) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Could not create temporary directory: %s"), + g_strerror (errno)); return; } - g_free (dirname); - tmpname = g_strdup_printf ("%s~", model->filename); + url = g_string_new ("mbox:"); + g_string_append (url, tmpdir); + if (!(store = camel_session_get_store (session, url->str, ex))) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Could not create temporary mbox store: %s"), + camel_exception_get_description (ex)); + g_string_free (url, TRUE); + + return; + } - if (!(fp = fopen (tmpname, "w+"))) { - g_free (tmpname); + if (!(dest = camel_store_get_folder (store, "mbox", CAMEL_STORE_FOLDER_CREATE, ex))) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Could not create temporary mbox folder: %s"), + camel_exception_get_description (ex)); + + camel_object_unref (store); + g_string_free (url, TRUE); + return; } - g_hash_table_foreach (model->expanded, (GHFunc) expanded_save, fp); + camel_object_unref (store); + uids = camel_folder_get_uids (src); - if (fflush (fp) != 0) - goto exception; + camel_folder_transfer_messages_to (src, uids, dest, NULL, FALSE, ex); + if (camel_exception_is_set (ex)) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Could not copy messages to temporary mbox folder: %s"), + camel_exception_get_description (ex)); + } else { + /* replace "mbox:" with "file:" */ + memcpy (url->str, "file", 4); + g_string_append (url, "\r\n"); + gtk_selection_data_set (selection, selection->target, 8, url->str, url->len); + } - if ((fd = fileno (fp)) == -1) - goto exception; + camel_folder_free_uids (src, uids); + camel_object_unref (dest); + g_string_free (url, TRUE); +} + + +gboolean +em_folder_tree_model_drag_data_get (EMFolderTreeModel *model, GtkTreePath *src_path, GtkSelectionData *selection, guint info) +{ + const char *full_name; + CamelFolder *folder; + CamelStore *store; + CamelException ex; + GtkTreeIter iter; + char *path, *uri; - if (fsync (fd) == -1) - goto exception; + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, src_path)) + return FALSE; - fclose (fp); - fp = NULL; + gtk_tree_model_get ((GtkTreeModel *) model, &iter, + COL_POINTER_CAMEL_STORE, &store, + COL_STRING_FOLDER_PATH, &path, + COL_STRING_URI, &uri, -1); - if (rename (tmpname, model->filename) == -1) - goto exception; + /* make sure user isn't try to drag on a placeholder row */ + if (path == NULL) + return FALSE; - g_free (tmpname); + full_name = path[0] == '/' ? path + 1 : path; - return; + camel_exception_init (&ex); - exception: + switch (info) { + case DND_DRAG_TYPE_FOLDER: + /* dragging to a new location in the folder tree */ + gtk_selection_data_set (selection, selection->target, 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))) { + drag_text_uri_list (folder, selection, &ex); + camel_object_unref (folder); + } + break; + default: + g_assert_not_reached (); + } - if (fp != NULL) - fclose (fp); + if (camel_exception_is_set (&ex)) { + camel_exception_clear (&ex); + return FALSE; + } - unlink (tmpname); - g_free (tmpname); + return TRUE; } -void -em_folder_tree_model_set_unread_count (EMFolderTreeModel *model, CamelStore *store, const char *path, int unread) +gboolean +em_folder_tree_model_drag_data_delete (EMFolderTreeModel *model, GtkTreePath *src_path) { - struct _EMFolderTreeModelStoreInfo *si; - GtkTreeRowReference *row; - GtkTreePath *tree_path; + const char *full_name; + gboolean is_store; + CamelStore *store; + CamelException ex; GtkTreeIter iter; + char *path; - g_return_if_fail (EM_IS_FOLDER_TREE_MODEL (model)); - g_return_if_fail (CAMEL_IS_STORE (store)); - g_return_if_fail (path != NULL); + if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, src_path)) + return FALSE; - if (unread < 0) - unread = 0; + gtk_tree_model_get ((GtkTreeModel *) model, &iter, + COL_POINTER_CAMEL_STORE, &store, + COL_STRING_FOLDER_PATH, &path, + COL_BOOL_IS_STORE, &is_store, -1); - if (!(si = g_hash_table_lookup (model->store_hash, store))) - return; + if (is_store) + return FALSE; - if (!(row = g_hash_table_lookup (si->path_hash, path))) - return; + full_name = path[0] == '/' ? path + 1 : path; - tree_path = gtk_tree_row_reference_get_path (row); - if (!gtk_tree_model_get_iter ((GtkTreeModel *) model, &iter, tree_path)) { - gtk_tree_path_free (tree_path); - return; + camel_exception_init (&ex); + camel_store_delete_folder (store, full_name, &ex); + if (camel_exception_is_set (&ex)) { + camel_exception_clear (&ex); + return FALSE; } - gtk_tree_path_free (tree_path); - - gtk_tree_store_set ((GtkTreeStore *) model, &iter, COL_UINT_UNREAD, unread, -1); + return TRUE; +} + + +void +em_folder_tree_model_set_drag_drop_types (EMFolderTreeModel *model, GtkWidget *widget) +{ + gtk_drag_source_set (widget, GDK_BUTTON1_MASK, drag_types, NUM_DRAG_TYPES, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL, drop_types, + NUM_DROP_TYPES, GDK_ACTION_COPY | GDK_ACTION_MOVE); } + -- cgit