diff options
author | Peter Williams <peterw@ximian.com> | 2001-08-07 03:02:00 +0800 |
---|---|---|
committer | Peter Williams <peterw@src.gnome.org> | 2001-08-07 03:02:00 +0800 |
commit | d7562e99ae9fa1c7a2dc894087b580a9e503fa71 (patch) | |
tree | 4bfdab5bbe5dc2e23c3e6b96059ad4905f29ebf6 | |
parent | f789abbd422604206c386199c80fb5eac8e34733 (diff) | |
download | gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar.gz gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar.bz2 gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar.lz gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar.xz gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.tar.zst gsoc2013-evolution-d7562e99ae9fa1c7a2dc894087b580a9e503fa71.zip |
Reimplement to be asynchronous and pretty, with progressive folder loading
2001-08-06 Peter Williams <peterw@ximian.com>
* subscribe-dialog.[ch]: Reimplement to be asynchronous and pretty,
with progressive folder loading and all sorts of wonderful
improvements.
* mail-callbacks.c (manage_subscriptions): Change to reflect API
update.
* component-factory.c (mail_load_storages): Don't add the storage
if it has been disabled.
* Makefile.am (etspec_DATA): Add subscribe-dialog.etspec.
(glade_DATA): And the glade file.
* subscribe-dialog.etspec: New file, break out the specification
from inside the subscribe-dialog.c
* subscribe-dialog.glade: Update this, actually use it now.
svn path=/trunk/; revision=11706
-rw-r--r-- | mail/ChangeLog | 20 | ||||
-rw-r--r-- | mail/Makefile.am | 4 | ||||
-rw-r--r-- | mail/mail-callbacks.c | 7 | ||||
-rw-r--r-- | mail/subscribe-dialog.c | 1860 | ||||
-rw-r--r-- | mail/subscribe-dialog.etspec | 9 | ||||
-rw-r--r-- | mail/subscribe-dialog.glade | 366 | ||||
-rw-r--r-- | mail/subscribe-dialog.h | 34 |
7 files changed, 1395 insertions, 905 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index 96af73c18a..96fc9d4e71 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,23 @@ +2001-08-06 Peter Williams <peterw@ximian.com> + + * subscribe-dialog.[ch]: Reimplement to be asynchronous and pretty, + with progressive folder loading and all sorts of wonderful + improvements. + + * mail-callbacks.c (manage_subscriptions): Change to reflect API + update. + + * component-factory.c (mail_load_storages): Don't add the storage + if it has been disabled. + + * Makefile.am (etspec_DATA): Add subscribe-dialog.etspec. + (glade_DATA): And the glade file. + + * subscribe-dialog.etspec: New file, break out the specification + from inside the subscribe-dialog.c + + * subscribe-dialog.glade: Update this, actually use it now. + 2001-08-06 Jeffrey Stedfast <fejj@ximian.com> * mail-callbacks.c (empty_trash): Yuck. Special case whether or diff --git a/mail/Makefile.am b/mail/Makefile.am index 156ceb55b8..ae84f4837f 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -134,10 +134,10 @@ oaf_DATA = $(oaf_in_files:.oaf.in=.oaf) @XML_I18N_MERGE_OAF_RULE@ gladedir = $(datadir)/evolution/glade -glade_DATA = mail-config.glade local-config.glade +glade_DATA = mail-config.glade local-config.glade subscribe-dialog.glade etspecdir = $(datadir)/evolution/etspec/ -etspec_DATA = message-list.etspec +etspec_DATA = message-list.etspec subscribe-dialog.etspec iconsdir = $(datadir)/images/evolution buttonsdir = $(datadir)/images/evolution/buttons diff --git a/mail/mail-callbacks.c b/mail/mail-callbacks.c index e1bb896ba5..b225c9bd98 100644 --- a/mail/mail-callbacks.c +++ b/mail/mail-callbacks.c @@ -1908,7 +1908,7 @@ print_preview_msg (GtkWidget *button, gpointer user_data) /******************** Begin Subscription Dialog ***************************/ -static GtkWidget *subscribe_dialog = NULL; +static GtkObject *subscribe_dialog = NULL; static void subscribe_dialog_destroy (GtkWidget *widget, gpointer user_data) @@ -1920,11 +1920,12 @@ void manage_subscriptions (BonoboUIComponent *uih, void *user_data, const char *path) { if (!subscribe_dialog) { - subscribe_dialog = subscribe_dialog_new ((FOLDER_BROWSER (user_data))->shell); + subscribe_dialog = subscribe_dialog_new (); gtk_signal_connect (GTK_OBJECT (subscribe_dialog), "destroy", subscribe_dialog_destroy, NULL); - gtk_widget_show (subscribe_dialog); + subscribe_dialog_run_and_close (SUBSCRIBE_DIALOG (subscribe_dialog)); + gtk_object_unref (subscribe_dialog); } else { /* FIXME: raise the subscription dialog window... */ } diff --git a/mail/subscribe-dialog.c b/mail/subscribe-dialog.c index b0b49805b9..24ef62f502 100644 --- a/mail/subscribe-dialog.c +++ b/mail/subscribe-dialog.c @@ -2,6 +2,7 @@ /* subscribe-dialog.c: Subscribe dialog */ /* * Authors: Chris Toshok <toshok@ximian.com> + * Peter Williams <peterw@ximian.com> * * Copyright 2000 Ximian, Inc. (www.ximian.com) * @@ -25,15 +26,6 @@ #include <config.h> #endif -#include <bonobo/bonobo-main.h> -#include <bonobo/bonobo-object.h> -#include <bonobo/bonobo-generic-factory.h> -#include <bonobo/bonobo-control.h> -#include <bonobo/bonobo-ui-component.h> -#include <bonobo/bonobo-ui-util.h> -#include <bonobo/bonobo-widget.h> - -#include <gtkhtml/gtkhtml.h> #include <gal/util/e-util.h> #include <gal/widgets/e-unicode.h> @@ -41,21 +33,16 @@ #include <gal/e-table/e-cell-text.h> #include <gal/e-table/e-cell-tree.h> -#include <gal/e-table/e-table-scrolled.h> -#include <gal/e-table/e-table-simple.h> -#include <gal/e-table/e-table.h> - #include <gal/e-table/e-tree-scrolled.h> #include <gal/e-table/e-tree-memory-callbacks.h> #include <gal/e-table/e-tree.h> -#include <gal/e-paned/e-hpaned.h> - -#include "e-util/e-html-utils.h" #include "evolution-shell-component-utils.h" #include "mail.h" #include "mail-tools.h" +#include "mail-ops.h" #include "mail-mt.h" +#include "mail-folder-cache.h" #include "camel/camel-exception.h" #include "camel/camel-store.h" #include "camel/camel-session.h" @@ -64,275 +51,278 @@ #include "art/empty.xpm" #include "art/mark.xpm" +/* Things to test. + * - Feature + * + How to check that it works. + * + * - Proper stores displayed + * + Skip stores that don't support subscriptions + * + Skip disabled stores + * - Changing subscription status + * + Select single folder, double-click row -> toggled + * + Select multiple folders, press subscribe -> all selected folders end up subscribed + * - (un)Subscribing from/to already (un)subscribed folder + * + Check that no IMAP command is sent + * - Switching views between stores + * + Proper tree shown + * - No crashes when buttons are pressed with "No store" screen + * + obvious + * - Restoring filter settings when view switched + * + Enter search, change view, change back -> filter checked and search entry set + * + Clear search, change view, change back -> "all" checked + * - Cancelling in middle of get_store + * + Enter invalid hostname, open dialog, click Close + * - Cancelling in middle if listing + * + Open large directory, click Close + * - Cancelling in middle of subscription op + * + How to test? + * - Test with both IMAP and NNTP + * + obvious + * - Verify that refresh view works + * + obvious + * - No unnecessary tree rebuilds + * + Show All folders, change filter with empty search -> no tree rebuild + * + Converse + * - No out of date tree + * + Show All Folders, change to filter with a search -> tree rebuild + * - Tree construction logic (mostly IMAP-specific terminology) + * + Tree is created progressively + * + No wasted LIST responses + * + No extraneous LIST commands + * + Specifying "folder names begin with" works + * + Always show folders below IMAP namespace (no escaping the namespace) + * + Don't allow subscription to NoSelect folders + * + IMAP accounts always show INBOX + * - Shell interactions + * + Folders are properly created / delete from folder tree when subscribed / unsubscribed + * + Mail Folder Cache doesn't complain + * - No ETable wackiness + * + Verify columns cannot be DnD'd + * + No sorting, eg (?) + * - UI cleanliness + * + Keybindings work + * + Some widget has focus by default + * + Escape / enter work + * + Close button works + */ -#define DEFAULT_STORE_TABLE_WIDTH 200 -#define DEFAULT_WIDTH 500 -#define DEFAULT_HEIGHT 300 - -#define PARENT_TYPE (gtk_object_get_type ()) - -#ifdef JUST_FOR_TRANSLATORS -static char *list [] = { - N_("Folder"), - N_("Store"), -}; -#endif - -#define FOLDER_ETREE_SPEC "<ETableSpecification cursor-mode=\"line\"> \ - <ETableColumn model_col=\"0\" pixbuf=\"subscribed-image\" expansion=\"0.0\" minimum_width=\"16\" resizable=\"false\" cell=\"cell_toggle\" compare=\"integer\"/> \ - <ETableColumn model_col=\"1\" _title=\"Folder\" expansion=\"1.0\" minimum_width=\"20\" resizable=\"true\" cell=\"cell_tree\" compare=\"string\"/> \ - <ETableState> \ - <column source=\"0\"/> \ - <column source=\"1\"/> \ - <grouping></grouping> \ - </ETableState> \ -</ETableSpecification>" - -#define STORE_ETABLE_SPEC "<ETableSpecification cursor-mode=\"line\"> \ - <ETableColumn model_col=\"0\" _title=\"Store\" expansion=\"1.0\" minimum_width=\"20\" resizable=\"true\" cell=\"string\" compare=\"string\"/> \ - <ETableState> \ - <column source=\"0\"/> \ - <grouping></grouping> \ - </ETableState> \ -</ETableSpecification>" - -enum { - FOLDER_COL_SUBSCRIBED, - FOLDER_COL_NAME, - FOLDER_COL_LAST -}; +/*#define NEED_TOGGLE_SELECTION*/ -enum { - STORE_COL_NAME, - STORE_COL_LAST -}; +/* util */ -static GtkObjectClass *subscribe_dialog_parent_class; +static void +recursive_add_folder (EvolutionStorage *storage, const char *path, const char *name, const char *url) +{ + char *parent, *pname, *p; -static void build_tree (SubscribeDialog *sc, CamelStore *store); + p = strrchr (path, '/'); + if (p && p != path) { + parent = g_strndup (path, p - path); + if (!evolution_storage_folder_exists (storage, parent)) { + p = strrchr (parent, '/'); + if (p) + pname = g_strdup (p + 1); + else + pname = g_strdup (""); + recursive_add_folder (storage, parent, pname, ""); + g_free (pname); + } + g_free (parent); + } -static EPixmap pixmaps [] = { - E_PIXMAP ("/Toolbar/SubscribeFolder", "buttons/fetch-mail.png"), /* XXX */ - E_PIXMAP ("/Toolbar/UnsubscribeFolder", "buttons/compose-message.png"), /* XXX */ - E_PIXMAP ("/Toolbar/RefreshList", "buttons/forward.png"), /* XXX */ - E_PIXMAP_END -}; + evolution_storage_new_folder (storage, path, name, "mail", url, name, FALSE); + mail_folder_cache_set_update_estorage (url, storage); + mail_folder_cache_note_name (url, name); +} -static GtkWidget* -make_folder_search_widget (GtkSignalFunc start_search_func, - gpointer user_data_for_search) +static char * +storage_tree_path (CamelFolderInfo *info) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data_for_search); - GtkWidget *search_hbox = gtk_hbox_new (FALSE, 0); + int len; + CamelFolderInfo *i; + char *path, *p; - sc->search_entry = gtk_entry_new (); + for (len = 0, i = info; i; i = i->parent) + len += strlen (i->name) + 1; - if (start_search_func) { - gtk_signal_connect (GTK_OBJECT (sc->search_entry), "activate", - start_search_func, - user_data_for_search); + /* We do this backwards because that's the way the pointers point. */ + path = g_malloc (len + 1); + p = path + len; + *p = '\0'; + for (i = info; i; i = i->parent) { + len = strlen (i->name); + p -= len; + memcpy (p, i->name, len); + *--p = '/'; } - - /* add the search entry to the our search_vbox */ - gtk_box_pack_start (GTK_BOX (search_hbox), - gtk_label_new(_("Display folders starting with:")), - FALSE, TRUE, 0); - gtk_box_pack_start (GTK_BOX (search_hbox), sc->search_entry, - FALSE, TRUE, 3); - return search_hbox; + return path; } - -/* Our async operations */ +/* ** Get one level of folderinfo ****************************************** */ -typedef void (*SubscribeGetStoreCallback)(SubscribeDialog *sc, CamelStore *store, gpointer cb_data); -typedef void (*SubscribeFolderCallback)(SubscribeDialog *sc, gboolean success, gpointer cb_data); +typedef void (*SubscribeShortFolderinfoFunc) (CamelStore *store, gchar *prefix, CamelFolderInfo *info, gpointer data); -/* ** GET STORE ******************************************************* */ +int subscribe_get_short_folderinfo (CamelStore *store, gchar *prefix, SubscribeShortFolderinfoFunc func, gpointer user_data); -struct _get_store_msg { - struct _mail_msg msg; +struct _get_short_folderinfo_msg { + struct _mail_msg msg; - SubscribeDialog *sc; - char *url; - SubscribeGetStoreCallback cb; - gpointer cb_data; - CamelStore *store; + gchar *prefix; + + CamelStore *store; + CamelFolderInfo *info; + + SubscribeShortFolderinfoFunc func; + gpointer user_data; }; -static char *get_store_desc(struct _mail_msg *mm, int done) +static char * +get_short_folderinfo_desc (struct _mail_msg *mm, int done) { - struct _get_store_msg *m = (struct _get_store_msg *)mm; + struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; + char *ret, *name; - return g_strdup_printf(_("Getting store for \"%s\""), m->url); + name = camel_service_get_name (CAMEL_SERVICE (m->store), TRUE); + + if (m->prefix) + ret = g_strdup_printf (_("Scanning folders under %s on \"%s\""), m->prefix, name); + else + ret = g_strdup_printf (_("Scanning root-level folders on \"%s\""), name); + + g_free (name); + return ret; } -static void get_store_get(struct _mail_msg *mm) +static void +get_short_folderinfo_get (struct _mail_msg *mm) { - struct _get_store_msg *m = (struct _get_store_msg *)mm; + struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - m->store = camel_session_get_store (session, m->url, &mm->ex); + camel_operation_register (mm->cancel); + m->info = camel_store_get_folder_info (m->store, m->prefix, CAMEL_STORE_FOLDER_INFO_FAST, &mm->ex); + camel_operation_unregister (mm->cancel); } -static void get_store_got(struct _mail_msg *mm) +static void +get_short_folderinfo_got (struct _mail_msg *mm) { - struct _get_store_msg *m = (struct _get_store_msg *)mm; + struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; + + if (camel_exception_is_set (&(mm->ex))) + g_warning ("Error getting folder info from store at %s: %s", + camel_service_get_url (CAMEL_SERVICE (m->store)), + camel_exception_get_description (&(mm->ex))); - m->cb(m->sc, m->store, m->cb_data); + /* 'done' is probably guaranteed to fail, but... */ + + if (m->func) + m->func (m->store, m->prefix, m->info, m->user_data); } -static void get_store_free(struct _mail_msg *mm) +static void +get_short_folderinfo_free (struct _mail_msg *mm) { - struct _get_store_msg *m = (struct _get_store_msg *)mm; + struct _get_short_folderinfo_msg *m = (struct _get_short_folderinfo_msg *) mm; - if (m->store) - camel_object_unref((CamelObject *)m->store); - g_free(m->url); + camel_store_free_folder_info (m->store, m->info); + camel_object_unref (CAMEL_OBJECT (m->store)); + + g_free (m->prefix); /* may be NULL but that's ok */ } -static struct _mail_msg_op get_store_op = { - get_store_desc, - get_store_get, - get_store_got, - get_store_free, +static struct _mail_msg_op get_short_folderinfo_op = { + get_short_folderinfo_desc, + get_short_folderinfo_get, + get_short_folderinfo_got, + get_short_folderinfo_free, }; -static void -subscribe_do_get_store (SubscribeDialog *sc, const char *url, SubscribeGetStoreCallback cb, gpointer cb_data) +int +subscribe_get_short_folderinfo (CamelStore *store, + gchar *prefix, + SubscribeShortFolderinfoFunc func, + gpointer user_data) { - struct _get_store_msg *m; + struct _get_short_folderinfo_msg *m; int id; - g_return_if_fail (url != NULL); + m = mail_msg_new (&get_short_folderinfo_op, NULL, sizeof(*m)); - m = mail_msg_new(&get_store_op, NULL, sizeof(*m)); - m->sc = sc; - m->url = g_strdup(url); - m->cb = cb; - m->cb_data = cb_data; - - id = m->msg.seq; - e_thread_put(mail_thread_queued, (EMsg *)m); - mail_msg_wait(id); -} + m->store = store; + camel_object_ref (CAMEL_OBJECT (store)); -/* ** SUBSCRIBE FOLDER ******************************************************* */ -/* Given a CamelFolderInfo, construct the corresponding - * EvolutionStorage path to it. - */ -static char * -storage_tree_path (CamelFolderInfo *info) -{ - int len; - CamelFolderInfo *i; - char *path, *p; + if (prefix) + m->prefix = g_strdup (prefix); + else + m->prefix = NULL; - for (len = 0, i = info; i; i = i->parent) - len += strlen (i->name) + 1; + m->func = func; + m->user_data = user_data; - /* We do this backwards because that's the way the pointers point. */ - path = g_malloc (len + 1); - p = path + len; - *p = '\0'; - for (i = info; i; i = i->parent) { - len = strlen (i->name); - p -= len; - memcpy (p, i->name, len); - *--p = '/'; - } - - return path; + id = m->msg.seq; + e_thread_put (mail_thread_new, (EMsg *)m); + return id; } -/* ********************************************************************** */ -/* Subscribe folder */ +/* ** Subscribe folder operation **************************************** */ + +typedef void (*SubscribeFolderCallback) (const char *, const char *, gboolean, gboolean, gpointer); struct _subscribe_msg { - struct _mail_msg msg; + struct _mail_msg msg; - SubscribeDialog *sc; - CamelStore *store; - gboolean subscribe; - SubscribeFolderCallback cb; - gpointer cb_data; - - char *path; - char *name; - char *full_name; - char *url; + CamelStore *store; + gboolean subscribe; + gchar *full_name; + gchar *name; + + SubscribeFolderCallback cb; + gpointer cb_data; }; -static char *subscribe_folder_desc(struct _mail_msg *mm, int done) +static char * +subscribe_folder_desc (struct _mail_msg *mm, int done) { - struct _subscribe_msg *m = (struct _subscribe_msg *)mm; + struct _subscribe_msg *m = (struct _subscribe_msg *) mm; if (m->subscribe) - return g_strdup_printf(_("Subscribing to folder \"%s\""), m->name); + return g_strdup_printf (_("Subscribing to folder \"%s\""), m->name); else - return g_strdup_printf(_("Unsubscribing to folder \"%s\""), m->name); + return g_strdup_printf (_("Unsubscribing to folder \"%s\""), m->name); } -static void subscribe_folder_subscribe(struct _mail_msg *mm) +static void +subscribe_folder_subscribe (struct _mail_msg *mm) { - struct _subscribe_msg *m = (struct _subscribe_msg *)mm; + struct _subscribe_msg *m = (struct _subscribe_msg *) mm; if (m->subscribe) - camel_store_subscribe_folder(m->store, m->full_name, &mm->ex); + camel_store_subscribe_folder (m->store, m->full_name, &mm->ex); else - camel_store_unsubscribe_folder(m->store, m->full_name, &mm->ex); + camel_store_unsubscribe_folder (m->store, m->full_name, &mm->ex); } -static void -recursive_add_folder (EvolutionStorage *storage, const char *path, const char *name, const char *url) +static void +subscribe_folder_subscribed (struct _mail_msg *mm) { - char *parent, *pname, *p; - - p = strrchr (path, '/'); - if (p && p != path) { - parent = g_strndup (path, p - path); - if (!evolution_storage_folder_exists (storage, parent)) { - p = strrchr (parent, '/'); - if (p) - pname = g_strdup (p + 1); - else - pname = g_strdup (""); - recursive_add_folder (storage, parent, pname, ""); - g_free (pname); - } - g_free (parent); - } - - evolution_storage_new_folder (storage, path, name, "mail", url, name, FALSE); -} - -static void subscribe_folder_subscribed(struct _mail_msg *mm) -{ - struct _subscribe_msg *m = (struct _subscribe_msg *)mm; - - if (!camel_exception_is_set (&mm->ex)) { - if (m->subscribe) - recursive_add_folder(m->sc->storage, m->path, m->name, m->url); - else - evolution_storage_removed_folder(m->sc->storage, m->path); - } + struct _subscribe_msg *m = (struct _subscribe_msg *) mm; if (m->cb) - m->cb(m->sc, !camel_exception_is_set(&mm->ex), m->cb_data); + (m->cb) (m->full_name, m->name, + m->subscribe, + !camel_exception_is_set(&mm->ex), m->cb_data); } -static void subscribe_folder_free(struct _mail_msg *mm) +static void +subscribe_folder_free (struct _mail_msg *mm) { - struct _subscribe_msg *m = (struct _subscribe_msg *)mm; + struct _subscribe_msg *m = (struct _subscribe_msg *) mm; - g_free(m->path); - g_free(m->name); - g_free(m->full_name); - g_free(m->url); + g_free (m->name); + g_free (m->full_name); - camel_object_unref((CamelObject *)m->store); - /* in wrong thread to do this? - gtk_object_unref (GTK_OBJECT (input->sc));*/ + camel_object_unref (CAMEL_OBJECT (m->store)); } static struct _mail_msg_op subscribe_folder_op = { @@ -342,721 +332,1132 @@ static struct _mail_msg_op subscribe_folder_op = { subscribe_folder_free, }; -static void -subscribe_do_subscribe_folder (SubscribeDialog *sc, CamelStore *store, CamelFolderInfo *info, +static int +subscribe_do_subscribe_folder (CamelStore *store, const char *full_name, const char *name, gboolean subscribe, SubscribeFolderCallback cb, gpointer cb_data) { struct _subscribe_msg *m; - - g_return_if_fail (CAMEL_IS_STORE (store)); - g_return_if_fail (info); + int id; - m = mail_msg_new(&subscribe_folder_op, NULL, sizeof(*m)); - m->sc = sc; - m->store = store; - camel_object_ref((CamelObject *)store); - m->subscribe = subscribe; - m->cb = cb; - m->cb_data = cb_data; + g_return_val_if_fail (CAMEL_IS_STORE (store), 0); + g_return_val_if_fail (full_name, 0); - m->path = storage_tree_path (info); - m->name = g_strdup (info->name); - m->full_name = g_strdup (info->full_name); - m->url = g_strdup (info->url); + m = mail_msg_new (&subscribe_folder_op, NULL, sizeof(*m)); + m->store = store; + m->subscribe = subscribe; + m->name = g_strdup (name); + m->full_name = g_strdup (full_name); + m->cb = cb; + m->cb_data = cb_data; - /* - gtk_object_ref (GTK_OBJECT (sc));*/ + camel_object_ref (CAMEL_OBJECT (store)); - e_thread_put(mail_thread_new, (EMsg *)m); + id = m->msg.seq; + e_thread_put (mail_thread_new, (EMsg *)m); + return id; } - +/* ** FolderETree Extras *************************************************** */ -static gboolean -folder_info_subscribed (SubscribeDialog *sc, CamelFolderInfo *info) -{ - return camel_store_folder_subscribed (sc->store, info->full_name); -} +typedef struct _FolderETreeExtras FolderETreeExtras; +typedef struct _FolderETreeExtrasClass FolderETreeExtrasClass; -static void -node_changed_cb (SubscribeDialog *sc, gboolean changed, gpointer data) -{ - ETreePath node = data; +enum { + FOLDER_COL_SUBSCRIBED, + FOLDER_COL_NAME, + FOLDER_COL_LAST +}; - if (changed) - e_tree_model_node_data_changed (sc->folder_model, node); -} +struct _FolderETreeExtras { + ETableExtras parent; + GdkPixbuf *toggles[2]; +}; -static void -subscribe_folder_info (SubscribeDialog *sc, CamelFolderInfo *info, ETreePath node) -{ - /* folders without urls cannot be subscribed to */ - if (info->url == NULL) - return; - - subscribe_do_subscribe_folder (sc, sc->store, info, TRUE, node_changed_cb, node); -} +struct _FolderETreeExtrasClass { + ETableExtrasClass parent; +}; -static void -unsubscribe_folder_info (SubscribeDialog *sc, CamelFolderInfo *info, ETreePath node) -{ - /* folders without urls cannot be subscribed to */ - if (info->url == NULL) - return; - - subscribe_do_subscribe_folder (sc, sc->store, info, FALSE, node_changed_cb, node); -} +static GtkObjectClass *ftree_extras_parent_class = NULL; static void -subscribe_close (BonoboUIComponent *uic, - void *user_data, const char *path) +fete_destroy (GtkObject *object) { - SubscribeDialog *sc = (SubscribeDialog*)user_data; + FolderETreeExtras *extras = (FolderETreeExtras *) object; - gtk_widget_destroy (sc->app); + gdk_pixbuf_unref (extras->toggles[0]); + gdk_pixbuf_unref (extras->toggles[1]); + + ftree_extras_parent_class->destroy (object); } static void -subscribe_select_all (BonoboUIComponent *uic, - void *user_data, const char *path) +fete_class_init (GtkObjectClass *object_class) { - SubscribeDialog *sc = (SubscribeDialog*)user_data; - ETreeScrolled *scrolled = E_TREE_SCROLLED (sc->folder_etree); - ETree *tree = e_tree_scrolled_get_tree (scrolled); - ESelectionModel *esm = e_tree_get_selection_model (E_TREE (tree)); + object_class->destroy = fete_destroy; - e_selection_model_select_all (E_SELECTION_MODEL (esm)); + ftree_extras_parent_class = gtk_type_class (E_TABLE_EXTRAS_TYPE); } static void -subscribe_invert_selection (BonoboUIComponent *uic, - void *user_data, const char *path) +fete_init (GtkObject *object) { - SubscribeDialog *sc = (SubscribeDialog*)user_data; - ETreeScrolled *scrolled = E_TREE_SCROLLED (sc->folder_etree); - ETree *tree = e_tree_scrolled_get_tree (scrolled); - ESelectionModel *esm = e_tree_get_selection_model (E_TREE (tree)); - - e_selection_model_invert_selection (E_SELECTION_MODEL (esm)); -} + FolderETreeExtras *extras = (FolderETreeExtras *) object; + ECell *cell; + ECell *text_cell; -static void -subscribe_folder_foreach (int model_row, gpointer closure) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (closure); - ETreePath node = e_tree_node_at_row (e_tree_scrolled_get_tree(E_TREE_SCROLLED(sc->folder_etree)), model_row); - CamelFolderInfo *info = e_tree_memory_node_get_data (E_TREE_MEMORY(sc->folder_model), node); + /* text column */ - if (!folder_info_subscribed (sc, info)) - subscribe_folder_info (sc, info, node); -} + cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + text_cell = cell; + gtk_object_set (GTK_OBJECT (cell), + "bold_column", FOLDER_COL_SUBSCRIBED, + NULL); + e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_text", cell); -static void -subscribe_folders (BonoboUIComponent *componet, gpointer user_data, const char *cname) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + /* toggle column */ - e_tree_selected_row_foreach (e_tree_scrolled_get_tree(E_TREE_SCROLLED(sc->folder_etree)), - subscribe_folder_foreach, sc); -} + extras->toggles[0] = gdk_pixbuf_new_from_xpm_data ((const char **)empty_xpm); + extras->toggles[1] = gdk_pixbuf_new_from_xpm_data ((const char **)mark_xpm); + cell = e_cell_toggle_new (0, 2, extras->toggles); + e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_toggle", cell); -static void -unsubscribe_folder_foreach (int model_row, gpointer closure) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (closure); - ETreePath node = e_tree_node_at_row (e_tree_scrolled_get_tree(E_TREE_SCROLLED(sc->folder_etree)), model_row); - CamelFolderInfo *info = e_tree_memory_node_get_data (E_TREE_MEMORY(sc->folder_model), node); + /* tree cell */ + + cell = e_cell_tree_new (NULL, NULL, TRUE, text_cell); + e_table_extras_add_cell (E_TABLE_EXTRAS (extras), "cell_tree", cell); - if (folder_info_subscribed(sc, info)) - unsubscribe_folder_info (sc, info, node); + /* misc */ + + e_table_extras_add_pixbuf (E_TABLE_EXTRAS (extras), "subscribed-image", extras->toggles[1]); } +/* naughty! */ +static +E_MAKE_TYPE (fete, "FolderETreeExtras", FolderETreeExtras, fete_class_init, fete_init, E_TABLE_EXTRAS_TYPE); -static void -unsubscribe_folders (BonoboUIComponent *component, gpointer user_data, const char *cname) -{ - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); +/* ** Global Extras ******************************************************** */ - e_tree_selected_row_foreach (e_tree_scrolled_get_tree(E_TREE_SCROLLED(sc->folder_etree)), - unsubscribe_folder_foreach, sc); -} +static FolderETreeExtras *global_extras = NULL; static void -subscribe_refresh_list (BonoboUIComponent *component, gpointer user_data, const char *cname) +global_extras_destroyed (GtkObject *obj, gpointer user_data) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + global_extras = NULL; +} - e_utf8_gtk_entry_set_text (GTK_ENTRY (sc->search_entry), ""); - if (sc->search_top) { - g_free (sc->search_top); - sc->search_top = NULL; +static ETableExtras * +subscribe_get_global_extras (void) +{ + if (global_extras == NULL) { + global_extras = gtk_type_new (fete_get_type()); + gtk_object_ref (GTK_OBJECT (global_extras)); + gtk_object_sink (GTK_OBJECT (global_extras)); + gtk_signal_connect (GTK_OBJECT (global_extras), "destroy", + global_extras_destroyed, NULL); } - if (sc->store) - build_tree (sc, sc->store); + + gtk_object_ref (GTK_OBJECT (global_extras)); + return E_TABLE_EXTRAS (global_extras); } -static void -subscribe_search (GtkWidget *widget, gpointer user_data) +/* ** Folder Tree Node ***************************************************** */ + +typedef struct _ftree_node ftree_node; + +struct _ftree_node { + guint8 flags; + int path_offset; + int uri_offset; + int full_name_offset; + + /* format: {name}{\0}{path}{\0}{uri}{\0}{full_name}{\0} + * (No braces). */ + char data[1]; +}; + +#define FTREE_NODE_GOT_CHILDREN (1 << 0) +#define FTREE_NODE_SUBSCRIBABLE (1 << 1) +#define FTREE_NODE_SUBSCRIBED (1 << 2) +#define FTREE_NODE_ROOT (1 << 3) + +static ftree_node * +ftree_node_new_root (const char *prefix) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - char* search_pattern = e_utf8_gtk_entry_get_text(GTK_ENTRY(widget)); + ftree_node *node; + size_t size; - if (sc->search_top) { - g_free (sc->search_top); - sc->search_top = NULL; - } + if (prefix == NULL) + prefix = ""; + + size = sizeof (ftree_node) + strlen (prefix) + 1; - if (search_pattern && *search_pattern) - sc->search_top = search_pattern; + node = g_malloc (size); + node->flags = FTREE_NODE_ROOT; + node->path_offset = 0; + node->uri_offset = 0; + node->full_name_offset = 1; + node->data[0] = '\0'; + strcpy (node->data + 1, prefix); - if (sc->store) - build_tree (sc, sc->store); + return node; } - -#if 0 -/* HTML Helpers */ -static void -html_size_req (GtkWidget *widget, GtkRequisition *requisition) +static ftree_node * +ftree_node_new (CamelStore *store, CamelFolderInfo *fi) { - if (GTK_LAYOUT (widget)->height > 90) - requisition->height = 90; + ftree_node *node; + int path_offset, uri_offset, full_name_offset; + size_t size; + char *path; + CamelURL *url; + + path = storage_tree_path (fi); + + path_offset = strlen (fi->name) + 1; + uri_offset = path_offset + strlen (path) + 1; + full_name_offset = uri_offset + strlen (fi->url) + 1; + size = full_name_offset + strlen (fi->full_name); + + /* - 1 for sizeof(node.data) but +1 for terminating \0 */ + node = g_malloc (sizeof (*node) + size); + + /* Noselect? */ + + url = camel_url_new (fi->url, NULL); + if (camel_url_get_param (url, "noselect")) + node->flags = 0; else - requisition->height = GTK_LAYOUT (widget)->height; -} + node->flags = FTREE_NODE_SUBSCRIBABLE; + camel_url_free (url); -/* Returns a GtkHTML which is already inside a GtkScrolledWindow. If - * @white is TRUE, the GtkScrolledWindow will be inside a GtkFrame. - */ -static GtkWidget * -html_new (gboolean white) -{ - GtkWidget *html, *scrolled, *frame; - GtkStyle *style; - - html = gtk_html_new (); - GTK_LAYOUT (html)->height = 0; - gtk_signal_connect (GTK_OBJECT (html), "size_request", - GTK_SIGNAL_FUNC (html_size_req), NULL); - gtk_html_set_editable (GTK_HTML (html), FALSE); - style = gtk_rc_get_style (html); - if (style) { - gtk_html_set_default_background_color (GTK_HTML (html), - white ? &style->white : - &style->bg[0]); - } - gtk_widget_set_sensitive (html, FALSE); - scrolled = gtk_scrolled_window_new (NULL, NULL); - gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled), - GTK_POLICY_NEVER, - GTK_POLICY_NEVER); - gtk_container_add (GTK_CONTAINER (scrolled), html); - - if (white) { - frame = gtk_frame_new (NULL); - gtk_frame_set_shadow_type (GTK_FRAME (frame), - GTK_SHADOW_ETCHED_IN); - gtk_container_add (GTK_CONTAINER (frame), scrolled); - gtk_widget_show_all (frame); - } else - gtk_widget_show_all (scrolled); - - return html; -} + /* subscribed? */ -static void -put_html (GtkHTML *html, char *text) -{ - GtkHTMLStream *handle; - - text = e_text_to_html (text, (E_TEXT_TO_HTML_CONVERT_NL | - E_TEXT_TO_HTML_CONVERT_SPACES | - E_TEXT_TO_HTML_CONVERT_URLS)); - handle = gtk_html_begin (html); - gtk_html_write (html, handle, "<HTML><BODY>", 12); - gtk_html_write (html, handle, text, strlen (text)); - gtk_html_write (html, handle, "</BODY></HTML>", 14); - g_free (text); - gtk_html_end (html, handle, GTK_HTML_STREAM_OK); + if (camel_store_folder_subscribed (store, fi->full_name)) + node->flags |= FTREE_NODE_SUBSCRIBED; + + /* Copy strings */ + + node->path_offset = path_offset; + node->uri_offset = uri_offset; + node->full_name_offset = full_name_offset; + + strcpy (node->data, fi->name); + strcpy (node->data + path_offset, path); + strcpy (node->data + uri_offset, fi->url); + strcpy (node->data + full_name_offset, fi->full_name); + + /* Done */ + + g_free (path); + return node; } -#endif - -/* etree stuff for the subscribe ui */ +#define ftree_node_subscribable(node) ( ((ftree_node *) (node))->flags & FTREE_NODE_SUBSCRIBABLE ) +#define ftree_node_subscribed(node) ( ((ftree_node *) (node))->flags & FTREE_NODE_SUBSCRIBED ) +#define ftree_node_get_name(node) ( ((ftree_node *) (node))->data ) +#define ftree_node_get_full_name(node) ( ((ftree_node *) (node))->data + ((ftree_node *) (node))->full_name_offset ) +#define ftree_node_get_path(node) ( ((ftree_node *) (node))->data + ((ftree_node *) (node))->path_offset ) +#define ftree_node_get_uri(node) ( ((ftree_node *) (node))->data + ((ftree_node *) (node))->uri_offset ) + +/* ** Folder Tree Model **************************************************** */ + +typedef struct _FolderETree FolderETree; +typedef struct _FolderETreeClass FolderETreeClass; + +struct _FolderETree { + ETreeMemory parent; + ETreePath root; + + GHashTable *scan_ops; + GHashTable *subscribe_ops; + + CamelStore *store; + EvolutionStorage *e_storage; + gchar *search; +}; + +struct _FolderETreeClass { + ETreeMemoryClass parent; +}; + +static GtkObjectClass *folder_etree_parent_class = NULL; + +/* A subscribe or scan operation */ + +typedef struct _ftree_op_data ftree_op_data; + +struct _ftree_op_data { + FolderETree *ftree; + ETreePath path; + ftree_node *data; + int handle; +}; + + +/* ETreeModel functions */ static int -folder_etree_column_count (ETreeModel *etm, void *data) +fe_column_count (ETreeModel *etm) { return FOLDER_COL_LAST; } -static void* -folder_etree_duplicate_value (ETreeModel *etm, int col, const void *val, void *data) +static void * +fe_duplicate_value (ETreeModel *etm, int col, const void *val) { return g_strdup (val); } static void -folder_etree_free_value (ETreeModel *etm, int col, void *val, void *data) +fe_free_value (ETreeModel *etm, int col, void *val) { g_free (val); } static void* -folder_etree_init_value (ETreeModel *etm, int col, void *data) +fe_init_value (ETreeModel *etm, int col) { return g_strdup (""); } static gboolean -folder_etree_value_is_empty (ETreeModel *etm, int col, const void *val, void *data) +fe_value_is_empty (ETreeModel *etm, int col, const void *val) { return !(val && *(char *)val); } -static char* -folder_etree_value_to_string (ETreeModel *etm, int col, const void *val, void *data) +static char * +fe_value_to_string (ETreeModel *etm, int col, const void *val) { - return g_strdup(val); + return g_strdup (val); } -static GdkPixbuf* -folder_etree_icon_at (ETreeModel *etree, ETreePath path, void *model_data) +static GdkPixbuf * +fe_icon_at (ETreeModel *etree, ETreePath path) { return NULL; /* XXX no icons for now */ } -static void* -folder_etree_value_at (ETreeModel *etree, ETreePath path, int col, void *model_data) +static gpointer +fe_root_value_at (FolderETree *ftree, int col) { - SubscribeDialog *dialog = SUBSCRIBE_DIALOG (model_data); - CamelFolderInfo *info = e_tree_memory_node_get_data (E_TREE_MEMORY(etree), path); - - if (col == FOLDER_COL_NAME) { - return info->name; + switch (col) { + case FOLDER_COL_NAME: + return camel_service_get_name (CAMEL_SERVICE (ftree->store), TRUE); + case FOLDER_COL_SUBSCRIBED: + return GINT_TO_POINTER (0); + default: + printf ("Oh no, unimplemented column %d in subscribe dialog\n", col); } - else /* FOLDER_COL_SUBSCRIBED */ { - /* folders without urls cannot be subscribed to */ - if (info->url == NULL) - return GINT_TO_POINTER(0); /* empty */ - else if (!folder_info_subscribed(dialog, info)) - return GINT_TO_POINTER(0); /* XXX unchecked */ - else - return GUINT_TO_POINTER (1); /* checked */ + + return NULL; +} + +static gpointer +fe_real_value_at (FolderETree *ftree, int col, gpointer data) +{ + switch (col) { + case FOLDER_COL_NAME: + return g_strdup (ftree_node_get_name (data)); + case FOLDER_COL_SUBSCRIBED: + if (ftree_node_subscribed (data)) + return GINT_TO_POINTER (1); + return GINT_TO_POINTER (0); + default: + printf ("Oh no, unimplemented column %d in subscribe dialog\n", col); } + + return NULL; +} + +static void * +fe_value_at (ETreeModel *etree, ETreePath path, int col) +{ + FolderETree *ftree = (FolderETree *) etree; + gpointer node_data; + + if (path == ftree->root) + return fe_root_value_at (ftree, col); + + node_data = e_tree_memory_node_get_data (E_TREE_MEMORY (etree), path); + return fe_real_value_at (ftree, col, node_data); } static void -folder_etree_set_value_at (ETreeModel *etree, ETreePath path, int col, const void *val, void *model_data) +fe_set_value_at (ETreeModel *etree, ETreePath path, int col, const void *val) { /* nothing */ } static gboolean -folder_etree_is_editable (ETreeModel *etree, ETreePath path, int col, void *model_data) +fe_return_false (void) { return FALSE; } - +/* scanning */ -static int -store_etable_col_count (ETableModel *etm, void *data) +static void +fe_got_children (CamelStore *store, gchar *prefix, CamelFolderInfo *info, gpointer data) +{ + ftree_op_data *closure = (ftree_op_data *) data; + + if (!info) /* cancelled */ + return; + + if (!prefix) + prefix = ""; + printf ("CHILDREN OF \"%s\":\n", prefix); + + for (; info; info = info->sibling) { + ETreePath child_path; + ftree_node *node; + + if (strcmp (info->full_name, prefix) == 0) + continue; + + node = ftree_node_new (store, info); + child_path = e_tree_memory_node_insert (E_TREE_MEMORY (closure->ftree), + closure->path, + 0, + node); + + printf (" \"%s\"\n", info->full_name); + } + + if (closure->data) + closure->data->flags |= FTREE_NODE_GOT_CHILDREN; + + g_hash_table_remove (closure->ftree->scan_ops, closure->path); + g_free (closure); +} + +static void +fe_check_for_children (FolderETree *ftree, ETreePath path) { - return STORE_COL_LAST; + ftree_op_data *closure; + ftree_node *node; + gchar *prefix; + + node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); + + /* have we already gotten these children? */ + if (node->flags & FTREE_NODE_GOT_CHILDREN) { + printf ("CHECK FOR CHILDREN: cancel: below %s, (got children)\n", ftree_node_get_full_name (node)); + fflush (stdout); + return; + } + + /* or we're loading them right now? */ + if (g_hash_table_lookup (ftree->scan_ops, path)) { + printf ("CHECK FOR CHILDREN: cancel: below %s, in progress\n", ftree_node_get_full_name (node)); + fflush (stdout); + return; + } + + printf ("CHECK FOR CHILDREN: keepon: below %s\n", ftree_node_get_full_name (node)); + fflush (stdout); + + /* figure out our search prefix */ + if (path == ftree->root) + prefix = ftree->search; + else + prefix = ftree_node_get_full_name (node); + + closure = g_new (ftree_op_data, 1); + closure->ftree = ftree; + closure->path = path; + closure->data = node; + closure->handle = -1; + + g_hash_table_insert (ftree->scan_ops, path, closure); + + /* FIXME. Tiny race possiblity I guess. */ + + closure->handle = subscribe_get_short_folderinfo (ftree->store, prefix, fe_got_children, closure); } -static int -store_etable_row_count (ETableModel *etm, void *data) +static void +fe_create_root_node (FolderETree *ftree) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (data); + ftree_node *node; - return g_list_length (sc->store_list); + node = ftree_node_new_root (ftree->search); + ftree->root = e_tree_memory_node_insert (E_TREE_MEMORY(ftree), NULL, 0, node); } -static void* -store_etable_value_at (ETableModel *etm, int col, int row, void *data) +static ETreePath +fe_get_first_child (ETreeModel *model, ETreePath path) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (data); - CamelStore *store = (CamelStore*)g_list_nth_data (sc->store_list, row); + ETreePath child_path; - return camel_service_get_name (CAMEL_SERVICE (store), TRUE); + child_path = E_TREE_MODEL_CLASS (folder_etree_parent_class)->get_first_child (model, path); + if (child_path) + fe_check_for_children ((FolderETree *) model, child_path); + else + fe_check_for_children ((FolderETree *) model, path); + return child_path; } +/* subscribing */ + static void -store_etable_set_value_at (ETableModel *etm, int col, int row, const void *val, void *data) +fe_done_subscribing (const char *full_name, const char *name, gboolean subscribe, gboolean success, gpointer user_data) { - /* nada */ + ftree_op_data *closure = (ftree_op_data *) user_data; + + if (success) { + if (subscribe) { + closure->data->flags |= FTREE_NODE_SUBSCRIBED; + recursive_add_folder (closure->ftree->e_storage, + ftree_node_get_path (closure->data), + name, + ftree_node_get_uri (closure->data)); + } else { + closure->data->flags &= ~FTREE_NODE_SUBSCRIBED; + evolution_storage_removed_folder (closure->ftree->e_storage, ftree_node_get_path (closure->data)); + } + + e_tree_model_node_data_changed (E_TREE_MODEL (closure->ftree), closure->path); + } + + g_hash_table_remove (closure->ftree->subscribe_ops, closure->path); + g_free (closure); } +/* cleanup */ + static gboolean -store_etable_is_editable (ETableModel *etm, int col, int row, void *data) +fe_cancel_op_foreach (gpointer key, gpointer value, gpointer user_data) { - return FALSE; + /*FolderETree *ftree = (FolderETree *) user_data;*/ + ftree_op_data *closure = (ftree_op_data *) value; + + if (closure->handle != -1) + mail_msg_cancel (closure->handle); + else + printf ("aaagh, annoying race condition in fe_cancel_op_foreach.\n"); + + g_free (value); + return TRUE; } -static void* -store_etable_duplicate_value (ETableModel *etm, int col, const void *val, void *data) +static void +fe_kill_current_tree (FolderETree *ftree) { - return g_strdup (val); + g_hash_table_foreach_remove (ftree->scan_ops, fe_cancel_op_foreach, ftree); + g_assert (g_hash_table_size (ftree->scan_ops) == 0); } static void -store_etable_free_value (ETableModel *etm, int col, void *val, void *data) +fe_destroy (GtkObject *obj) { - g_free (val); + FolderETree *ftree = (FolderETree *) (obj); + + fe_kill_current_tree (ftree); + + g_hash_table_foreach_remove (ftree->subscribe_ops, fe_cancel_op_foreach, ftree); + + g_hash_table_destroy (ftree->scan_ops); + g_hash_table_destroy (ftree->subscribe_ops); + + camel_object_unref (CAMEL_OBJECT (ftree->store)); + + g_free (ftree->search); } -static void* -store_etable_initialize_value (ETableModel *etm, int col, void *data) +typedef gboolean (*bool_func_1) (ETreeModel *, ETreePath, int); +typedef gboolean (*bool_func_2) (ETreeModel *); + +static void +folder_etree_class_init (GtkObjectClass *klass) { - return g_strdup (""); + ETreeModelClass *etree_model_class = E_TREE_MODEL_CLASS (klass); + + folder_etree_parent_class = gtk_type_class (E_TREE_MEMORY_TYPE); + + klass->destroy = fe_destroy; + + etree_model_class->value_at = fe_value_at; + etree_model_class->set_value_at = fe_set_value_at; + etree_model_class->column_count = fe_column_count; + etree_model_class->duplicate_value = fe_duplicate_value; + etree_model_class->free_value = fe_free_value; + etree_model_class->initialize_value = fe_init_value; + etree_model_class->value_is_empty = fe_value_is_empty; + etree_model_class->value_to_string = fe_value_to_string; + etree_model_class->icon_at = fe_icon_at; + etree_model_class->is_editable = (bool_func_1) fe_return_false; + etree_model_class->has_save_id = (bool_func_2) fe_return_false; + etree_model_class->has_get_node_by_id = (bool_func_2) fe_return_false; + etree_model_class->get_first_child = fe_get_first_child; } -static gboolean -store_etable_value_is_empty (ETableModel *etm, int col, const void *val, void *data) +static void +folder_etree_init (GtkObject *object) { - return !(val && *(char *)val); + FolderETree *ftree = (FolderETree *) object; + + e_tree_memory_set_node_destroy_func (E_TREE_MEMORY (ftree), (GFunc) g_free, ftree); + + ftree->scan_ops = g_hash_table_new (g_direct_hash, g_direct_equal); + ftree->subscribe_ops = g_hash_table_new (g_direct_hash, g_direct_equal); + + ftree->search = g_strdup (""); } -static char* -store_etable_value_to_string (ETableModel *etm, int col, const void *val, void *data) +static FolderETree * +folder_etree_construct (FolderETree *ftree, + CamelStore *store) { - return g_strdup(val); + e_tree_memory_construct (E_TREE_MEMORY (ftree)); + + fe_create_root_node (ftree); + + ftree->store = store; + camel_object_ref (CAMEL_OBJECT (store)); + ftree->e_storage = mail_lookup_storage (store); + + return ftree; } - +static +E_MAKE_TYPE (folder_etree, "FolderETree", FolderETree, folder_etree_class_init, folder_etree_init, E_TREE_MEMORY_TYPE); + +/* public */ + +static FolderETree * +folder_etree_new (CamelStore *store) +{ + FolderETree *ftree; + + ftree = gtk_type_new (folder_etree_get_type()); + ftree = folder_etree_construct (ftree, store); + return ftree; +} static void -build_etree_from_folder_info (SubscribeDialog *sc, ETreePath parent, CamelFolderInfo *info) +folder_etree_clear_tree (FolderETree *ftree) { - CamelFolderInfo *i; + e_tree_memory_freeze (E_TREE_MEMORY (ftree)); + e_tree_memory_node_remove (E_TREE_MEMORY (ftree), ftree->root); + fe_create_root_node (ftree); + e_tree_memory_thaw (E_TREE_MEMORY (ftree)); +} - if (info == NULL) +static void +folder_etree_set_search (FolderETree *ftree, const char *search) +{ + if (!strcmp (search, ftree->search)) return; - for (i = info; i; i = i->sibling) { - ETreePath node = e_tree_memory_node_insert (E_TREE_MEMORY(sc->folder_model), parent, -1, i); - build_etree_from_folder_info (sc, node, i->child); - } + g_free (ftree->search); + ftree->search = g_strdup (search); + + folder_etree_clear_tree (ftree); } -static void -build_tree (SubscribeDialog *sc, CamelStore *store) + +static int +folder_etree_path_set_subscription (FolderETree *ftree, ETreePath path, gboolean subscribe) { - CamelException *ex = camel_exception_new(); + ftree_op_data *closure; + ftree_node *node; - /* free up the existing CamelFolderInfo* if there is any */ - if (sc->folder_info) - camel_store_free_folder_info (sc->store, sc->folder_info); - if (sc->storage) - gtk_object_unref (GTK_OBJECT (sc->storage)); + /* already in progress? */ - sc->store = store; - sc->storage = mail_lookup_storage (sc->store); - sc->folder_info = camel_store_get_folder_info (sc->store, sc->search_top, CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_FAST, ex); + if (g_hash_table_lookup (ftree->subscribe_ops, path)) + return 0; - if (camel_exception_is_set (ex)) { - printf ("camel_store_get_folder_info failed\n"); - camel_exception_free (ex); - return; - } + /* noselect? */ + + node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); + + if (!ftree_node_subscribable (node)) + return -1; - e_tree_memory_freeze(E_TREE_MEMORY(sc->folder_model)); - e_tree_memory_node_remove (E_TREE_MEMORY(sc->folder_model), sc->folder_root); - sc->folder_root = e_tree_memory_node_insert (E_TREE_MEMORY(sc->folder_model), NULL, - 0, NULL); + /* noop? */ - build_etree_from_folder_info (sc, sc->folder_root, sc->folder_info); - e_tree_memory_thaw(E_TREE_MEMORY(sc->folder_model)); + /* uh, this should be a not XOR or something */ + if ((ftree_node_subscribed (node) && subscribe) || + (!ftree_node_subscribed (node) && !subscribe)) + return 0; - camel_exception_free (ex); + closure = g_new (ftree_op_data, 1); + closure->ftree = ftree; + closure->path = path; + closure->data = node; + closure->handle = -1; + + g_hash_table_insert (ftree->subscribe_ops, path, closure); + + closure->handle = subscribe_do_subscribe_folder (ftree->store, + ftree_node_get_full_name (node), + ftree_node_get_name (node), + subscribe, + fe_done_subscribing, + closure); + return 0; } -static void -storage_selected_cb (ETree *table, int row, gpointer data) +static int +folder_etree_path_toggle_subscription (FolderETree *ftree, ETreePath path) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (data); - CamelStore *store = (CamelStore*)g_list_nth_data (sc->store_list, row); + ftree_node *node = e_tree_memory_node_get_data (E_TREE_MEMORY (ftree), path); - build_tree (sc, store); + if (ftree_node_subscribed (node)) + return folder_etree_path_set_subscription (ftree, path, FALSE); + else + return folder_etree_path_set_subscription (ftree, path, TRUE); } - +/* ** StoreData ************************************************************ */ + +typedef struct _StoreData StoreData; + +typedef void (*StoreDataStoreFunc) (StoreData *, CamelStore *, gpointer); + +struct _StoreData { + gchar *uri; + + FolderETree *ftree; + CamelStore *store; + + int request_id; + + GtkWidget *widget; + StoreDataStoreFunc store_func; + gpointer store_data; +}; + +static StoreData * +store_data_new (const gchar *uri) +{ + StoreData *sd; + + sd = g_new0 (StoreData, 1); + sd->uri = g_strdup (uri); + return sd; +} static void -folder_toggle_cb (ETree *tree, int row, ETreePath path, int col, GdkEvent *event, gpointer data) +sd_got_store (char *uri, CamelStore *store, gpointer user_data) { - SubscribeDialog *sc = SUBSCRIBE_DIALOG (data); - CamelFolderInfo *info = e_tree_memory_node_get_data (E_TREE_MEMORY(sc->folder_model), path); + StoreData *sd = (StoreData *) user_data; - if (folder_info_subscribed(sc, info)) - unsubscribe_folder_info (sc, info, path); - else - subscribe_folder_info (sc, info, path); + sd->store = store; - e_tree_model_node_data_changed (sc->folder_model, path); -} + if (store) /* we can have exceptions getting the store... server is down, eg */ + camel_object_ref (CAMEL_OBJECT (sd->store)); - + /* uh, so we might have a problem if this operation is cancelled. Unsure. */ + sd->request_id = 0; -#define EXAMPLE_DESCR "And the beast shall come forth surrounded by a roiling cloud of vengeance.\n" \ -" The house of the unbelievers shall be razed and they shall be scorched to the\n" \ -" earth. Their tags shall blink until the end of days. \n" \ -" from The Book of Mozilla, 12:10" + if (sd->store_func) + (sd->store_func) (sd, sd->store, sd->store_data); +} -static BonoboUIVerb verbs [] = { - /* File Menu */ - BONOBO_UI_VERB ("FileCloseWin", subscribe_close), +static void +store_data_async_get_store (StoreData *sd, StoreDataStoreFunc func, gpointer user_data) +{ + if (sd->request_id) { + printf ("Already loading store, nooping\n"); + return; + } - /* Edit Menu */ - BONOBO_UI_VERB ("EditSelectAll", subscribe_select_all), - BONOBO_UI_VERB ("EditInvertSelection", subscribe_invert_selection), - - /* Folder Menu / Toolbar */ - BONOBO_UI_VERB ("SubscribeFolder", subscribe_folders), - BONOBO_UI_VERB ("UnsubscribeFolder", unsubscribe_folders), + if (sd->store) { + func (sd, sd->store, user_data); + return; + } - /* Toolbar Specific */ - BONOBO_UI_VERB ("RefreshList", subscribe_refresh_list), + sd->store_func = func; + sd->store_data = user_data; + sd->request_id = mail_get_store (sd->uri, sd_got_store, sd); +} - BONOBO_UI_VERB_END -}; +static void +store_data_cancel_get_store (StoreData *sd) +{ + g_return_if_fail (sd->request_id); + + mail_msg_cancel (sd->request_id); + sd->request_id = 0; +} static void -store_cb (SubscribeDialog *sc, CamelStore *store, gpointer data) +sd_toggle_cb (ETree *tree, int row, ETreePath path, int col, GdkEvent *event, gpointer user_data) { - if (!store) - return; + StoreData *sd = (StoreData *) user_data; + + folder_etree_path_toggle_subscription (sd->ftree, path); +} - if (camel_store_supports_subscriptions (store)) { - camel_object_ref((CamelObject *)store); - sc->store_list = g_list_prepend (sc->store_list, store); - e_table_model_row_inserted (sc->store_model, 0); +static GtkWidget * +store_data_get_widget (StoreData *sd) +{ + GtkWidget *tree; + + if (!sd->store) { + printf ("store data can't get widget before getting store.\n"); + return NULL; } + + if (sd->widget) + return sd->widget; + + sd->ftree = folder_etree_new (sd->store); + + /* You annoy me, etree! */ + tree = gtk_widget_new (E_TREE_SCROLLED_TYPE, + "hadjustment", NULL, + "vadjustment", NULL, + NULL); + + tree = (GtkWidget *) e_tree_scrolled_construct_from_spec_file (E_TREE_SCROLLED (tree), + E_TREE_MODEL (sd->ftree), + subscribe_get_global_extras (), + EVOLUTION_ETSPECDIR "/subscribe-dialog.etspec", + NULL); + e_tree_root_node_set_visible (e_tree_scrolled_get_tree(E_TREE_SCROLLED(tree)), TRUE); + gtk_signal_connect (GTK_OBJECT (e_tree_scrolled_get_tree(E_TREE_SCROLLED (tree))), + "double_click", GTK_SIGNAL_FUNC (sd_toggle_cb), sd); + + gtk_object_unref (GTK_OBJECT (global_extras)); + + sd->widget = tree; + gtk_object_ref (GTK_OBJECT (sd->widget)); + + return sd->widget; } +typedef struct _selection_closure { + StoreData *sd; + enum { SET, CLEAR, TOGGLE } mode; +} selection_closure; + static void -populate_store_foreach (MailConfigService *service, SubscribeDialog *sc) +sd_subscribe_folder_foreach (int model_row, gpointer closure) { - g_return_if_fail (service->url != NULL); - - subscribe_do_get_store (sc, service->url, store_cb, NULL); + selection_closure *sc = (selection_closure *) closure; + StoreData *sd = sc->sd; + ETree *tree = e_tree_scrolled_get_tree(E_TREE_SCROLLED(sd->widget)); + ETreePath path = e_tree_node_at_row (tree, model_row); + + /* ignore results */ + switch (sc->mode) { + case SET: + folder_etree_path_set_subscription (sd->ftree, path, TRUE); + break; + case CLEAR: + folder_etree_path_set_subscription (sd->ftree, path, FALSE); + break; + case TOGGLE: + folder_etree_path_toggle_subscription (sd->ftree, path); + break; + } } static void -populate_store_list (SubscribeDialog *sc) +store_data_selection_set_subscription (StoreData *sd, gboolean subscribe) { - const GSList *news; - GSList *sources; - - sources = mail_config_get_sources (); - g_slist_foreach (sources, (GFunc)populate_store_foreach, sc); - g_slist_free (sources); + selection_closure sc; + ETree *tree; - news = mail_config_get_news (); - g_slist_foreach ((GSList *)news, (GFunc)populate_store_foreach, sc); - - e_table_model_changed (sc->store_model); + sc.sd = sd; + if (subscribe) + sc.mode = SET; + else + sc.mode = CLEAR; + + tree = e_tree_scrolled_get_tree (E_TREE_SCROLLED (sd->widget)); + e_tree_selected_row_foreach (tree, sd_subscribe_folder_foreach, &sc); } +#ifdef NEED_TOGGLE_SELECTION static void -subscribe_dialog_gui_init (SubscribeDialog *sc) -{ - ETableExtras *extras; - ECell *cell; - GdkPixbuf *toggles[2]; - BonoboUIComponent *component; - BonoboUIContainer *container; - GtkWidget *folder_search_widget; - BonoboControl *search_control; - CORBA_Environment ev; - - CORBA_exception_init (&ev); - - /* Construct the app */ - sc->app = bonobo_window_new ("subscribe-dialog", _("Manage Subscriptions")); - - /* Build the menu and toolbar */ - container = bonobo_ui_container_new (); - bonobo_ui_container_set_win (container, BONOBO_WINDOW (sc->app)); - - /* set up the bonobo stuff */ - component = bonobo_ui_component_new_default (); - bonobo_ui_component_set_container ( - component, bonobo_object_corba_objref (BONOBO_OBJECT (container))); +store_data_selection_toggle_subscription (StoreData *sd) +{ + selection_closure sc; + ETree *tree; - bonobo_ui_component_add_verb_list_with_data ( - component, verbs, sc); + sc.sd = sd; + sc.mode = TOGGLE; + + tree = e_tree_scrolled_get_tree (E_TREE_SCROLLED (sd->widget)); + e_tree_selected_row_foreach (tree, sd_subscribe_folder_foreach, &sc); +} +#endif - bonobo_ui_component_freeze (component, NULL); +static gboolean +store_data_mid_request (StoreData *sd) +{ + return (gboolean) sd->request_id; +} - bonobo_ui_util_set_ui (component, EVOLUTION_DATADIR, - "evolution-subscribe.xml", - "evolution-subscribe"); +static void +store_data_free (StoreData *sd) +{ + if (sd->request_id) + mail_msg_cancel (sd->request_id); - e_pixmaps_update (component, pixmaps); + if (sd->widget) + gtk_object_unref (GTK_OBJECT (sd->widget)); - bonobo_ui_component_thaw (component, NULL); + if (sd->ftree) + gtk_object_unref (GTK_OBJECT (sd->ftree)); - sc->table = gtk_table_new (1, 2, FALSE); + if (sd->store) + camel_object_unref ((CamelObject *) sd->store); - sc->hpaned = e_hpaned_new (); + g_free (sd->uri); + g_free (sd); +} - folder_search_widget = make_folder_search_widget (subscribe_search, sc); - gtk_widget_show_all (folder_search_widget); - search_control = bonobo_control_new (folder_search_widget); +/* ** yaay, SubscribeDialog ******************************************************* */ - bonobo_ui_component_object_set (component, "/Searchbar/FolderSearch", - bonobo_object_corba_objref (BONOBO_OBJECT (search_control)), - NULL); - - /* set our our contents */ -#if 0 - sc->description = html_new (TRUE); - put_html (GTK_HTML (sc->description), EXAMPLE_DESCR); +#define PARENT_TYPE (gtk_object_get_type ()) - gtk_table_attach (GTK_TABLE (sc->table), sc->description->parent->parent, - 0, 1, 0, 1, - GTK_FILL | GTK_EXPAND, - 0, - 0, 0); +#ifdef JUST_FOR_TRANSLATORS +static char *str = N_("Folder"); #endif - /* set up the store etable */ - sc->store_model = e_table_simple_new (store_etable_col_count, - store_etable_row_count, - store_etable_value_at, - store_etable_set_value_at, - store_etable_is_editable, - store_etable_duplicate_value, - store_etable_free_value, - store_etable_initialize_value, - store_etable_value_is_empty, - store_etable_value_to_string, - sc); +#define STORE_DATA_KEY "store-data" - extras = e_table_extras_new (); +struct _SubscribeDialogPrivate { + GladeXML *xml; + GList *store_list; - sc->store_etable = e_table_scrolled_new (E_TABLE_MODEL(sc->store_model), - extras, STORE_ETABLE_SPEC, NULL); + StoreData *current_store; + GtkWidget *current_widget; - gtk_object_sink (GTK_OBJECT (extras)); + GtkWidget *default_widget; + GtkWidget *none_item; + GtkWidget *search_entry; + GtkWidget *hbox; +}; - gtk_signal_connect (GTK_OBJECT (e_table_scrolled_get_table(E_TABLE_SCROLLED (sc->store_etable))), - "cursor_change", GTK_SIGNAL_FUNC (storage_selected_cb), - sc); +static GtkObjectClass *subscribe_dialog_parent_class; - /* set up the folder etable */ - sc->folder_model = e_tree_memory_callbacks_new (folder_etree_icon_at, +static void +sc_refresh_pressed (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); - folder_etree_column_count, + e_utf8_gtk_entry_set_text (GTK_ENTRY (sc->priv->search_entry), ""); - NULL, - NULL, + if (sc->priv->current_store) + folder_etree_clear_tree (sc->priv->current_store->ftree); +} - NULL, - NULL, +static void +sc_search_activated (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *store = sc->priv->current_store; + char *search; + GtkWidget *filter_radio; - folder_etree_value_at, - folder_etree_set_value_at, - folder_etree_is_editable, + filter_radio = glade_xml_get_widget (sc->priv->xml, "filter_radio"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (filter_radio), TRUE); - folder_etree_duplicate_value, - folder_etree_free_value, - folder_etree_init_value, - folder_etree_value_is_empty, - folder_etree_value_to_string, + if (!store) + return; - sc); + search = e_utf8_gtk_entry_get_text (GTK_ENTRY (widget)); + folder_etree_set_search (store->ftree, search); +} - e_tree_memory_set_expanded_default (E_TREE_MEMORY(sc->folder_model), TRUE); +static void +sc_subscribe_pressed (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *store = sc->priv->current_store; - sc->folder_root = e_tree_memory_node_insert (E_TREE_MEMORY(sc->folder_model), NULL, - 0, NULL); + if (!store) + return; - toggles[0] = gdk_pixbuf_new_from_xpm_data ((const char **)empty_xpm); - toggles[1] = gdk_pixbuf_new_from_xpm_data ((const char **)mark_xpm); + store_data_selection_set_subscription (store, TRUE); +} - extras = e_table_extras_new (); +static void +sc_unsubscribe_pressed (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *store = sc->priv->current_store; - cell = e_cell_text_new(NULL, GTK_JUSTIFY_LEFT); + if (!store) + return; - e_table_extras_add_cell (extras, "cell_text", cell); - e_table_extras_add_cell (extras, "cell_toggle", e_cell_toggle_new (0, 2, toggles)); - e_table_extras_add_cell (extras, "cell_tree", e_cell_tree_new(NULL, NULL, TRUE, cell)); + store_data_selection_set_subscription (store, FALSE); +} - gtk_object_set (GTK_OBJECT (cell), - "bold_column", FOLDER_COL_SUBSCRIBED, - NULL); +static void +sc_all_toggled (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *store = sc->priv->current_store; + + if (!store) + return; + + if (GTK_TOGGLE_BUTTON (widget)->active) + folder_etree_set_search (store->ftree, ""); +} + +static void +sc_filter_toggled (GtkWidget *widget, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *store = sc->priv->current_store; + + if (!store) + return; + + if (GTK_TOGGLE_BUTTON (widget)->active) + sc_search_activated (sc->priv->search_entry, sc); +} + +static void +populate_store_foreach (MailConfigService *service, SubscribeDialog *sc) +{ + StoreData *sd; - e_table_extras_add_pixbuf (extras, "subscribed-image", toggles[1]); + if (service->url == NULL || service->enabled == FALSE) + return; - sc->folder_etree = e_tree_scrolled_new (E_TREE_MODEL(sc->folder_model), - extras, FOLDER_ETREE_SPEC, NULL); + sd = store_data_new (service->url); + sc->priv->store_list = g_list_prepend (sc->priv->store_list, sd); +} - e_tree_root_node_set_visible (e_tree_scrolled_get_tree(E_TREE_SCROLLED(sc->folder_etree)), FALSE); +static void +menu_item_selected (GtkMenuItem *item, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + StoreData *sd = gtk_object_get_data (GTK_OBJECT (item), STORE_DATA_KEY); + GtkWidget *toggle; - gtk_object_sink (GTK_OBJECT (extras)); - gdk_pixbuf_unref(toggles[0]); - gdk_pixbuf_unref(toggles[1]); + g_return_if_fail (sd); - gtk_signal_connect (GTK_OBJECT (e_tree_scrolled_get_tree(E_TREE_SCROLLED (sc->folder_etree))), - "double_click", GTK_SIGNAL_FUNC (folder_toggle_cb), - sc); - gtk_table_attach ( - GTK_TABLE (sc->table), sc->folder_etree, - 0, 1, 1, 3, - GTK_FILL | GTK_EXPAND, - GTK_FILL | GTK_EXPAND, - 0, 0); + if (sd->widget == NULL) { + GtkWidget *widget; - e_paned_add1 (E_PANED (sc->hpaned), sc->store_etable); - e_paned_add2 (E_PANED (sc->hpaned), sc->table); - e_paned_set_position (E_PANED (sc->hpaned), DEFAULT_STORE_TABLE_WIDTH); + widget = store_data_get_widget (sd); + gtk_box_pack_start (GTK_BOX (sc->priv->hbox), widget, TRUE, TRUE, 0); + } - bonobo_window_set_contents (BONOBO_WINDOW (sc->app), sc->hpaned); + gtk_widget_hide (sc->priv->current_widget); + gtk_widget_hide (sc->priv->none_item); /* will happen redundantly... so what? */ + gtk_widget_show (sd->widget); + sc->priv->current_widget = sd->widget; + sc->priv->current_store = sd; + + if (sd->ftree->search) { + e_utf8_gtk_entry_set_text (GTK_ENTRY (sc->priv->search_entry), sd->ftree->search); + toggle = glade_xml_get_widget (sc->priv->xml, "filter_radio"); + } else { + e_utf8_gtk_entry_set_text (GTK_ENTRY (sc->priv->search_entry), ""); + toggle = glade_xml_get_widget (sc->priv->xml, "all_radio"); + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), TRUE); +} -#if 0 - gtk_widget_show (sc->description); -#endif +static void +dummy_item_selected (GtkMenuItem *item, gpointer user_data) +{ + SubscribeDialog *sc = SUBSCRIBE_DIALOG (user_data); + GtkWidget *all_toggle; - gtk_widget_show (sc->folder_etree); - gtk_widget_show (sc->table); - gtk_widget_show (sc->store_etable); - gtk_widget_show (sc->hpaned); + gtk_widget_hide (sc->priv->current_widget); + gtk_widget_show (sc->priv->default_widget); + sc->priv->current_widget = sc->priv->default_widget; + sc->priv->current_store = NULL; - /* FIXME: Session management and stuff? */ - gtk_window_set_default_size (GTK_WINDOW (sc->app), - DEFAULT_WIDTH, DEFAULT_HEIGHT); + e_utf8_gtk_entry_set_text (GTK_ENTRY (sc->priv->search_entry), ""); - populate_store_list (sc); + all_toggle = glade_xml_get_widget (sc->priv->xml, "all_radio"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (all_toggle), TRUE); +} + +/* wonderful */ + +static void +got_sd_store (StoreData *sd, CamelStore *store, gpointer data) +{ + if (store && camel_store_supports_subscriptions (store)) + gtk_widget_show (GTK_WIDGET (data)); +} + +/* FIXME: if there aren't any stores that are subscribable, the option + * menu will only have the "No server selected" item and the user will + * be confused. */ + +static void +populate_store_list (SubscribeDialog *sc) +{ + const GSList *news; + GSList *sources; + GList *iter; + GtkWidget *menu; + GtkWidget *omenu; + + sources = mail_config_get_sources (); + g_slist_foreach (sources, (GFunc) populate_store_foreach, sc); + g_slist_free (sources); + + news = mail_config_get_news (); + g_slist_foreach ((GSList *) news, (GFunc) populate_store_foreach, sc); + + menu = gtk_menu_new (); + + for (iter = sc->priv->store_list; iter; iter = iter->next) { + GtkWidget *item; + CamelURL *url; + gchar *string; + + url = camel_url_new (((StoreData *) iter->data)->uri, NULL); + string = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); + camel_url_free (url); + item = gtk_menu_item_new_with_label (string); + store_data_async_get_store (iter->data, got_sd_store, item); + gtk_object_set_data (GTK_OBJECT (item), STORE_DATA_KEY, iter->data); + gtk_signal_connect (GTK_OBJECT (item), "activate", menu_item_selected, sc); + g_free (string); + + gtk_menu_prepend (GTK_MENU (menu), item); + /*gtk_object_unref (GTK_OBJECT (item));*/ + } + + sc->priv->none_item = gtk_menu_item_new_with_label (_("No server has been selected")); + gtk_signal_connect (GTK_OBJECT (sc->priv->none_item), "activate", dummy_item_selected, sc); + gtk_widget_show (sc->priv->none_item); + gtk_menu_prepend (GTK_MENU (menu), sc->priv->none_item); + + gtk_widget_show (menu); + + omenu = glade_xml_get_widget (sc->priv->xml, "store_menu"); + gtk_option_menu_set_menu (GTK_OPTION_MENU (omenu), menu); + /*gtk_object_unref (GTK_OBJECT (menu));*/ } static void subscribe_dialog_destroy (GtkObject *object) { SubscribeDialog *sc; + GList *iter; sc = SUBSCRIBE_DIALOG (object); - /* free our folder information */ - e_tree_memory_node_remove (E_TREE_MEMORY(sc->folder_model), sc->folder_root); - gtk_object_unref (GTK_OBJECT (sc->folder_model)); - if (sc->folder_info) - camel_store_free_folder_info (sc->store, sc->folder_info); + for (iter = sc->priv->store_list; iter; iter = iter->next) { + if (store_data_mid_request (iter->data)) + store_data_cancel_get_store (iter->data); + store_data_free (iter->data); + } - /* free our store information */ - gtk_object_unref (GTK_OBJECT (sc->store_model)); - g_list_foreach (sc->store_list, (GFunc)gtk_object_unref, NULL); + g_list_free (sc->priv->store_list); - /* free our storage */ - if (sc->storage) - gtk_object_unref (GTK_OBJECT (sc->storage)); + gtk_object_unref (GTK_OBJECT (sc->priv->xml)); - /* free our search */ - if (sc->search_top) - g_free (sc->search_top); + g_free (sc->priv); subscribe_dialog_parent_class->destroy (object); } @@ -1072,37 +1473,86 @@ subscribe_dialog_class_init (GtkObjectClass *object_class) static void subscribe_dialog_init (GtkObject *object) { + SubscribeDialog *sc = SUBSCRIBE_DIALOG (object); + + sc->priv = g_new0 (SubscribeDialogPrivate, 1); +} + +static GtkWidget * +sc_create_default_widget (void) +{ + GtkWidget *label; + GtkWidget *viewport; + + label = gtk_label_new (_("Please select a server.")); + gtk_widget_show (label); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (viewport), label); + + return viewport; } static void -subscribe_dialog_construct (GtkObject *object, GNOME_Evolution_Shell shell) +subscribe_dialog_construct (GtkObject *object) { SubscribeDialog *sc = SUBSCRIBE_DIALOG (object); + GtkWidget *widget; + + /* Load the XML */ + + sc->priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/subscribe-dialog.glade", NULL); + sc->app = glade_xml_get_widget (sc->priv->xml, "Manage Subscriptions"); + sc->priv->hbox = glade_xml_get_widget (sc->priv->xml, "tree_box"); + sc->priv->search_entry = glade_xml_get_widget (sc->priv->xml, "search_entry"); + + /* create default view */ + + sc->priv->default_widget = sc_create_default_widget(); + sc->priv->current_widget = sc->priv->default_widget; + gtk_box_pack_start (GTK_BOX (sc->priv->hbox), sc->priv->default_widget, TRUE, TRUE, 0); + gtk_widget_show (sc->priv->default_widget); + + /* hook up some signals */ + + gtk_signal_connect (GTK_OBJECT (sc->priv->search_entry), "activate", sc_search_activated, sc); + + widget = glade_xml_get_widget (sc->priv->xml, "subscribe_button"); + gtk_signal_connect (GTK_OBJECT (widget), "clicked", sc_subscribe_pressed, sc); + + widget = glade_xml_get_widget (sc->priv->xml, "unsubscribe_button"); + gtk_signal_connect (GTK_OBJECT (widget), "clicked", sc_unsubscribe_pressed, sc); - /* - * Our instance data - */ - sc->shell = shell; - sc->store = NULL; - sc->storage = NULL; - sc->folder_info = NULL; - sc->store_list = NULL; - sc->search_top = NULL; + widget = glade_xml_get_widget (sc->priv->xml, "refresh_button"); + gtk_signal_connect (GTK_OBJECT (widget), "clicked", sc_refresh_pressed, sc); - subscribe_dialog_gui_init (sc); + widget = glade_xml_get_widget (sc->priv->xml, "all_radio"); + gtk_signal_connect (GTK_OBJECT (widget), "toggled", sc_all_toggled, sc); + + widget = glade_xml_get_widget (sc->priv->xml, "filter_radio"); + gtk_signal_connect (GTK_OBJECT (widget), "toggled", sc_filter_toggled, sc); + + /* Get the list of stores */ + + populate_store_list (sc); } -GtkWidget * -subscribe_dialog_new (GNOME_Evolution_Shell shell) +GtkObject * +subscribe_dialog_new (void) { SubscribeDialog *subscribe_dialog; - subscribe_dialog = gtk_type_new (subscribe_dialog_get_type ()); + subscribe_dialog = gtk_type_new (SUBSCRIBE_DIALOG_TYPE); + subscribe_dialog_construct (GTK_OBJECT (subscribe_dialog)); - subscribe_dialog_construct (GTK_OBJECT (subscribe_dialog), shell); - - return GTK_WIDGET (subscribe_dialog->app); + return GTK_OBJECT (subscribe_dialog); } E_MAKE_TYPE (subscribe_dialog, "SubscribeDialog", SubscribeDialog, subscribe_dialog_class_init, subscribe_dialog_init, PARENT_TYPE); +void +subscribe_dialog_run_and_close (SubscribeDialog *dialog) +{ + gnome_dialog_run_and_close (GNOME_DIALOG (dialog->app)); +} diff --git a/mail/subscribe-dialog.etspec b/mail/subscribe-dialog.etspec new file mode 100644 index 0000000000..1f5decbb36 --- /dev/null +++ b/mail/subscribe-dialog.etspec @@ -0,0 +1,9 @@ +<ETableSpecification cursor-mode="line" no-headers="true"> + <ETableColumn model_col="0" pixbuf="subscribed-image" expansion="0.0" minimum_width="16" resizable="false" cell="cell_toggle" compare="integer"/> + <ETableColumn model_col="1" _title="Folder" expansion="1.0" minimum_width="20" resizable="true" cell="cell_tree" compare="string"/> + <ETableState> + <column source="0"/> + <column source="1"/> + <grouping></grouping> + </ETableState> +</ETableSpecification> diff --git a/mail/subscribe-dialog.glade b/mail/subscribe-dialog.glade index 5169f450c3..472491b3e8 100644 --- a/mail/subscribe-dialog.glade +++ b/mail/subscribe-dialog.glade @@ -15,12 +15,13 @@ <widget> <class>GnomeDialog</class> - <name>subscriptions</name> - <visible>False</visible> - <title></title> + <name>Manage Subscriptions</name> + <title>Manage Subscriptions</title> <type>GTK_WINDOW_TOPLEVEL</type> <position>GTK_WIN_POS_NONE</position> - <modal>True</modal> + <modal>False</modal> + <default_width>484</default_width> + <default_height>423</default_height> <allow_shrink>False</allow_shrink> <allow_grow>True</allow_grow> <auto_shrink>False</auto_shrink> @@ -30,7 +31,7 @@ <widget> <class>GtkVBox</class> <child_name>GnomeDialog:vbox</child_name> - <name>dialog-vbox1</name> + <name>dialog-vbox2</name> <homogeneous>False</homogeneous> <spacing>8</spacing> <child> @@ -42,7 +43,7 @@ <widget> <class>GtkHButtonBox</class> <child_name>GnomeDialog:action_area</child_name> - <name>dialog-action_area1</name> + <name>dialog-action_area2</name> <layout_style>GTK_BUTTONBOX_END</layout_style> <spacing>8</spacing> <child_min_width>85</child_min_width> @@ -58,36 +59,19 @@ <widget> <class>GtkButton</class> - <name>button1</name> + <name>button9</name> <can_default>True</can_default> <can_focus>True</can_focus> - <stock_button>GNOME_STOCK_BUTTON_OK</stock_button> - </widget> - - <widget> - <class>GtkButton</class> - <name>button2</name> - <can_default>True</can_default> - <can_focus>True</can_focus> - <stock_button>GNOME_STOCK_BUTTON_APPLY</stock_button> - </widget> - - <widget> - <class>GtkButton</class> - <name>button3</name> - <can_default>True</can_default> - <has_default>True</has_default> - <can_focus>True</can_focus> - <has_focus>True</has_focus> - <stock_button>GNOME_STOCK_BUTTON_CANCEL</stock_button> + <stock_button>GNOME_STOCK_BUTTON_CLOSE</stock_button> </widget> </widget> <widget> <class>GtkVBox</class> - <name>vbox1</name> + <name>vbox2</name> + <border_width>3</border_width> <homogeneous>False</homogeneous> - <spacing>0</spacing> + <spacing>3</spacing> <child> <padding>0</padding> <expand>True</expand> @@ -95,199 +79,243 @@ </child> <widget> - <class>GtkTable</class> - <name>table</name> - <rows>3</rows> - <columns>2</columns> + <class>GtkHBox</class> + <name>hbox1</name> <homogeneous>False</homogeneous> - <row_spacing>0</row_spacing> - <column_spacing>3</column_spacing> + <spacing>0</spacing> <child> <padding>0</padding> - <expand>True</expand> + <expand>False</expand> <fill>True</fill> </child> <widget> <class>GtkLabel</class> - <name>lblDisplay</name> - <label>Display folders whose name contain:</label> - <justify>GTK_JUSTIFY_LEFT</justify> + <name>label1</name> + <label>Show _folders from server: </label> + <justify>GTK_JUSTIFY_CENTER</justify> <wrap>False</wrap> - <xalign>0</xalign> + <xalign>0.5</xalign> <yalign>0.5</yalign> <xpad>0</xpad> <ypad>0</ypad> + <default_focus_target>store_menu</default_focus_target> <child> - <left_attach>0</left_attach> - <right_attach>1</right_attach> - <top_attach>0</top_attach> - <bottom_attach>1</bottom_attach> - <xpad>0</xpad> - <ypad>0</ypad> - <xexpand>False</xexpand> - <yexpand>False</yexpand> - <xshrink>False</xshrink> - <yshrink>False</yshrink> - <xfill>False</xfill> - <yfill>False</yfill> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> </child> </widget> <widget> - <class>GtkEntry</class> - <name>txtSearch``</name> + <class>GtkOptionMenu</class> + <name>store_menu</name> <can_focus>True</can_focus> - <editable>True</editable> - <text_visible>True</text_visible> - <text_max_length>0</text_max_length> - <text></text> + <items></items> + <initial_choice>0</initial_choice> <child> - <left_attach>0</left_attach> - <right_attach>1</right_attach> - <top_attach>1</top_attach> - <bottom_attach>2</bottom_attach> - <xpad>0</xpad> - <ypad>0</ypad> - <xexpand>True</xexpand> - <yexpand>False</yexpand> - <xshrink>False</xshrink> - <yshrink>False</yshrink> - <xfill>True</xfill> - <yfill>False</yfill> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> </child> </widget> + </widget> + + <widget> + <class>GtkHBox</class> + <name>tree_box</name> + <homogeneous>False</homogeneous> + <spacing>0</spacing> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + + <widget> + <class>Placeholder</class> + </widget> <widget> - <class>GtkNotebook</class> - <name>notebook</name> - <can_focus>True</can_focus> - <show_tabs>True</show_tabs> - <show_border>True</show_border> - <tab_pos>GTK_POS_BOTTOM</tab_pos> - <scrollable>False</scrollable> - <tab_hborder>2</tab_hborder> - <tab_vborder>2</tab_vborder> - <popup_enable>False</popup_enable> + <class>GtkVBox</class> + <name>vbox3</name> + <border_width>3</border_width> + <homogeneous>False</homogeneous> + <spacing>3</spacing> <child> - <left_attach>0</left_attach> - <right_attach>1</right_attach> - <top_attach>2</top_attach> - <bottom_attach>3</bottom_attach> - <xpad>0</xpad> - <ypad>0</ypad> - <xexpand>False</xexpand> - <yexpand>True</yexpand> - <xshrink>False</xshrink> - <yshrink>False</yshrink> - <xfill>True</xfill> - <yfill>True</yfill> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + <pack>GTK_PACK_END</pack> </child> <widget> - <class>Custom</class> - <name>etableAll</name> - <creation_function>create_folderlist</creation_function> - <int1>0</int1> - <int2>0</int2> - <last_modification_time>Thu, 09 Nov 2000 23:31:36 GMT</last_modification_time> - </widget> - - <widget> <class>GtkLabel</class> - <child_name>Notebook:tab</child_name> - <name>lblAll</name> - <label>All Folders</label> + <name>label2</name> + <label> +</label> <justify>GTK_JUSTIFY_CENTER</justify> <wrap>False</wrap> <xalign>0.5</xalign> <yalign>0.5</yalign> <xpad>0</xpad> <ypad>0</ypad> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> </widget> <widget> - <class>Custom</class> - <name>etableSubscribed</name> - <creation_function>create_folderlist</creation_function> - <int1>1</int1> - <int2>0</int2> - <last_modification_time>Thu, 09 Nov 2000 23:30:19 GMT</last_modification_time> + <class>GtkVButtonBox</class> + <name>vbuttonbox2</name> + <layout_style>GTK_BUTTONBOX_SPREAD</layout_style> + <spacing>0</spacing> + <child_min_width>85</child_min_width> + <child_min_height>27</child_min_height> + <child_ipad_x>7</child_ipad_x> + <child_ipad_y>0</child_ipad_y> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + + <widget> + <class>GtkButton</class> + <name>subscribe_button</name> + <can_default>True</can_default> + <can_focus>True</can_focus> + <label>_Subscribe</label> + <relief>GTK_RELIEF_NORMAL</relief> + </widget> + + <widget> + <class>GtkButton</class> + <name>unsubscribe_button</name> + <can_default>True</can_default> + <can_focus>True</can_focus> + <label>_Unsubscribe</label> + <relief>GTK_RELIEF_NORMAL</relief> + </widget> </widget> <widget> - <class>GtkLabel</class> - <child_name>Notebook:tab</child_name> - <name>lblSubscribed</name> - <label>Subscribed</label> - <justify>GTK_JUSTIFY_CENTER</justify> - <wrap>False</wrap> - <xalign>0.5</xalign> - <yalign>0.5</yalign> - <xpad>0</xpad> - <ypad>0</ypad> + <class>GtkHSeparator</class> + <name>hseparator1</name> + <child> + <padding>14</padding> + <expand>False</expand> + <fill>False</fill> + </child> </widget> - </widget> - <widget> - <class>GtkButton</class> - <name>cmdQuery</name> - <can_focus>True</can_focus> - <label>Query</label> - <child> - <left_attach>1</left_attach> - <right_attach>2</right_attach> - <top_attach>1</top_attach> - <bottom_attach>2</bottom_attach> - <xpad>5</xpad> - <ypad>0</ypad> - <xexpand>False</xexpand> - <yexpand>False</yexpand> - <xshrink>False</xshrink> - <yshrink>False</yshrink> - <xfill>True</xfill> - <yfill>False</yfill> - </child> + <widget> + <class>GtkVButtonBox</class> + <name>vbuttonbox3</name> + <layout_style>GTK_BUTTONBOX_START</layout_style> + <spacing>10</spacing> + <child_min_width>85</child_min_width> + <child_min_height>27</child_min_height> + <child_ipad_x>7</child_ipad_x> + <child_ipad_y>0</child_ipad_y> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + + <widget> + <class>GtkButton</class> + <name>refresh_button</name> + <can_default>True</can_default> + <can_focus>True</can_focus> + <label> _Refresh List </label> + <relief>GTK_RELIEF_NORMAL</relief> + </widget> + </widget> </widget> + </widget> + + <widget> + <class>GtkFrame</class> + <name>frame1</name> + <label>Display options</label> + <label_xalign>0</label_xalign> + <shadow_type>GTK_SHADOW_ETCHED_IN</shadow_type> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>True</fill> + </child> <widget> - <class>GtkVButtonBox</class> - <name>vbuttonbox1</name> - <layout_style>GTK_BUTTONBOX_DEFAULT_STYLE</layout_style> - <spacing>0</spacing> - <child_min_width>85</child_min_width> - <child_min_height>27</child_min_height> - <child_ipad_x>7</child_ipad_x> - <child_ipad_y>0</child_ipad_y> - <child> - <left_attach>1</left_attach> - <right_attach>2</right_attach> - <top_attach>2</top_attach> - <bottom_attach>3</bottom_attach> - <xpad>0</xpad> - <ypad>0</ypad> - <xexpand>False</xexpand> - <yexpand>False</yexpand> - <xshrink>False</xshrink> - <yshrink>False</yshrink> - <xfill>True</xfill> - <yfill>True</yfill> - </child> + <class>GtkHBox</class> + <name>hbox3</name> + <border_width>3</border_width> + <homogeneous>False</homogeneous> + <spacing>3</spacing> + + <widget> + <class>GtkRadioButton</class> + <name>all_radio</name> + <can_focus>True</can_focus> + <label>All folders</label> + <active>False</active> + <draw_indicator>True</draw_indicator> + <group>view_type</group> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> + </widget> <widget> - <class>GtkButton</class> - <name>cmdSubscribe</name> - <can_default>True</can_default> + <class>GtkRadioButton</class> + <name>filter_radio</name> <can_focus>True</can_focus> - <label>Subscribe</label> + <label>Folders whose names begin with:</label> + <active>False</active> + <draw_indicator>True</draw_indicator> + <group>view_type</group> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> </widget> <widget> - <class>GtkButton</class> - <name>cmdUnsubscribe</name> - <can_default>True</can_default> + <class>GtkEntry</class> + <name>search_entry</name> <can_focus>True</can_focus> - <label>Unsubscribe</label> + <editable>True</editable> + <text_visible>True</text_visible> + <text_max_length>0</text_max_length> + <text></text> + <child> + <padding>0</padding> + <expand>True</expand> + <fill>True</fill> + </child> + </widget> + + <widget> + <class>GtkHButtonBox</class> + <name>hbuttonbox1</name> + <layout_style>GTK_BUTTONBOX_DEFAULT_STYLE</layout_style> + <spacing>30</spacing> + <child_min_width>85</child_min_width> + <child_min_height>27</child_min_height> + <child_ipad_x>7</child_ipad_x> + <child_ipad_y>0</child_ipad_y> + <child> + <padding>0</padding> + <expand>False</expand> + <fill>False</fill> + </child> </widget> </widget> </widget> diff --git a/mail/subscribe-dialog.h b/mail/subscribe-dialog.h index 2696acf2e2..10fa5d9f44 100644 --- a/mail/subscribe-dialog.h +++ b/mail/subscribe-dialog.h @@ -1,6 +1,7 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Authors: Chris Toshok <toshok@ximian.com> + * Peter Williams <peterw@ximian.com> * * Copyright 2000 Ximian, Inc. (www.ximian.com) * @@ -39,32 +40,12 @@ #define IS_SUBSCRIBE_DIALOG(o) (GTK_CHECK_TYPE ((o), SUBSCRIBE_DIALOG_TYPE)) #define IS_SUBSCRIBE_DIALOG_CLASS(k) (GTK_CHECK_CLASS_TYPE ((k), SUBSCRIBE_DIALOG_TYPE)) +typedef struct _SubscribeDialogPrivate SubscribeDialogPrivate; struct _SubscribeDialog { - GtkObject parent; + GtkObject parent; - GNOME_Evolution_Shell shell; - - GtkWidget *app; - - GtkWidget *hpaned; - GtkWidget *table; - GtkWidget *description; - - GtkWidget *store_etable; - ETableModel *store_model; - - GtkWidget *folder_etree; - ETreeModel *folder_model; - ETreePath folder_root; - - CamelStore *store; - EvolutionStorage *storage; - CamelFolderInfo *folder_info; - - GList *store_list; - - GtkWidget *search_entry; - char *search_top; + GtkWidget *app; + SubscribeDialogPrivate *priv; }; @@ -72,7 +53,8 @@ typedef struct { GtkObjectClass parent_class; } SubscribeDialogClass; -GtkType subscribe_dialog_get_type (void); -GtkWidget *subscribe_dialog_new (GNOME_Evolution_Shell shell); +GtkType subscribe_dialog_get_type (void); +GtkObject *subscribe_dialog_new (void); +void subscribe_dialog_run_and_close (SubscribeDialog *dialog); #endif /* _SUBSCRIBE_DIALOG_H_ */ |