aboutsummaryrefslogtreecommitdiffstats
path: root/e-util
diff options
context:
space:
mode:
authorMichael Zucci <zucchi@src.gnome.org>2004-09-20 13:59:55 +0800
committerMichael Zucci <zucchi@src.gnome.org>2004-09-20 13:59:55 +0800
commitef6a3af717132e0750f226fa8a0ee0f3c98e19f0 (patch)
treef4ed25f7a247a8ccb855a8d940777e7a3d21a3e3 /e-util
parent96111b1f1487ca3fe454b340a73ba927cc6bfb83 (diff)
downloadgsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.gz
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.bz2
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.lz
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.xz
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.zst
gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.zip
Merged notzed-eplugin-2-branch to head.
svn path=/trunk/; revision=27300
Diffstat (limited to 'e-util')
-rw-r--r--e-util/ChangeLog179
-rw-r--r--e-util/Makefile.am22
-rw-r--r--e-util/e-account.c196
-rw-r--r--e-util/e-account.h13
-rw-r--r--e-util/e-config.c1415
-rw-r--r--e-util/e-config.h374
-rw-r--r--e-util/e-event.c557
-rw-r--r--e-util/e-event.h238
-rw-r--r--e-util/e-menu.c901
-rw-r--r--e-util/e-menu.h323
-rw-r--r--e-util/e-plugin-mono.c198
-rw-r--r--e-util/e-plugin-mono.h27
-rw-r--r--e-util/e-plugin.c850
-rw-r--r--e-util/e-plugin.h221
-rw-r--r--e-util/e-popup.c799
-rw-r--r--e-util/e-popup.h300
16 files changed, 6565 insertions, 48 deletions
diff --git a/e-util/ChangeLog b/e-util/ChangeLog
index 80d2665544..e003553d5a 100644
--- a/e-util/ChangeLog
+++ b/e-util/ChangeLog
@@ -79,6 +79,185 @@
(d): turn off debugging
(expire_dir_rec): remove debug printfs
+2004-08-24 JP Rosevear <jpr@novell.com>
+
+ * Makefile.am: define EVOLUTION_PLUGINDIR
+
+ * e-plugin.c (e_plugin_get_type): add both a global and user
+ specifc eplug path as defaults
+ (e_plugin_load_plugins): just skip a directory if it can't be
+ opened
+
+2004-09-10 Not Zed <NotZed@Ximian.com>
+
+ * e-popup.c (emph_construct_menu): duh, setup the hook pointer.
+
+ * e-menu.c (e_menu_add_items): pass pixmaps and ui files to this
+ function now.
+ (e_menu_add_pixmap): removed, covered by above.
+ (e_menu_add_ui): removed, covered by above.
+ (emph_construct_menu): setup the hook pointer properly.
+
+ * e-menu.h: moved the pixmap and ui file structures to be public,
+ removed hte hook equivalents.
+
+2004-09-09 Not Zed <NotZed@Ximian.com>
+
+ * e-event.c (e_event_remove_items): implement a remove function.
+ (e_event_add_items): return a handle to pass to above.
+ (emph_event_handle): noop if we're disabled.
+
+ * e-popup.c (emph_popup_factory): noop if the
+ plugin is disabled.
+
+ * e-plugin.c (ep_init): setup an enabled bit for the plugin.
+ Preliminary work on being able to manage plugins.
+ (e_plugin_hook_enable): set hook enable state.
+ (e_plugin_enable): set plugin enable state.
+
+2004-09-07 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (ec_rebuild): pass the right old widget to the page
+ factory.
+ (ec_widget_destroy): unref the config and the target when done.
+ (e_config_create_widget): force the notebook to page 0, workaround
+ some other bug i don't understand.
+ (e_config_create_window): set dialog hint on druid.
+
+2004-09-06 Not Zed <NotZed@Ximian.com>
+
+ * e-account.c (e_account_import): emit a changed event on the dest.
+ (e_account_set_from_xml): emit a changed event if we were.
+
+ * e-config.c (ec_rebuild): add a table item for some type-safety.
+ remove/alter the asserts so the code attemps to fail gracefully
+ rather than just fail.
+
+2004-09-01 Not Zed <NotZed@Ximian.com>
+
+ * e-menu.[ch]:
+ * e-popup.[ch]:
+ * e-config.[ch]:
+ * e-event.[ch]: API syncrhonisation. Item callbacks now get the
+ root object, the item, and user-supplied data. Removed
+ now-redundant data and structures. Documentation updates.
+
+2004-08-30 Not Zed <NotZed@Ximian.com>
+
+ * e-popup.c (emph_construct_item): just build a popupitem
+ directly, use user_data to pass the activate method name.
+
+ * e-popup.h: removed epopuphookitem.
+
+ * e-popup.c (emph_construct_item): dont set hook on hookitem
+ anymore, dont need to set user_data either.
+
+ * e-popup.h: no longer pass parent pointer in EPopupHookItem, its
+ handled by add_items's data now.
+
+ * e-popup.c (ep_finalise): re-arrange and call freefunc with the
+ new args.
+ (e_popup_create_menu): change the activate data so we can pass the
+ new api arguments.
+ (ep_activate): changes for api changes.
+ (emph_popup_factory): no longer takes target arg, taken from the
+ popup, pass the popuphook to the add_items call.
+ (emph_popup_activate): changed for new argument types.
+
+ * e-popup.h: removed popup from epopupitem.
+
+ * e-popup.c (e_popup_add_static_items): make private/rename to
+ ep_add_static_items.
+ (ep_add_static_items): dont pass target, it is set on the epopup
+ structure.
+ (e_popup_add_items): change the freefunc to be an EPopupItemsFunc
+ and add a user-data field, and track the parent epopup.
+
+ * e-popup.h: change the activatefunc to pass the target and data
+ supplied to add_items.
+
+2004-08-25 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (e_config_new): removed, this shouldn't be here for
+ an abstract class.
+ (*): Added doco.
+
+2004-08-24 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (emph_construct_item): translate the label text.
+
+ * e-popup.c (emph_construct_item): translate the label text.
+
+ * e-plugin.c (e_plugin_xml_prop_domain): helper to get a property
+ translated.
+ (ep_construct): translate the name and description based on the
+ new domain tag. get the description as tag content not property.
+ (e_plugin_xml_content_domain): similar for xml node content.
+
+2004-08-24 Not Zed <NotZed@Ximian.com>
+
+ * e-account.c (init): setup some defaults for the account.
+
+ * e-config.c (e_config_create_widget): remove target arg actually.
+ (ec_rebuild): add druid page start/end pages. fixes.
+ (ec_druid_finish, ec_druid_cancel): handle finishing the druid.
+
+2004-08-23 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (ec_druid_prepare, ec_druid_prev, ec_druid_next):
+ handle druid navigation.
+
+2004-08-20 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (e_config_target_changed): add 'how' changed arg.
+ (e_config_create_widget): dont take target anymore, set that
+ separately.
+ (e_config_set_target): new virtual method to set the target.
+ (ec_set_target): implementation.
+
+2004-08-19 Not Zed <NotZed@Ximian.com>
+
+ * e-account.c (e_account_set_bool): only emit changed if it did.
+
+2004-08-17 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (e_config_page_check): fix a past-o.
+ (e_config_target_changed): treat the returned page for a druid
+ page factory as a gnomedruidpagestandard, so get_page works, etc.
+
+2004-08-16 Not Zed <NotZed@Ximian.com>
+
+ * e-event.[ch]: Added event hook and dispatch router.
+
+2004-08-13 Not Zed <NotZed@Ximian.com>
+
+ * e-plugin.c (e_plugin_load_plugins): don't take a path, load all
+ paths set.
+ (e_plugin_add_load_path): add a load path to the search path.
+ (e_plugin_get_type): setup the initial load path from environment
+ or defaults.
+ (e_plugin_xml_int): helper to get a prop in int format.
+
+2004-08-11 Not Zed <NotZed@Ximian.com>
+
+ * e-config.c (e_config_target_changed): handle DRUID root type.
+
+2004-08-05 Not Zed <NotZed@Ximian.com>
+
+ * e-account.c (class_init): add a changed signal.
+ (e_account_set_string, e_account_set_int, e_account_set_bool):
+ emit changed signal if it did.
+
+ * e-config.c (e_config_add_page_check): add a page verification
+ function. It will be called for the given page, or all pages, to
+ verify the page contains valid data.
+
+2004-07-29 Not Zed <NotZed@Ximian.com>
+
+ * e-account.c (e_account_get_string, e_account_get_int)
+ (e_account_get_bool, e_account_set_string, e_account_set_int)
+ (e_account_set_bool): implement. completely table driven.
+
2004-07-02 Chris Toshok <toshok@ximian.com>
[ fixes #60691 ]
diff --git a/e-util/Makefile.am b/e-util/Makefile.am
index 25e2d6a5a6..6f7923d86d 100644
--- a/e-util/Makefile.am
+++ b/e-util/Makefile.am
@@ -1,11 +1,17 @@
eutilincludedir = $(privincludedir)/e-util
econdincludedir = $(privincludedir)/e-conduit
+if ENABLE_MONO
+MONOHEADERS = e-plugin-mono.h
+MONOSOURCES = e-plugin-mono.c
+endif
+
INCLUDES = \
-I$(top_srcdir) \
-DEVOLUTION_IMAGES=\""$(imagesdir)"\" \
-DEVOLUTION_ICONSDIR=\""$(imagesdir)"\" \
-DEVOLUTION_CATEGORY_ICONS=\""$(imagesdir)/categories"\" \
+ -DEVOLUTION_PLUGINDIR=\""$(plugindir)"\" \
-DG_LOG_DOMAIN=\"e-utils\" \
$(GNOME_PILOT_CFLAGS) \
$(E_UTIL_CFLAGS)
@@ -19,10 +25,12 @@ eutilinclude_HEADERS = \
e-categories-config.h \
e-categories-master-list-wombat.h \
e-component-listener.h \
+ e-config.h \
e-config-listener.h \
e-corba-utils.h \
e-dialog-utils.h \
e-dialog-widgets.h \
+ e-event.h \
e-folder-map.h \
e-fsutils.h \
e-gtk-utils.h \
@@ -34,10 +42,13 @@ eutilinclude_HEADERS = \
e-list-iterator.h \
e-list.h \
e-memory.h \
+ e-menu.h \
e-mktemp.h \
e-msgport.h \
e-passwords.h \
e-path.h \
+ e-plugin.h \
+ e-popup.h \
e-request.h \
e-sexp.h \
e-signature.h \
@@ -46,7 +57,8 @@ eutilinclude_HEADERS = \
e-trie.h \
e-uid.h \
e-url.h \
- md5-utils.h
+ md5-utils.h \
+ $(MONOHEADERS)
libeutil_la_SOURCES = \
$(MARSHAL_GENERATED) \
@@ -57,10 +69,12 @@ libeutil_la_SOURCES = \
e-categories-config.c \
e-categories-master-list-wombat.c \
e-component-listener.c \
+ e-config.c \
e-config-listener.c \
e-corba-utils.c \
e-dialog-utils.c \
e-dialog-widgets.c \
+ e-event.c \
e-folder-map.c \
e-fsutils.c \
e-gtk-utils.c \
@@ -72,10 +86,13 @@ libeutil_la_SOURCES = \
e-list-iterator.c \
e-list.c \
e-memory.c \
+ e-menu.c \
e-mktemp.c \
e-msgport.c \
e-passwords.c \
e-path.c \
+ e-plugin.c \
+ e-popup.c \
e-request.c \
e-sexp.c \
e-signature.c \
@@ -86,7 +103,8 @@ libeutil_la_SOURCES = \
e-url.c \
eggtrayicon.c \
eggtrayicon.h \
- md5-utils.c
+ md5-utils.c \
+ $(MONOSOURCES)
MARSHAL_GENERATED = e-util-marshal.c e-util-marshal.h
@EVO_MARSHAL_RULE@
diff --git a/e-util/e-account.c b/e-util/e-account.c
index 37132017f0..61282ced8e 100644
--- a/e-util/e-account.c
+++ b/e-util/e-account.c
@@ -37,6 +37,13 @@
#define PARENT_TYPE G_TYPE_OBJECT
static GObjectClass *parent_class = NULL;
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
/*
lock mail accounts Relatively difficult -- involves redesign of the XML blobs which describe accounts
disable adding mail accounts Simple -- can be done with just a Gconf key and some UI work to make assoc. widgets unavailable
@@ -81,6 +88,16 @@ class_init (GObjectClass *object_class)
/* virtual method override */
object_class->finalize = finalize;
+
+ signals[CHANGED] =
+ g_signal_new("changed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EAccountClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
}
static void
@@ -89,6 +106,9 @@ init (EAccount *account)
account->id = g_new0 (EAccountIdentity, 1);
account->source = g_new0 (EAccountService, 1);
account->transport = g_new0 (EAccountService, 1);
+
+ account->source->auto_check = FALSE;
+ account->source->auto_check_time = 10;
}
static void
@@ -412,10 +432,11 @@ e_account_set_from_xml (EAccount *account, const char *xml)
xmlFreeDoc (doc);
+ g_signal_emit(account, signals[CHANGED], 0, -1);
+
return changed;
}
-
/**
* e_account_import:
* @dest: destination account object
@@ -481,8 +502,9 @@ e_account_import (EAccount *dest, EAccount *src)
dest->smime_encrypt_to_self = src->smime_encrypt_to_self;
g_free (dest->smime_encrypt_key);
dest->smime_encrypt_key = g_strdup (src->smime_encrypt_key);
-}
+ g_signal_emit(dest, signals[CHANGED], 0, -1);
+}
/**
* e_account_to_xml:
@@ -641,44 +663,55 @@ static struct _system_info {
{ "transport", 1<<EAP_LOCK_TRANSPORT },
};
-static struct {
+#define TYPE_STRING (1)
+#define TYPE_INT (2)
+#define TYPE_BOOL (3)
+#define TYPE_MASK (0xff)
+#define TYPE_STRUCT (1<<8)
+
+static struct _account_info {
guint32 perms;
-} account_perms[E_ACCOUNT_ITEM_LAST] = {
- { /* E_ACCOUNT_ID_NAME, */ },
- { /* E_ACCOUNT_ID_ADDRESS, */ },
- { /* E_ACCOUNT_ID_REPLY_TO, */ },
- { /* E_ACCOUNT_ID_ORGANIZATION */ },
- { /* E_ACCOUNT_ID_SIGNATURE */ 1<<EAP_LOCK_SIGNATURE },
-
- { /* E_ACCOUNT_SOURCE_URL */ 1<<EAP_LOCK_SOURCE },
- { /* E_ACCOUNT_SOURCE_KEEP_ON_SERVER */ },
- { /* E_ACCOUNT_SOURCE_AUTO_CHECK */ 1<<EAP_LOCK_AUTOCHECK },
- { /* E_ACCOUNT_SOURCE_AUTO_CHECK_TIME */ 1<<EAP_LOCK_AUTOCHECK },
- { /* E_ACCOUNT_SOURCE_SAVE_PASSWD */ 1<<EAP_LOCK_SAVE_PASSWD },
-
- { /* E_ACCOUNT_TRANSPORT_URL */ 1<<EAP_LOCK_TRANSPORT },
- { /* E_ACCOUNT_TRANSPORT_SAVE_PASSWD */ 1<<EAP_LOCK_SAVE_PASSWD },
-
- { /* E_ACCOUNT_DRAFTS_FOLDER_URI */ 1<<EAP_LOCK_DEFAULT_FOLDERS },
- { /* E_ACCOUNT_SENT_FOLDER_URI */ 1<<EAP_LOCK_DEFAULT_FOLDERS },
-
- { /* E_ACCOUNT_CC_ALWAYS */ },
- { /* E_ACCOUNT_CC_ADDRS */ },
-
- { /* E_ACCOUNT_BCC_ALWAYS */ },
- { /* E_ACCOUNT_BCC_ADDRS */ },
-
- { /* E_ACCOUNT_PGP_KEY */ },
- { /* E_ACCOUNT_PGP_ENCRYPT_TO_SELF */ },
- { /* E_ACCOUNT_PGP_ALWAYS_SIGN */ },
- { /* E_ACCOUNT_PGP_NO_IMIP_SIGN */ },
- { /* E_ACCOUNT_PGP_ALWAYS_TRUST */ },
-
- { /* E_ACCOUNT_SMIME_SIGN_KEY */ },
- { /* E_ACCOUNT_SMIME_ENCRYPT_KEY */ },
- { /* E_ACCOUNT_SMIME_SIGN_DEFAULT */ },
- { /* E_ACCOUNT_SMIME_ENCRYPT_TO_SELF */ },
- { /* E_ACCOUNT_SMIME_ENCRYPE_DEFAULT */ },
+ guint32 type;
+ unsigned int offset;
+ unsigned int struct_offset;
+} account_info[E_ACCOUNT_ITEM_LAST] = {
+ { /* E_ACCOUNT_NAME */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, name) },
+
+ { /* E_ACCOUNT_ID_NAME, */ 0, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, id), G_STRUCT_OFFSET(EAccountIdentity, name) },
+ { /* E_ACCOUNT_ID_ADDRESS, */ 0, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, id), G_STRUCT_OFFSET(EAccountIdentity, address) },
+ { /* E_ACCOUNT_ID_REPLY_TO, */ 0, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, id), G_STRUCT_OFFSET(EAccountIdentity, reply_to) },
+ { /* E_ACCOUNT_ID_ORGANIZATION */ 0, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, id), G_STRUCT_OFFSET(EAccountIdentity, organization) },
+ { /* E_ACCOUNT_ID_SIGNATURE */ 1<<EAP_LOCK_SIGNATURE, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, id), G_STRUCT_OFFSET(EAccountIdentity, sig_uid) },
+
+ { /* E_ACCOUNT_SOURCE_URL */ 1<<EAP_LOCK_SOURCE, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, source), G_STRUCT_OFFSET(EAccountService, url) },
+ { /* E_ACCOUNT_SOURCE_KEEP_ON_SERVER */ 0, TYPE_BOOL|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, source), G_STRUCT_OFFSET(EAccountService, keep_on_server) },
+ { /* E_ACCOUNT_SOURCE_AUTO_CHECK */ 1<<EAP_LOCK_AUTOCHECK, TYPE_BOOL|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, source), G_STRUCT_OFFSET(EAccountService, auto_check) },
+ { /* E_ACCOUNT_SOURCE_AUTO_CHECK_TIME */ 1<<EAP_LOCK_AUTOCHECK, TYPE_INT|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, source), G_STRUCT_OFFSET(EAccountService, auto_check_time) },
+ { /* E_ACCOUNT_SOURCE_SAVE_PASSWD */ 1<<EAP_LOCK_SAVE_PASSWD, TYPE_BOOL|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, source), G_STRUCT_OFFSET(EAccountService, save_passwd) },
+
+ { /* E_ACCOUNT_TRANSPORT_URL */ 1<<EAP_LOCK_TRANSPORT, TYPE_STRING|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, transport), G_STRUCT_OFFSET(EAccountService, url) },
+ { /* E_ACCOUNT_TRANSPORT_SAVE_PASSWD */ 1<<EAP_LOCK_SAVE_PASSWD, TYPE_BOOL|TYPE_STRUCT, G_STRUCT_OFFSET(EAccount, transport), G_STRUCT_OFFSET(EAccountService, save_passwd) },
+
+ { /* E_ACCOUNT_DRAFTS_FOLDER_URI */ 1<<EAP_LOCK_DEFAULT_FOLDERS, TYPE_STRING, G_STRUCT_OFFSET(EAccount, drafts_folder_uri) },
+ { /* E_ACCOUNT_SENT_FOLDER_URI */ 1<<EAP_LOCK_DEFAULT_FOLDERS, TYPE_STRING, G_STRUCT_OFFSET(EAccount, sent_folder_uri) },
+
+ { /* E_ACCOUNT_CC_ALWAYS */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, always_cc) },
+ { /* E_ACCOUNT_CC_ADDRS */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, cc_addrs) },
+
+ { /* E_ACCOUNT_BCC_ALWAYS */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, always_bcc) },
+ { /* E_ACCOUNT_BCC_ADDRS */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, bcc_addrs) },
+
+ { /* E_ACCOUNT_PGP_KEY */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, pgp_key) },
+ { /* E_ACCOUNT_PGP_ENCRYPT_TO_SELF */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, pgp_encrypt_to_self) },
+ { /* E_ACCOUNT_PGP_ALWAYS_SIGN */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, pgp_always_sign) },
+ { /* E_ACCOUNT_PGP_NO_IMIP_SIGN */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, pgp_no_imip_sign) },
+ { /* E_ACCOUNT_PGP_ALWAYS_TRUST */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, pgp_always_trust) },
+
+ { /* E_ACCOUNT_SMIME_SIGN_KEY */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, smime_sign_key) },
+ { /* E_ACCOUNT_SMIME_ENCRYPT_KEY */ 0, TYPE_STRING, G_STRUCT_OFFSET(EAccount, smime_encrypt_key) },
+ { /* E_ACCOUNT_SMIME_SIGN_DEFAULT */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, smime_sign_default) },
+ { /* E_ACCOUNT_SMIME_ENCRYPT_TO_SELF */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, smime_encrypt_to_self) },
+ { /* E_ACCOUNT_SMIME_ENCRYPT_DEFAULT */ 0, TYPE_BOOL, G_STRUCT_OFFSET(EAccount, smime_encrypt_default) },
};
static GHashTable *ea_option_table;
@@ -761,6 +794,89 @@ ea_setting_setup(void)
g_object_unref(gconf);
}
+/* look up the item in the structure or the substructure using our table of reflection data */
+#define addr(ea, type) \
+ ((account_info[type].type & TYPE_STRUCT)? \
+ (((char **)(((char *)ea)+account_info[type].offset))[0] + account_info[type].struct_offset): \
+ (((char *)ea)+account_info[type].offset))
+
+const char *e_account_get_string(EAccount *ea, e_account_item_t type)
+{
+ return *((const char **)addr(ea, type));
+}
+
+int e_account_get_int(EAccount *ea, e_account_item_t type)
+{
+ return *((int *)addr(ea, type));
+}
+
+gboolean e_account_get_bool(EAccount *ea, e_account_item_t type)
+{
+ return *((gboolean *)addr(ea, type));
+}
+
+static void
+dump_account(EAccount *ea)
+{
+ char *xml;
+
+ printf("Account changed\n");
+ xml = e_account_to_xml(ea);
+ printf(" ->\n%s\n", xml);
+ g_free(xml);
+}
+
+/* TODO: should it return true if it changed? */
+void e_account_set_string(EAccount *ea, e_account_item_t type, const char *val)
+{
+ char **p;
+
+ if (!e_account_writable(ea, type)) {
+ g_warning("Trying to set non-writable option account value");
+ } else {
+ p = (char **)addr(ea, type);
+ printf("Setting string %d: old '%s' new '%s'\n", type, *p, val);
+ if (*p != val
+ && (*p == NULL || val == NULL || strcmp(*p, val) != 0)) {
+ g_free(*p);
+ *p = g_strdup(val);
+
+ dump_account(ea);
+ g_signal_emit(ea, signals[CHANGED], 0, type);
+ }
+ }
+}
+
+void e_account_set_int(EAccount *ea, e_account_item_t type, int val)
+{
+ if (!e_account_writable(ea, type)) {
+ g_warning("Trying to set non-writable option account value");
+ } else {
+ int *p = (int *)addr(ea, type);
+
+ if (*p != val) {
+ *p = val;
+ dump_account(ea);
+ g_signal_emit(ea, signals[CHANGED], 0, type);
+ }
+ }
+}
+
+void e_account_set_bool(EAccount *ea, e_account_item_t type, gboolean val)
+{
+ if (!e_account_writable(ea, type)) {
+ g_warning("Trying to set non-writable option account value");
+ } else {
+ gboolean *p = (gboolean *)addr(ea, type);
+
+ if (*p != val) {
+ *p = val;
+ dump_account(ea);
+ g_signal_emit(ea, signals[CHANGED], 0, type);
+ }
+ }
+}
+
gboolean
e_account_writable_option(EAccount *ea, const char *protocol, const char *option)
{
@@ -789,5 +905,5 @@ e_account_writable(EAccount *ea, e_account_item_t type)
{
ea_setting_setup();
- return (account_perms[type].perms & ea_perms) == 0;
+ return (account_info[type].perms & ea_perms) == 0;
}
diff --git a/e-util/e-account.h b/e-util/e-account.h
index 07c0ccd5d7..0e153ca993 100644
--- a/e-util/e-account.h
+++ b/e-util/e-account.h
@@ -29,13 +29,15 @@
#define E_IS_ACCOUNT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((obj), E_TYPE_ACCOUNT))
typedef enum _e_account_item_t {
+ E_ACCOUNT_NAME,
+
E_ACCOUNT_ID_NAME,
E_ACCOUNT_ID_ADDRESS,
E_ACCOUNT_ID_REPLY_TO,
E_ACCOUNT_ID_ORGANIZATION,
E_ACCOUNT_ID_SIGNATURE,
- E_ACCOUNT_SOURCE_URL,
+ E_ACCOUNT_SOURCE_URL, /* what about separating out host/user/path settings?? sigh */
E_ACCOUNT_SOURCE_KEEP_ON_SERVER,
E_ACCOUNT_SOURCE_AUTO_CHECK,
E_ACCOUNT_SOURCE_AUTO_CHECK_TIME,
@@ -63,7 +65,7 @@ typedef enum _e_account_item_t {
E_ACCOUNT_SMIME_ENCRYPT_KEY,
E_ACCOUNT_SMIME_SIGN_DEFAULT,
E_ACCOUNT_SMIME_ENCRYPT_TO_SELF,
- E_ACCOUNT_SMIME_ENCRYPE_DEFAULT,
+ E_ACCOUNT_SMIME_ENCRYPT_DEFAULT,
E_ACCOUNT_ITEM_LAST
} e_account_item_t;
@@ -123,6 +125,7 @@ typedef struct _EAccount {
typedef struct {
GObjectClass parent_class;
+ void (*changed)(EAccount *, int field);
} EAccountClass;
@@ -136,15 +139,13 @@ void e_account_import (EAccount *dest, EAccount *src);
char *e_account_to_xml (EAccount *account);
char *e_account_uid_from_xml (const char *xml);
-#if 0
const char *e_account_get_string(EAccount *, e_account_item_t type);
int e_account_get_int(EAccount *, e_account_item_t type);
gboolean e_account_get_bool(EAccount *, e_account_item_t type);
void e_account_set_string(EAccount *, e_account_item_t type, const char *);
-void e_account_set_int(EAccount *, e_account_item_t type, const char *);
-void e_account_set_bool(EAccount *, e_account_item_t type, const char *);
-#endif
+void e_account_set_int(EAccount *, e_account_item_t type, int);
+void e_account_set_bool(EAccount *, e_account_item_t type, gboolean);
gboolean e_account_writable(EAccount *ea, e_account_item_t type);
gboolean e_account_writable_option(EAccount *ea, const char *protocol, const char *option);
diff --git a/e-util/e-config.c b/e-util/e-config.c
new file mode 100644
index 0000000000..4ca7974623
--- /dev/null
+++ b/e-util/e-config.c
@@ -0,0 +1,1415 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2004 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include <gtk/gtknotebook.h>
+#include <gtk/gtkvbox.h>
+#include <gtk/gtkhbox.h>
+#include <gtk/gtktable.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkframe.h>
+#include <gtk/gtkalignment.h>
+
+#include <libgnomeui/gnome-druid.h>
+#include <libgnomeui/gnome-druid-page-standard.h>
+#include <libgnomeui/gnome-druid-page-edge.h>
+
+#include "e-config.h"
+
+#include <e-util/e-icon-factory.h>
+
+#include <libgnome/gnome-i18n.h>
+
+struct _EConfigFactory {
+ struct _EConfigFactory *next, *prev;
+
+ char *id;
+ EConfigFactoryFunc factory;
+ void *factory_data;
+};
+
+struct _menu_node {
+ struct _menu_node *next, *prev;
+
+ GSList *menu;
+ EConfigItemsFunc free;
+ EConfigItemsFunc abort;
+ EConfigItemsFunc commit;
+ void *data;
+};
+
+struct _widget_node {
+ struct _widget_node *next, *prev;
+
+ EConfig *config;
+
+ struct _menu_node *context;
+ EConfigItem *item;
+ struct _GtkWidget *widget; /* widget created by the factory, if any */
+ struct _GtkWidget *frame; /* if created by us */
+
+ int empty:1; /* set if empty (i.e. hidden) */
+};
+
+struct _check_node {
+ struct _check_node *next, *prev;
+
+ char *pageid;
+ EConfigCheckFunc check;
+ void *data;
+};
+
+struct _EConfigPrivate {
+ EDList menus;
+ EDList widgets;
+ EDList checks;
+
+ struct _widget_node *druid_page; /* current druid page if using the druid */
+};
+
+static GObjectClass *ep_parent;
+
+static void
+ep_init(GObject *o)
+{
+ EConfig *emp = (EConfig *)o;
+ struct _EConfigPrivate *p;
+
+ p = emp->priv = g_malloc0(sizeof(struct _EConfigPrivate));
+
+ e_dlist_init(&p->menus);
+ e_dlist_init(&p->widgets);
+ e_dlist_init(&p->checks);
+}
+
+static void
+ep_finalise(GObject *o)
+{
+ EConfig *emp = (EConfig *)o;
+ struct _EConfigPrivate *p = emp->priv;
+ struct _menu_node *mnode;
+ struct _widget_node *wn;
+ struct _check_node *cn;
+
+ printf("finalising EConfig %p\n", o);
+
+ g_free(emp->id);
+
+ while ((mnode = (struct _menu_node *)e_dlist_remhead(&p->menus))) {
+ if (mnode->free)
+ mnode->free(emp, mnode->menu, mnode->data);
+
+ g_free(mnode);
+ }
+
+ while ( (wn = (struct _widget_node *)e_dlist_remhead(&p->widgets)) ) {
+ g_free(wn);
+ }
+
+ while ( (cn = (struct _check_node *)e_dlist_remhead(&p->widgets)) ) {
+ g_free(cn->pageid);
+ g_free(cn);
+ }
+
+ g_free(p);
+
+ ((GObjectClass *)ep_parent)->finalize(o);
+}
+
+static void
+ec_target_free(EConfig *ep, EConfigTarget *t)
+{
+ g_free(t);
+ g_object_unref(ep);
+}
+
+static void
+ec_set_target(EConfig *emp, EConfigTarget *target)
+{
+ if (emp->target)
+ e_config_target_free(emp, target);
+
+ emp->target = target;
+}
+
+static void
+ep_class_init(GObjectClass *klass)
+{
+ printf("EConfig class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ klass->finalize = ep_finalise;
+ ((EConfigClass *)klass)->set_target = ec_set_target;
+ ((EConfigClass *)klass)->target_free = ec_target_free;
+}
+
+static void
+ep_base_init(GObjectClass *klass)
+{
+ e_dlist_init(&((EConfigClass *)klass)->factories);
+}
+
+/**
+ * e_config_get_type:
+ *
+ * Standard GObject method. Used to subclass for the concrete
+ * implementations.
+ *
+ * Return value: EConfig type.
+ **/
+GType
+e_config_get_type(void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof(EConfigClass),
+ (GBaseInitFunc)ep_base_init, NULL,
+ (GClassInitFunc)ep_class_init, NULL, NULL,
+ sizeof(EConfig), 0,
+ (GInstanceInitFunc)ep_init
+ };
+ ep_parent = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EConfig", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_config_construct:
+ * @ep: The instance to initialise.
+ * @type: The type of configuration manager, @E_CONFIG_BOOK or
+ * @E_CONFIG_DRUID.
+ * @id: The name of the configuration window this manager drives.
+ *
+ * Used by implementing classes to initialise base parameters.
+ *
+ * Return value: @ep is returned.
+ **/
+EConfig *e_config_construct(EConfig *ep, int type, const char *id)
+{
+ g_assert(type == E_CONFIG_BOOK || type == E_CONFIG_DRUID);
+
+ ep->type = type;
+ ep->id = g_strdup(id);
+
+ return ep;
+}
+
+/**
+ * e_config_add_items:
+ * @ec: An initialised implementing instance of EConfig.
+ * @items: A list of EConfigItem's to add to the configuration manager
+ * @ec.
+ * @commitfunc: If supplied, called to commit the configuration items
+ * to persistent storage.
+ * @abortfunc: If supplied, called to abort/undo the storage of these
+ * items permanently.
+ * @freefunc: If supplied, called to free the item list (and/or items)
+ * once they are no longer needed.
+ * @data: Data for the callback methods.
+ *
+ * Add new EConfigItems to the configuration window. Nothing will be
+ * done with them until the widget is built.
+ *
+ * TODO: perhaps commit and abort should just be signals.
+ **/
+void
+e_config_add_items(EConfig *ec, GSList *items, EConfigItemsFunc commitfunc, EConfigItemsFunc abortfunc, EConfigItemsFunc freefunc, void *data)
+{
+ struct _menu_node *node;
+
+ node = g_malloc(sizeof(*node));
+ node->menu = items;
+ node->commit = commitfunc;
+ node->abort = abortfunc;
+ node->free = freefunc;
+ node->data = data;
+ e_dlist_addtail(&ec->priv->menus, (EDListNode *)node);
+}
+
+/**
+ * e_config_add_page_check:
+ * @ec: Initialised implemeting instance of EConfig.
+ * @pageid: pageid to check.
+ * @check: checking callback.
+ * @data: user-data for the callback.
+ *
+ * Add a page-checking function callback. It will be called to validate the
+ * data in the given page or pages. If @pageid is NULL then it will be called
+ * to validate every page, or the whole configuration window.
+ *
+ * In the latter case, the pageid in the callback will be either the
+ * specific page being checked, or NULL when the whole config window
+ * is being checked.
+ *
+ * The page check function is used to validate input before allowing
+ * the druid to continue or the notebook to close.
+ **/
+void
+e_config_add_page_check(EConfig *ec, const char *pageid, EConfigCheckFunc check, void *data)
+{
+ struct _check_node *cn;
+
+ cn = g_malloc0(sizeof(*cn));
+ cn->pageid = g_strdup(pageid);
+ cn->check = check;
+ cn->data = data;
+
+ e_dlist_addtail(&ec->priv->checks, (EDListNode *)cn);
+}
+
+static void
+ec_add_static_items(EConfig *ec)
+{
+ struct _EConfigFactory *f;
+ EConfigClass *klass = (EConfigClass *)G_OBJECT_GET_CLASS(ec);
+
+ f = (struct _EConfigFactory *)klass->factories.head;
+ while (f->next) {
+ if (f->id == NULL
+ || !strcmp(f->id, ec->id)) {
+ f->factory(ec, f->factory_data);
+ }
+ f = f->next;
+ }
+}
+
+static int
+ep_cmp(const void *ap, const void *bp)
+{
+ struct _widget_node *a = *((void **)ap);
+ struct _widget_node *b = *((void **)bp);
+
+ return strcmp(a->item->path, b->item->path);
+}
+
+static void
+ec_druid_check_current(EConfig *ec)
+{
+ g_return_if_fail(ec->priv->druid_page != NULL);
+
+ if (e_config_page_check(ec, ec->priv->druid_page->item->path)) {
+ gtk_widget_set_sensitive(((GnomeDruid *)ec->widget)->next, TRUE);
+ } else {
+ gtk_widget_set_sensitive(((GnomeDruid *)ec->widget)->next, FALSE);
+ }
+}
+
+static void
+ec_druid_cancel(GnomeDruid *druid, struct _widget_node *wn)
+{
+ printf("finishing druid, calling abort\n");
+ e_config_abort(wn->config);
+
+ if (wn->config->window)
+ gtk_widget_destroy(wn->config->window);
+}
+
+static void
+ec_druid_finish(GnomeDruidPage *page, GnomeDruid *druid, struct _widget_node *wn)
+{
+ printf("finishing druid, calling commit\n");
+ e_config_commit(wn->config);
+
+ /* TODO: allow the commit to fail? Do we care? */
+ if (wn->config->window)
+ gtk_widget_destroy(wn->config->window);
+}
+
+static void
+ec_druid_prepare(GnomeDruidPage *page, GnomeDruid *druid, struct _widget_node *wn)
+{
+ printf("prepare page '%s'\n", wn->item->path);
+ wn->config->priv->druid_page = wn;
+ ec_druid_check_current(wn->config);
+}
+
+static gboolean
+ec_druid_prev(GnomeDruidPage *page, GnomeDruid *druid, struct _widget_node *wn)
+{
+ EConfig *ec = wn->config;
+
+ printf("prev page from '%s'\n", wn->item->path);
+ if (wn->prev) {
+ for (wn = wn->prev;wn->prev;wn=wn->prev) {
+ if (!wn->empty
+ && (wn->item->type == E_CONFIG_PAGE
+ || wn->item->type == E_CONFIG_PAGE_START
+ || wn->item->type == E_CONFIG_PAGE_FINISH))
+ break;
+ }
+ }
+
+ if (wn->prev) {
+ printf(" is %s\n",wn->item->path);
+ gnome_druid_set_page((GnomeDruid *)ec->widget, (GnomeDruidPage *)wn->frame);
+ ec->priv->druid_page = wn;
+ } else {
+ /* do we need to indicate first? */
+ ec->priv->druid_page = NULL;
+ }
+
+ return wn->prev != NULL;
+}
+
+static gboolean
+ec_druid_next(GnomeDruidPage *page, GnomeDruid *druid, struct _widget_node *wn)
+{
+ EConfig *ec = wn->config;
+
+ printf("next page from '%s'\n", wn->item->path);
+ if (wn->next) {
+ for (wn = wn->next;wn->next;wn=wn->next) {
+ if (!wn->empty
+ && (wn->item->type == E_CONFIG_PAGE
+ || wn->item->type == E_CONFIG_PAGE_START
+ || wn->item->type == E_CONFIG_PAGE_FINISH))
+ break;
+ }
+ }
+
+ if (wn->next) {
+ printf(" is %s\n",wn->item->path);
+ gnome_druid_set_page((GnomeDruid *)ec->widget, (GnomeDruidPage *)wn->frame);
+ ec->priv->druid_page = wn;
+ } else {
+ /* do we need to indicate last? */
+ ec->priv->druid_page = NULL;
+ }
+
+ return wn->next != NULL;
+}
+
+static void
+ec_rebuild(EConfig *emp)
+{
+ struct _EConfigPrivate *p = emp->priv;
+ struct _widget_node *wn, *sectionnode = NULL, *pagenode = NULL;
+ GtkWidget *book = NULL, *page = NULL, *section = NULL, *root = NULL, *druid = NULL;
+ int pageno = 0, sectionno = 0, itemno = 0;
+
+ printf("target changed, rebuilding:\n");
+
+ /* TODO: This code is pretty complex, and will probably just
+ * become more complex with time. It could possibly be split
+ * into the two base types, but there would be a lot of code
+ * duplication */
+
+ for (wn = (struct _widget_node *)p->widgets.head;wn->next;wn=wn->next) {
+ struct _EConfigItem *item = wn->item;
+ GtkWidget *w;
+
+ printf(" '%s'\n", item->path);
+
+ /* If the last section doesn't contain anything, hide it */
+ if (sectionnode != NULL
+ && (item->type == E_CONFIG_PAGE_START
+ || item->type == E_CONFIG_PAGE_FINISH
+ || item->type == E_CONFIG_PAGE
+ || item->type == E_CONFIG_SECTION
+ || item->type == E_CONFIG_SECTION_TABLE)) {
+ if ( (sectionnode->empty = itemno == 0) ) {
+ gtk_widget_hide(sectionnode->frame);
+ sectionno--;
+ } else
+ gtk_widget_show(sectionnode->frame);
+ printf("%s section '%s' [sections=%d]\n", sectionnode->empty?"hiding":"showing", sectionnode->item->path, sectionno);
+ }
+
+ /* If the last page doesn't contain anything, hide it */
+ if (pagenode != NULL
+ && (item->type == E_CONFIG_PAGE_START
+ || item->type == E_CONFIG_PAGE_FINISH
+ || item->type == E_CONFIG_PAGE)) {
+ if ( (pagenode->empty = sectionno == 0) ) {
+ gtk_widget_hide(pagenode->frame);
+ pageno--;
+ } else
+ gtk_widget_show(pagenode->frame);
+ printf("%s page '%s' [section=%d]\n", pagenode->empty?"hiding":"showing", pagenode->item->path, pageno);
+ }
+
+ /* Now process the item */
+ switch (item->type) {
+ case E_CONFIG_BOOK:
+ case E_CONFIG_DRUID:
+ /* Only one of BOOK or DRUID may be define, it
+ is used by the defining code to mark the
+ type of the config window. It is
+ cross-checked with the code's defined
+ type. */
+ if (root != NULL) {
+ g_warning("EConfig book/druid redefined at: %s", item->path);
+ break;
+ }
+
+ if (wn->widget == NULL) {
+ if (item->type != emp->type) {
+ g_warning("EConfig book/druid type mismatch");
+ break;
+ }
+ if (item->factory) {
+ root = item->factory(emp, item, NULL, wn->widget, wn->context->data);
+ } else if (item->type == E_CONFIG_BOOK) {
+ root = book = gtk_notebook_new();
+ } else if (item->type == E_CONFIG_DRUID) {
+ root = druid = gnome_druid_new();
+ } else
+ abort();
+
+ if (item->type == E_CONFIG_DRUID)
+ g_signal_connect(root, "cancel", G_CALLBACK(ec_druid_cancel), wn);
+
+ emp->widget = root;
+ wn->widget = root;
+ } else {
+ root = wn->widget;
+ }
+
+ if (item->type == E_CONFIG_BOOK)
+ book = root;
+ else
+ druid = root;
+
+ page = NULL;
+ pagenode = NULL;
+ section = NULL;
+ sectionnode = NULL;
+ pageno = 0;
+ sectionno = 0;
+ break;
+ case E_CONFIG_PAGE_START:
+ case E_CONFIG_PAGE_FINISH:
+ if (root == NULL) {
+ g_warning("EConfig page defined before container widget: %s", item->path);
+ break;
+ }
+ if (emp->type != E_CONFIG_DRUID) {
+ g_warning("EConfig druid start/finish pages can't be used on E_CONFIG_BOOKs");
+ break;
+ }
+
+ if (wn->widget == NULL) {
+ if (item->factory) {
+ page = item->factory(emp, item, root, wn->frame, wn->context->data);
+ } else {
+ page = gnome_druid_page_edge_new(item->type == E_CONFIG_PAGE_START?GNOME_EDGE_START:GNOME_EDGE_FINISH);
+ gtk_widget_show(page);
+ gnome_druid_page_edge_set_title((GnomeDruidPageEdge *)page, item->label);
+ gnome_druid_insert_page((GnomeDruid *)druid, pagenode?(GnomeDruidPage *)pagenode->frame:NULL, (GnomeDruidPage *)page);
+ }
+ if (item->type == E_CONFIG_PAGE_FINISH) {
+ g_signal_connect(page, "back", G_CALLBACK(ec_druid_prev), wn);
+ g_signal_connect(page, "finish", G_CALLBACK(ec_druid_finish), wn);
+ } else
+ g_signal_connect(page, "next", G_CALLBACK(ec_druid_next), wn);
+ wn->frame = page;
+ wn->widget = page;
+ }
+ pageno++;
+ page = NULL;
+ pagenode = wn; /* need this for previous page linking */
+ section = NULL;
+ sectionnode = NULL;
+ sectionno = 1; /* never want to hide these */
+ break;
+ case E_CONFIG_PAGE: {
+ int connect = 0; /* connect druid signals */
+
+ /* CONFIG_PAGEs depend on the config type.
+ E_CONFIG_BOOK:
+ The page is a VBox, stored in the notebook.
+ E_CONFIG_DRUID
+ The page is a GnomeDruidPageStandard,
+ any sections automatically added are added to
+ the vbox inside it. */
+ sectionno = 0;
+ if (root == NULL) {
+ g_warning("EConfig page defined before container widget: %s", item->path);
+ break;
+ }
+
+ if (item->factory) {
+ page = item->factory(emp, item, root, wn->frame, wn->context->data);
+ if (emp->type == E_CONFIG_DRUID) {
+ if (page) {
+ g_assert(GNOME_IS_DRUID_PAGE_STANDARD(page));
+ connect = wn->frame != page;
+ wn->frame = page;
+ page = ((GnomeDruidPageStandard *)page)->vbox;
+ } else
+ wn->frame = page;
+ } else {
+ wn->frame = page;
+ }
+ if (page)
+ sectionno = 1;
+ } else if (wn->widget == NULL) {
+ if (emp->type == E_CONFIG_DRUID) {
+ w = gnome_druid_page_standard_new();
+ gtk_widget_show(w);
+ gnome_druid_page_standard_set_title((GnomeDruidPageStandard *)w, item->label);
+ gnome_druid_insert_page((GnomeDruid *)druid, pagenode?(GnomeDruidPage *)pagenode->frame:NULL, (GnomeDruidPage *)w);
+ wn->frame = w;
+ page = ((GnomeDruidPageStandard *)w)->vbox;
+ connect = TRUE;
+ } else {
+ w = gtk_label_new(item->label);
+ gtk_widget_show(w);
+ page = gtk_vbox_new(FALSE, 12);
+ gtk_container_set_border_width((GtkContainer *)page, 12);
+ gtk_widget_show(page);
+ gtk_notebook_insert_page((GtkNotebook *)book, page, w, pageno);
+ wn->frame = page;
+ }
+ } else
+ page = wn->widget;
+
+ printf("page %d:%s widget %p\n", pageno, item->path, w);
+
+ if (wn->widget && wn->widget != page) {
+ printf("destroy old widget for page '%s'\n", item->path);
+ gtk_widget_destroy(wn->widget);
+ }
+
+ if (connect) {
+ g_signal_connect(wn->frame, "next", G_CALLBACK(ec_druid_next), wn);
+ g_signal_connect(wn->frame, "back", G_CALLBACK(ec_druid_prev), wn);
+ /* GnomeDruid bug, need to connect_after */
+ g_signal_connect_after(wn->frame, "prepare", G_CALLBACK(ec_druid_prepare), wn);
+ }
+
+ pageno++;
+ pagenode = wn;
+ section = NULL;
+ sectionnode = NULL;
+ wn->widget = page;
+ break; }
+ case E_CONFIG_SECTION:
+ case E_CONFIG_SECTION_TABLE:
+ /* The section factory is always called with
+ the parent vbox object. Even for druid
+ pages. */
+ if (page == NULL) {
+ g_warning("EConfig section '%s' has no parent page", item->path);
+ section = NULL;
+ goto nopage;
+ }
+
+ itemno = 0;
+ if (item->factory) {
+ section = item->factory(emp, item, page, wn->widget, wn->context->data);
+ wn->frame = section;
+ itemno = 1;
+
+ if (section
+ && ((item->type == E_CONFIG_SECTION && !GTK_IS_BOX(section))
+ || (item->type == E_CONFIG_SECTION_TABLE && !GTK_IS_TABLE(section))))
+ g_warning("ECofnig section type is wrong");
+ } else if (wn->widget == NULL) {
+ GtkWidget *frame;
+ GtkWidget *label = NULL;
+
+ if (item->label) {
+ char *txt = g_strdup_printf("<span weight=\"bold\">%s</span>", item->label);
+
+ label = g_object_new(gtk_label_get_type(),
+ "label", txt,
+ "use_markup", TRUE,
+ "xalign", 0.0, NULL);
+ g_free(txt);
+ }
+
+ if (item->type == E_CONFIG_SECTION)
+ section = gtk_vbox_new(FALSE, 6);
+ else
+ section = gtk_table_new(1, 1, FALSE);
+
+ frame = g_object_new(gtk_frame_get_type(),
+ "shadow_type", GTK_SHADOW_NONE,
+ "label_widget", label,
+ "child", g_object_new(gtk_alignment_get_type(),
+ "left_padding", 12,
+ "top_padding", 6,
+ "child", section, NULL),
+ NULL);
+ gtk_widget_show_all(frame);
+ gtk_box_pack_start((GtkBox *)page, frame, FALSE, FALSE, 0);
+ wn->frame = frame;
+ } else {
+ section = wn->widget;
+ }
+ nopage:
+ if (wn->widget && wn->widget != section) {
+ printf("destroy old widget for section '%s'\n", item->path);
+ gtk_widget_destroy(wn->widget);
+ }
+
+ sectionno++;
+ wn->widget = section;
+ sectionnode = wn;
+ break;
+ case E_CONFIG_ITEM:
+ case E_CONFIG_ITEM_TABLE:
+ /* ITEMs are called with the section parent.
+ The type depends on the section type,
+ either a GtkTable, or a GtkVBox */
+ w = NULL;
+ if (section == NULL)
+ g_warning("EConfig item has no parent section: %s", item->path);
+ else if ((item->type == E_CONFIG_ITEM && !GTK_IS_BOX(section))
+ || (item->type == E_CONFIG_ITEM_TABLE && !GTK_IS_TABLE(section)))
+ g_warning("EConfig item parent type is incorrect: %s", item->path);
+ else if (item->factory)
+ w = item->factory(emp, item, section, wn->widget, wn->context->data);
+
+ printf("item %d:%s widget %p\n", itemno, item->path, w);
+
+ if (wn->widget && wn->widget != w) {
+ printf("destroy old widget for item '%s'\n", item->path);
+ gtk_widget_destroy(wn->widget);
+ }
+
+ wn->widget = w;
+ if (w)
+ itemno++;
+ break;
+ }
+ }
+
+ if (book) {
+ /* make this depend on flags?? */
+ if (gtk_notebook_get_n_pages((GtkNotebook *)book) == 1) {
+ gtk_notebook_set_show_tabs((GtkNotebook *)book, FALSE);
+ gtk_notebook_set_show_border((GtkNotebook *)book, FALSE);
+ }
+ }
+}
+
+/**
+ * e_config_set_target:
+ * @emp: An initialised EConfig.
+ * @target: A target allocated from @emp.
+ *
+ * Sets the target object for the config window. Generally the target
+ * is set only once, and will supply its own "changed" signal which
+ * can be used to drive the modal. This is a virtual method so that
+ * the implementing class can connect to the changed signal and
+ * initiate a e_config_target_changed() call where appropriate.
+ **/
+void
+e_config_set_target(EConfig *emp, EConfigTarget *target)
+{
+ if (emp->target != target)
+ ((EConfigClass *)G_OBJECT_GET_CLASS(emp))->set_target(emp, target);
+}
+
+static void
+ec_widget_destroy(GtkWidget *w, EConfig *ec)
+{
+ if (ec->target) {
+ e_config_target_free(ec, ec->target);
+ ec->target = NULL;
+ }
+
+ g_object_unref(ec);
+}
+
+/**
+ * e_config_create_widget:
+ * @emp: An initialised EConfig object.
+ *
+ * Create the widget described by @emp. Only the core widget
+ * appropriate for the given type is created, i.e. a GtkNotebook for
+ * the E_CONFIG_BOOK type and a GnomeDruid for the E_CONFIG_DRUID
+ * type.
+ *
+ * This object will be self-driving, but will not close itself once
+ * complete.
+ *
+ * Unless reffed otherwise, the management object @emp will be
+ * finalised when the widget is.
+ *
+ * Return value: The widget, also available in @emp.widget
+ **/
+GtkWidget *
+e_config_create_widget(EConfig *emp)
+{
+ struct _EConfigPrivate *p = emp->priv;
+ struct _menu_node *mnode;
+ GPtrArray *items = g_ptr_array_new();
+ GSList *l;
+ /*char *domain = NULL;*/
+ int i;
+
+ g_assert(emp->target != NULL);
+
+ ec_add_static_items(emp);
+
+ /* FIXME: need to override old ones with new names */
+ for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
+ for (l=mnode->menu; l; l = l->next) {
+ struct _EConfigItem *item = l->data;
+ struct _widget_node *wn = g_malloc0(sizeof(*wn));
+
+ wn->item = item;
+ wn->context = mnode;
+ wn->config = emp;
+ g_ptr_array_add(items, wn);
+ }
+
+ qsort(items->pdata, items->len, sizeof(items->pdata[0]), ep_cmp);
+
+ for (i=0;i<items->len;i++) {
+ struct _widget_node *wn = items->pdata[i];
+
+ e_dlist_addtail(&p->widgets, (EDListNode *)wn);
+ }
+
+ g_ptr_array_free(items, TRUE);
+ ec_rebuild(emp);
+
+ /* auto-unref it */
+ g_signal_connect(emp->widget, "destroy", G_CALLBACK(ec_widget_destroy), emp);
+
+ /* FIXME: for some reason ec_rebuild puts the widget on page 1, this is just to override that */
+ if (emp->type == E_CONFIG_BOOK)
+ gtk_notebook_set_current_page((GtkNotebook *)emp->widget, 0);
+
+ return emp->widget;
+}
+
+static void
+ec_dialog_response(GtkWidget *d, int id, EConfig *ec)
+{
+ if (id == GTK_RESPONSE_OK)
+ e_config_commit(ec);
+ else
+ e_config_abort(ec);
+
+ gtk_widget_destroy(d);
+}
+
+/**
+ * e_config_create_window:
+ * @emp: Initialised and configured EMConfig derived instance.
+ * @parent: Parent window or NULL.
+ * @title: Title of window or dialog.
+ *
+ * Create a managed GtkWindow object from @emp. This window will be
+ * fully driven by the EConfig @emp. If @emp.type is
+ * @E_CONFIG_DRUID, then this will be a toplevel GtkWindow containing
+ * a GnomeDruid. If it is @E_CONFIG_BOOK then it will be a GtkDialog
+ * containing a Nnotebook.
+ *
+ * Unless reffed otherwise, the management object @emp will be
+ * finalised when the widget is.
+ *
+ * Return value: The window widget. This is also stored in @emp.window.
+ **/
+GtkWidget *
+e_config_create_window(EConfig *emp, struct _GtkWindow *parent, const char *title)
+{
+ GtkWidget *w;
+
+ e_config_create_widget(emp);
+
+ if (emp->type == E_CONFIG_BOOK) {
+ w = gtk_dialog_new_with_buttons(title, parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OK, GTK_RESPONSE_OK,
+ NULL);
+ g_signal_connect(w, "response", G_CALLBACK(ec_dialog_response), emp);
+ gtk_box_pack_start((GtkBox *)((GtkDialog *)w)->vbox, emp->widget, TRUE, TRUE, 0);
+ } else {
+ /* response is handled directly by the druid stuff */
+ w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_container_add((GtkContainer *)w, emp->widget);
+ gtk_window_set_type_hint((GtkWindow *)w, GDK_WINDOW_TYPE_HINT_DIALOG);
+ }
+
+ emp->window = w;
+ gtk_widget_show(w);
+
+ return w;
+}
+
+/**
+ * e_config_target_changed:
+ * @emp:
+ * @how:
+ *
+ * Indicate that the target has changed. This may be called by the
+ * self-aware target itself, or by the driving code. If @how is
+ * %E_CONFIG_TARGET_CHANGED_REBUILD, then the entire configuration
+ * widget may be recreated based on the changed target.
+ *
+ * This is used to sensitise Druid next/back buttons and the Apply
+ * button for the Notebook mode.
+ **/
+void e_config_target_changed(EConfig *emp, e_config_target_change_t how)
+{
+ if (how == E_CONFIG_TARGET_CHANGED_REBUILD)
+ ec_rebuild(emp);
+
+ if (emp->type == E_CONFIG_DRUID) {
+ if (emp->priv->druid_page)
+ ec_druid_check_current(emp);
+ } else {
+ if (emp->window) {
+ if (e_config_page_check(emp, NULL)) {
+ gtk_dialog_set_response_sensitive((GtkDialog *)emp->window, GTK_RESPONSE_OK, TRUE);
+ } else {
+ gtk_dialog_set_response_sensitive((GtkDialog *)emp->window, GTK_RESPONSE_OK, FALSE);
+ }
+ }
+ }
+
+ /* virtual method/signal? */
+}
+
+/**
+ * e_config_abort:
+ * @ec:
+ *
+ * Signify that the stateful configuration changes must be discarded
+ * to all listeners. This is used by self-driven druid or notebook, or
+ * may be used by code using the widget directly.
+ **/
+void e_config_abort(EConfig *ec)
+{
+ struct _EConfigPrivate *p = ec->priv;
+ struct _menu_node *mnode;
+
+ /* TODO: should these just be signals? */
+
+ for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
+ if (mnode->abort)
+ mnode->abort(ec, mnode->menu, mnode->data);
+}
+
+/**
+ * e_config_commit:
+ * @ec:
+ *
+ * Signify that the stateful configuration changes should be saved.
+ * This is used by the self-driven druid or notebook, or may be used
+ * by code driving the widget directly.
+ **/
+void e_config_commit(EConfig *ec)
+{
+ struct _EConfigPrivate *p = ec->priv;
+ struct _menu_node *mnode;
+
+ /* TODO: should these just be signals? */
+
+ for (mnode = (struct _menu_node *)p->menus.head;mnode->next;mnode=mnode->next)
+ if (mnode->commit)
+ mnode->commit(ec, mnode->menu, mnode->data);
+}
+
+/**
+ * e_config_page_check:
+ * @ec:
+ * @pageid: The path of the page item.
+ *
+ * Check that a given page is complete. If @pageid is NULL, then check
+ * the whole config. No check is made that the page actually exists.
+ *
+ * Return value: FALSE if the data is inconsistent/incomplete.
+ **/
+gboolean e_config_page_check(EConfig *ec, const char *pageid)
+{
+ struct _EConfigPrivate *p = ec->priv;
+ struct _check_node *mnode;
+
+ for (mnode = (struct _check_node *)p->checks.head;mnode->next;mnode=mnode->next)
+ if ((pageid == NULL
+ || mnode->pageid == NULL
+ || strcmp(mnode->pageid, pageid) == 0)
+ && !mnode->check(ec, pageid, mnode->data))
+ return FALSE;
+
+ return TRUE;
+}
+
+/**
+ * e_config_page_get:
+ * @ec:
+ * @pageid: The path of the page item.
+ *
+ * Retrieve the page widget corresponding to @pageid.
+ *
+ * Return value: The page widget. It will be the root GtkNotebook
+ * container or the GnomeDruidPage object.
+ **/
+GtkWidget *e_config_page_get(EConfig *ec, const char *pageid)
+{
+ struct _widget_node *wn;
+
+ for (wn = (struct _widget_node *)ec->priv->widgets.head;wn->next;wn=wn->next)
+ if (!wn->empty
+ && (wn->item->type == E_CONFIG_PAGE
+ || wn->item->type == E_CONFIG_PAGE_START
+ || wn->item->type == E_CONFIG_PAGE_FINISH)
+ && !strcmp(wn->item->path, pageid))
+ return wn->frame;
+
+ return NULL;
+}
+
+/**
+ * e_config_page_next:
+ * @ec:
+ * @pageid: The path of the page item.
+ *
+ * Find the path of the next visible page after @pageid. If @pageid
+ * is NULL then find the first visible page.
+ *
+ * Return value: The path of the next page, or @NULL if @pageid was the
+ * last configured and visible page.
+ **/
+const char *e_config_page_next(EConfig *ec, const char *pageid)
+{
+ struct _widget_node *wn;
+ int found;
+
+ found = pageid == NULL ? 1:0;
+ for (wn = (struct _widget_node *)ec->priv->widgets.head;wn->next;wn=wn->next)
+ if (!wn->empty
+ && (wn->item->type == E_CONFIG_PAGE
+ || wn->item->type == E_CONFIG_PAGE_START
+ || wn->item->type == E_CONFIG_PAGE_FINISH)) {
+ if (found)
+ return wn->item->path;
+ else if (strcmp(wn->item->path, pageid) == 0)
+ found = 1;
+ }
+
+ return NULL;
+}
+
+/**
+ * e_config_page_next:
+ * @ec:
+ * @pageid: The path of the page item.
+ *
+ * Find the path of the previous visible page before @pageid. If @pageid
+ * is NULL then find the last visible page.
+ *
+ * Return value: The path of the previous page, or @NULL if @pageid was the
+ * first configured and visible page.
+ **/
+const char *e_config_page_prev(EConfig *ec, const char *pageid)
+{
+ struct _widget_node *wn;
+ int found;
+
+ found = pageid == NULL ? 1:0;
+ for (wn = (struct _widget_node *)ec->priv->widgets.tailpred;wn->prev;wn=wn->prev)
+ if (!wn->empty
+ && (wn->item->type == E_CONFIG_PAGE
+ || wn->item->type == E_CONFIG_PAGE_START
+ || wn->item->type == E_CONFIG_PAGE_FINISH)) {
+ if (found)
+ return wn->item->path;
+ else if (strcmp(wn->item->path, pageid) == 0)
+ found = 1;
+ }
+
+ return NULL;
+}
+
+/* ********************************************************************** */
+
+/**
+ * e_config_class_add_factory:
+ * @klass: Implementing class pointer.
+ * @id: The name of the configuration window you're interested in.
+ * This may be NULL to be called for all windows.
+ * @func: An EConfigFactoryFunc to call when the window @id is being
+ * created.
+ * @data: Callback data.
+ *
+ * Add a config factory which will be called to add_items() any
+ * extra items's if wants to, to the current Config window.
+ *
+ * TODO: Make the id a pattern?
+ *
+ * Return value: A handle to the factory.
+ **/
+EConfigFactory *
+e_config_class_add_factory(EConfigClass *klass, const char *id, EConfigFactoryFunc func, void *data)
+{
+ struct _EConfigFactory *f = g_malloc0(sizeof(*f));
+
+ f->id = g_strdup(id);
+ f->factory = func;
+ f->factory_data = data;
+ e_dlist_addtail(&klass->factories, (EDListNode *)f);
+
+ return f;
+}
+
+/**
+ * e_config_class_remove_factory:
+ * @f: Handle from :class_add_factory() call.
+ *
+ * Remove a config factory. The handle @f may only be removed once.
+ **/
+void
+e_config_class_remove_factory(EConfigClass *klass, EConfigFactory *f)
+{
+ e_dlist_remove((EDListNode *)f);
+ g_free(f->id);
+ g_free(f);
+}
+
+/**
+ * e_config_target_new:
+ * @ep: Parent EConfig object.
+ * @type: type, up to implementor
+ * @size: Size of object to allocate.
+ *
+ * Allocate a new config target suitable for this class. Implementing
+ * classes will define the actual content of the target.
+ **/
+void *e_config_target_new(EConfig *ep, int type, size_t size)
+{
+ EConfigTarget *t;
+
+ g_assert(size >= sizeof(EConfigTarget));
+
+ t = g_malloc0(size);
+ t->config = ep;
+ g_object_ref(ep);
+ t->type = type;
+
+ return t;
+}
+
+/**
+ * e_config_target_free:
+ * @ep: Parent EConfig object.
+ * @o: The target to fre.
+ *
+ * Free a target. The implementing class can override this method to
+ * free custom targets.
+ **/
+void
+e_config_target_free(EConfig *ep, void *o)
+{
+ EConfigTarget *t = o;
+
+ ((EConfigClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
+}
+
+/* ********************************************************************** */
+
+/* Config menu plugin handler */
+
+/*
+<e-plugin
+ class="com.ximian.mail.plugin.config:1.0"
+ id="com.ximian.mail.plugin.config.item:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ name="imap"
+ description="IMAP4 and IMAP4v1 mail store">
+ <hook class="com.ximian.mail.configMenu:1.0"
+ handler="HandleConfig">
+ <menu id="any" target="select">
+ <item
+ type="item|toggle|radio|image|submenu|bar"
+ active
+ path="foo/bar"
+ label="label"
+ icon="foo"
+ activate="ep_view_emacs"/>
+ </menu>
+ </extension>
+
+*/
+
+static void *emph_parent_class;
+#define emph ((EConfigHook *)eph)
+
+static const EPluginHookTargetKey ech_item_types[] = {
+ { "book", E_CONFIG_BOOK },
+ { "druid", E_CONFIG_DRUID },
+
+ { "page", E_CONFIG_PAGE },
+ { "page_start", E_CONFIG_PAGE_START },
+ { "page_finish", E_CONFIG_PAGE_FINISH },
+ { "section", E_CONFIG_SECTION },
+ { "section_table", E_CONFIG_SECTION_TABLE },
+ { "item", E_CONFIG_ITEM },
+ { "item_table", E_CONFIG_ITEM_TABLE },
+ { 0 },
+};
+
+static void
+ech_commit(EConfig *ec, GSList *items, void *data)
+{
+ struct _EConfigHookGroup *group = data;
+
+ if (group->commit)
+ e_plugin_invoke(group->hook->hook.plugin, group->commit, ec->target);
+}
+
+static void
+ech_abort(EConfig *ec, GSList *items, void *data)
+{
+ struct _EConfigHookGroup *group = data;
+
+ if (group->abort)
+ e_plugin_invoke(group->hook->hook.plugin, group->abort, ec->target);
+}
+
+static void
+ech_config_factory(EConfig *emp, void *data)
+{
+ struct _EConfigHookGroup *group = data;
+
+ printf("config factory called %s\n", group->id?group->id:"all menus");
+
+ if (emp->target->type != group->target_type)
+ return;
+
+ if (group->items)
+ e_config_add_items(emp, group->items, ech_commit, ech_abort, NULL, group);
+}
+
+static void
+emph_free_item(struct _EConfigItem *item)
+{
+ g_free(item->path);
+ g_free(item->label);
+ g_free(item->user_data);
+ g_free(item);
+}
+
+static void
+emph_free_group(struct _EConfigHookGroup *group)
+{
+ g_slist_foreach(group->items, (GFunc)emph_free_item, NULL);
+ g_slist_free(group->items);
+
+ g_free(group->id);
+ g_free(group);
+}
+
+static struct _GtkWidget *
+ech_config_widget_factory(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, void *data)
+{
+ struct _EConfigHookGroup *group = data;
+ EConfigHookItemFactoryData hdata;
+
+ hdata.config = ec;
+ hdata.item = item;
+ hdata.target = ec->target;
+ hdata.parent = parent;
+ hdata.old = old;
+
+ return (struct _GtkWidget *)e_plugin_invoke(group->hook->hook.plugin, (char *)item->user_data, &hdata);
+}
+
+static struct _EConfigItem *
+emph_construct_item(EPluginHook *eph, EConfigHookGroup *menu, xmlNodePtr root, EConfigHookTargetMap *map)
+{
+ struct _EConfigItem *item;
+
+ printf(" loading config item\n");
+ item = g_malloc0(sizeof(*item));
+ if ((item->type = e_plugin_hook_id(root, ech_item_types, "type")) == -1)
+ goto error;
+ item->path = e_plugin_xml_prop(root, "path");
+ item->label = e_plugin_xml_prop_domain(root, "label", eph->plugin->domain);
+ item->user_data = e_plugin_xml_prop(root, "factory");
+
+ if (item->path == NULL
+ || (item->label == NULL && item->user_data == NULL))
+ goto error;
+
+ if (item->user_data)
+ item->factory = ech_config_widget_factory;
+
+ printf(" path=%s label=%s factory=%s\n", item->path, item->label, (char *)item->user_data);
+
+ return item;
+error:
+ printf("error!\n");
+ emph_free_item(item);
+ return NULL;
+}
+
+static struct _EConfigHookGroup *
+emph_construct_menu(EPluginHook *eph, xmlNodePtr root)
+{
+ struct _EConfigHookGroup *menu;
+ xmlNodePtr node;
+ EConfigHookTargetMap *map;
+ EConfigHookClass *klass = (EConfigHookClass *)G_OBJECT_GET_CLASS(eph);
+ char *tmp;
+
+ printf(" loading menu\n");
+ menu = g_malloc0(sizeof(*menu));
+
+ tmp = xmlGetProp(root, "target");
+ if (tmp == NULL)
+ goto error;
+ map = g_hash_table_lookup(klass->target_map, tmp);
+ xmlFree(tmp);
+ if (map == NULL)
+ goto error;
+
+ menu->target_type = map->id;
+ menu->id = e_plugin_xml_prop(root, "id");
+ menu->commit = e_plugin_xml_prop(root, "commit");
+ menu->abort = e_plugin_xml_prop(root, "abort");
+ menu->hook = (EConfigHook *)eph;
+ node = root->children;
+ while (node) {
+ if (0 == strcmp(node->name, "item")) {
+ struct _EConfigItem *item;
+
+ item = emph_construct_item(eph, menu, node, map);
+ if (item)
+ menu->items = g_slist_append(menu->items, item);
+ }
+ node = node->next;
+ }
+
+ return menu;
+error:
+ emph_free_group(menu);
+ return NULL;
+}
+
+static int
+emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ EConfigClass *klass;
+
+ printf("loading config hook\n");
+
+ if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
+ return -1;
+
+ klass = ((EConfigHookClass *)G_OBJECT_GET_CLASS(eph))->config_class;
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "group") == 0) {
+ struct _EConfigHookGroup *group;
+
+ group = emph_construct_menu(eph, node);
+ if (group) {
+ e_config_class_add_factory(klass, group->id, ech_config_factory, group);
+ emph->groups = g_slist_append(emph->groups, group);
+ }
+ }
+ node = node->next;
+ }
+
+ eph->plugin = ep;
+
+ return 0;
+}
+
+static void
+emph_finalise(GObject *o)
+{
+ EPluginHook *eph = (EPluginHook *)o;
+
+ g_slist_foreach(emph->groups, (GFunc)emph_free_group, NULL);
+ g_slist_free(emph->groups);
+
+ ((GObjectClass *)emph_parent_class)->finalize(o);
+}
+
+static void
+emph_class_init(EPluginHookClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = emph_finalise;
+ klass->construct = emph_construct;
+
+ /* this is actually an abstract implementation but list it anyway */
+ klass->id = "com.ximian.evolution.config:1.0";
+
+ printf("EConfigHook: init class %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ ((EConfigHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
+ ((EConfigHookClass *)klass)->config_class = g_type_class_ref(e_config_get_type());
+}
+
+/**
+ * e_config_hook_get_type:
+ *
+ * Standard GObject function to get the object type.
+ *
+ * Return value: The EConfigHook class type.
+ **/
+GType
+e_config_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EConfigHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
+ sizeof(EConfigHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
+ type = g_type_register_static(e_plugin_hook_get_type(), "EConfigHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_config_hook_class_add_target_map:
+ *
+ * @klass: The dervied EconfigHook class.
+ * @map: A map used to describe a single EConfigTarget type for this
+ * class.
+ *
+ * Add a targe tmap to a concrete derived class of EConfig. The
+ * target map enumates the target types available for the implenting
+ * class.
+ **/
+void e_config_hook_class_add_target_map(EConfigHookClass *klass, const EConfigHookTargetMap *map)
+{
+ g_hash_table_insert(klass->target_map, (void *)map->type, (void *)map);
+}
diff --git a/e-util/e-config.h b/e-util/e-config.h
new file mode 100644
index 0000000000..ce119f1e56
--- /dev/null
+++ b/e-util/e-config.h
@@ -0,0 +1,374 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michel Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __E_CONFIG_H__
+#define __E_CONFIG_H__
+
+#include <glib-object.h>
+#include "e-util/e-msgport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+struct _GtkWindow;
+struct _GtkWidget;
+
+/* This is a config window management/merging class. */
+
+typedef struct _EConfig EConfig;
+typedef struct _EConfigClass EConfigClass;
+
+typedef struct _EConfigItem EConfigItem;
+typedef struct _EConfigFactory EConfigFactory;
+typedef struct _EConfigTarget EConfigTarget;
+
+typedef void (*EConfigFactoryFunc)(EConfig *ec, void *data);
+
+typedef gboolean (*EConfigCheckFunc)(EConfig *ec, const char *pageid, void *data);
+
+typedef void (*EConfigItemsFunc)(EConfig *ec, GSList *items, void *data);
+
+typedef struct _GtkWidget * (*EConfigItemFactoryFunc)(EConfig *ec, EConfigItem *, struct _GtkWidget *parent, struct _GtkWidget *old, void *data);
+
+/* ok so this is all a bit bogussy
+ we need to map to glade stuff instead */
+
+/* Add types?
+ if no factory, setup appropriate container ?
+ if factory, then assume that returns the right sort of object?
+ what about pages ?
+*/
+
+/**
+ * enum _e_config_target_changed_t - Target changed mode.
+ *
+ * @E_CONFIG_TARGET_CHANGED_STATE: A state of the target has changed.
+ * @E_CONFIG_TARGET_CHANGED_REBUILD: A state of the target has
+ * changed, and the UI must be reconfigured as a result.
+ *
+ * How the target has changed. If @E_CONFIG_TARGET_CHANGED_REBUILD then a
+ * widget reconfigure is necessary, otherwise it is used to check if
+ * the widget is complete yet.
+ **/
+typedef
+enum _e_config_target_change_t {
+ E_CONFIG_TARGET_CHANGED_STATE,
+ E_CONFIG_TARGET_CHANGED_REBUILD,
+} e_config_target_change_t;
+
+/**
+ * enum _e_config_t - configuration item type.
+ *
+ * @E_CONFIG_BOOK: A notebook item. Only one of this or
+ * @E_CONFIG_DRUID may be included in the item list for the entire
+ * configuration description.
+ * @E_CONFIG_DRUID: A druid item. Only one of this or @E_CONFIG_BOOK
+ * may be included in the item list for the entire configutation
+ * description.
+ * @E_CONFIG_PAGE: A configuration page. The item @label will be
+ * either the notebook tab label or the druid page title if no factory
+ * is supplied.
+ * @E_CONFIG_PAGE_START: A druid start page. Only one of these may be
+ * supplied for a druid and it should be the first page in the druid.
+ * @E_CONFIG_PAGE_FINISH: A druid finish page. Only one of these may
+ * be supplied for a druid and it should be the last page of the druid.
+ * @E_CONFIG_SECTION: A section in the configuration page. A page for
+ * this section must have already been defined. The item @label if
+ * supplied will be setup as a borderless hig-compliant frame title.
+ * The content of the section will be a GtkVBox. If a factory is used
+ * then it is up to the factory method to create the section and add
+ * it to the parent page, and return a GtkVBox for following sections.
+ * @E_CONFIG_SECTION_TABLE: A table section. The same as an
+ * @E_CONFIG_SECTION but the content object is a GtkTable instead.
+ * @E_CONFIG_ITEM: A configuration item. It must have a parent
+ * section defined in the configuration system.
+ * @E_CONFIG_ITEM_TABLE: A configuration item with a parent
+ * @E_CONFIG_SECTION_TABLE.
+ *
+ * A configuration item type for each configuration item added to the
+ * EConfig object. These are merged from all contributors to the
+ * configuration window, and then processed to form the combined
+ * display.
+ **/
+enum _e_config_t {
+ /* use one and only one of these for any given config-window id */
+ E_CONFIG_BOOK,
+ E_CONFIG_DRUID,
+
+ E_CONFIG_PAGE,
+ E_CONFIG_PAGE_START, /* only allowed in druid types */
+ E_CONFIG_PAGE_FINISH, /* only allowed in druid types */
+ E_CONFIG_SECTION,
+ E_CONFIG_SECTION_TABLE,
+ E_CONFIG_ITEM,
+ E_CONFIG_ITEM_TABLE, /* only allowed in table sections */
+};
+
+/**
+ * struct _EConfigItem - A configuration item.
+ *
+ * @type: The configuration item type.
+ * @path: An absolute path positioning this item in the configuration
+ * window. This will be used as a sort key for an ASCII sort to
+ * position the item in the layout tree.
+ * @label: A label or section title string which is used if no factory
+ * is supplied to title the page or section.
+ * @factory: If supplied, this will be invoked instead to create the
+ * appropriate item.
+ * @user_data: User data for the factory.
+ *
+ * The basic descriptor of a configuration item. This may be
+ * subclassed to store extra context information for each item.
+ **/
+struct _EConfigItem {
+ enum _e_config_t type;
+ char *path; /* absolute path, must sort ascii-lexographically into the right spot */
+ char *label;
+ EConfigItemFactoryFunc factory;
+ void *user_data;
+};
+
+/**
+ * struct _EConfigTarget - configuration context.
+ *
+ * @config: The parent object.
+ * @widget: A target-specific parent widget.
+ * @type: The type of target, defined by implementing classes.
+ *
+ * The base target object is used as the parent and placeholder for
+ * configuration context for a given configuration window. It is
+ * subclassed by implementing classes to provide domain-specific
+ * context.
+ **/
+struct _EConfigTarget {
+ struct _EConfig *config;
+ struct _GtkWidget *widget; /* used if you need a parent toplevel, if available */
+
+ guint32 type;
+
+ /* implementation fields follow, depends on window type */
+};
+
+/**
+ * struct _EConfig - A configuration management object.
+ *
+ * @object: Superclass.
+ * @priv: Private data.
+ * @type: Either @E_CONFIG_BOOK or @E_CONFIG_DRIUD, describing the
+ * root window type.
+ * @id: The globally unique identifider for this configuration window,
+ * used for hooking into it.
+ * @target: The current target.
+ * @widget: The GtkNoteBook or GnomeDruid created after
+ * :create_widget() is called that represents the merged and combined
+ * configuration window.
+ * @window: If :create_window() is called, then the containing
+ * toplevel GtkDialog or GtkWindow appropriate for the @type of
+ * configuration window created.
+ *
+ **/
+struct _EConfig {
+ GObject object;
+
+ struct _EConfigPrivate *priv;
+
+ int type; /* E_CONFIG_BOOK or E_CONFIG_DRUID */
+
+ char *id;
+
+ EConfigTarget *target;
+
+ struct _GtkWidget *widget; /* the generated internal */
+ struct _GtkWidget *window; /* the window widget, GtkWindow or GtkDialog */
+};
+
+/**
+ * struct _EConfigClass - Configuration management abstract class.
+ *
+ * @object_class: Superclass.
+ * @factories: A list of factories registered on this type of
+ * configuration manager.
+ * @set_target: A virtual method used to set the target on the
+ * configuration manager. This is used by subclasses so they may hook
+ * into changes on the target to propery drive the manager.
+ * @target_free: A virtual method used to free the target in an
+ * implementation-defined way.
+ *
+ **/
+struct _EConfigClass {
+ GObjectClass object_class;
+
+ EDList factories;
+
+ void (*set_target)(EConfig *ep, EConfigTarget *t);
+
+ void (*target_free)(EConfig *ep, EConfigTarget *t);
+};
+
+GType e_config_get_type(void);
+
+/* Static class methods */
+EConfigFactory *e_config_class_add_factory(EConfigClass *klass, const char *id, EConfigFactoryFunc func, void *data);
+void e_config_class_remove_factory(EConfigClass *klass, EConfigFactory *f);
+
+EConfig *e_config_construct(EConfig *, int type, const char *id);
+
+void e_config_add_items(EConfig *, GSList *items, EConfigItemsFunc commitfunc, EConfigItemsFunc abortfunc, EConfigItemsFunc freefunc, void *data);
+void e_config_add_page_check(EConfig *, const char *pageid, EConfigCheckFunc, void *data);
+
+void e_config_set_target(EConfig *emp, EConfigTarget *target);
+struct _GtkWidget *e_config_create_widget(EConfig *);
+GtkWidget *e_config_create_window(EConfig *emp, struct _GtkWindow *parent, const char *title);
+
+void e_config_target_changed(EConfig *emp, e_config_target_change_t how);
+
+gboolean e_config_page_check(EConfig *, const char *);
+
+GtkWidget *e_config_page_get(EConfig *ec, const char *pageid);
+const char *e_config_page_next(EConfig *ec, const char *pageid);
+const char *e_config_page_prev(EConfig *ec, const char *pageid);
+
+void e_config_abort(EConfig *);
+void e_config_commit(EConfig *);
+
+void *e_config_target_new(EConfig *, int type, size_t size);
+void e_config_target_free(EConfig *, void *);
+
+/* ********************************************************************** */
+
+/* config plugin target, they are closely integrated */
+
+/* To implement a basic config plugin, you just need to subclass
+ this and initialise the class target type tables */
+
+#include "e-util/e-plugin.h"
+
+typedef struct _EConfigHookGroup EConfigHookGroup;
+typedef struct _EConfigHook EConfigHook;
+typedef struct _EConfigHookClass EConfigHookClass;
+
+typedef struct _EPluginHookTargetMap EConfigHookTargetMap;
+typedef struct _EPluginHookTargetKey EConfigHookTargetMask;
+
+typedef struct _EConfigHookItemFactoryData EConfigHookItemFactoryData;
+
+typedef void (*EConfigHookFunc)(struct _EPlugin *plugin, EConfigTarget *target);
+typedef void (*EConfigHookItemFactoryFunc)(struct _EPlugin *plugin, EConfigHookItemFactoryData *data);
+
+/**
+ * struct _EConfigHookItemFactoryData - Factory marshalling structure.
+ *
+ * @config: The parent EConfig. This is also available in
+ * @target->config but is here as a convenience. (TODO: do we need this).
+ * @item: The corresponding configuration item.
+ * @target: The current configuration target. This is also available
+ * on @config->target.
+ * @parent: The parent widget for this item. Depends on the item
+ * type.
+ * @old: The last widget created by this factory. The factory is only
+ * re-invoked if a reconfigure request is invoked on the EConfig.
+ *
+ * Used to marshal the callback data for the EConfigItemFactory method
+ * to a single pointer for the EPlugin system.
+ **/
+struct _EConfigHookItemFactoryData {
+ EConfig *config;
+ EConfigItem *item;
+ EConfigTarget *target;
+ struct _GtkWidget *parent;
+ struct _GtkWidget *old;
+};
+
+/**
+ * struct _EConfigHookGroup - A group of configuration items.
+ *
+ * @hook: Parent object.
+ * @id: The configuration window to which these items apply.
+ * @target_type: The target type expected by the items. This is
+ * defined by implementing classes.
+ * @items: A list of EConfigHookItem's for this group.
+ * @commit: The name of the commit function for this group of items, or NULL
+ * for instant-apply configuration windows. Its format is plugin-type defined.
+ * @abort: Similar to the @commit function but for aborting or
+ * cancelling a configuration edit.
+ *
+ * Each plugin that hooks into a given configuration page will define
+ * all of the tiems for that page in a single group.
+ **/
+struct _EConfigHookGroup {
+ struct _EConfigHook *hook; /* parent pointer */
+ char *id; /* target menu id for these config items */
+ int target_type; /* target type of this group */
+ GSList *items; /* items to add to group */
+ char *commit; /* commit handler, if set */
+ char *abort; /* abort handler, if set */
+};
+
+/**
+ * struct _EConfigHook - Plugin hook for configuration windows.
+ *
+ * @hook: Superclass.
+ * @groups: A list of EConfigHookGroup's of all configuration windows
+ * this plugin hooks into.
+ *
+ **/
+struct _EConfigHook {
+ EPluginHook hook;
+
+ GSList *groups;
+};
+
+/**
+ * struct _EConfigHookClass - Abstract class for configuration window
+ * plugin hooks.
+ *
+ * @hook_class: Superclass.
+ * @target_map: A table of EConfigHookTargetMap structures describing
+ * the possible target types supported by this class.
+ * @config_class: The EConfig derived class that this hook
+ * implementation drives.
+ *
+ * This is an abstract class defining the plugin hook point for
+ * configuration windows.
+ *
+ **/
+struct _EConfigHookClass {
+ EPluginHookClass hook_class;
+
+ /* EConfigHookTargetMap by .type */
+ GHashTable *target_map;
+ /* the config class these configs's belong to */
+ EConfigClass *config_class;
+};
+
+GType e_config_hook_get_type(void);
+
+/* for implementors */
+void e_config_hook_class_add_target_map(EConfigHookClass *klass, const EConfigHookTargetMap *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E_CONFIG_H__ */
diff --git a/e-util/e-event.c b/e-util/e-event.c
new file mode 100644
index 0000000000..c8ff8d4cb2
--- /dev/null
+++ b/e-util/e-event.c
@@ -0,0 +1,557 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2004 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkradiomenuitem.h>
+#include <gtk/gtkseparatormenuitem.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkimage.h>
+
+#include "e-event.h"
+
+#include <e-util/e-icon-factory.h>
+
+#include <libgnome/gnome-i18n.h>
+
+struct _EEventFactory {
+ struct _EEventFactory *next, *prev;
+
+ char *menuid;
+ EEventFactoryFunc factory;
+ void *factory_data;
+};
+
+struct _event_node {
+ struct _event_node *next, *prev;
+
+ GSList *events;
+ void *data;
+ EEventItemsFunc freefunc;
+};
+
+struct _event_info {
+ struct _event_node *parent;
+ EEventItem *item;
+};
+
+struct _EEventPrivate {
+ EDList events;
+
+ GSList *sorted; /* sorted list of struct _event_info's */
+};
+
+static GObjectClass *ep_parent;
+
+static void
+ep_init(GObject *o)
+{
+ EEvent *emp = (EEvent *)o;
+ struct _EEventPrivate *p;
+
+ p = emp->priv = g_malloc0(sizeof(struct _EEventPrivate));
+
+ e_dlist_init(&p->events);
+}
+
+static void
+ep_finalise(GObject *o)
+{
+ EEvent *emp = (EEvent *)o;
+ struct _EEventPrivate *p = emp->priv;
+ struct _event_node *node;
+
+ if (emp->target)
+ e_event_target_free(emp, emp->target);
+
+ g_free(emp->id);
+
+ while ((node = (struct _event_node *)e_dlist_remhead(&p->events))) {
+ if (node->freefunc)
+ node->freefunc(emp, node->events, node->data);
+
+ g_free(node);
+ }
+
+ g_slist_foreach(p->sorted, (GFunc)g_free, NULL);
+ g_slist_free(p->sorted);
+
+ g_free(p);
+
+ ((GObjectClass *)ep_parent)->finalize(o);
+}
+
+static void
+ep_target_free(EEvent *ep, EEventTarget *t)
+{
+ g_free(t);
+ g_object_unref(ep);
+}
+
+static void
+ep_class_init(GObjectClass *klass)
+{
+ printf("EEvent class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ klass->finalize = ep_finalise;
+ ((EEventClass *)klass)->target_free = ep_target_free;
+}
+
+/**
+ * e_event_get_type:
+ *
+ * Standard GObject type function. Used to subclass EEvent.
+ *
+ * Return value: The EEvent type.
+ **/
+GType
+e_event_get_type(void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof(EEventClass),
+ (GBaseInitFunc)NULL, NULL,
+ (GClassInitFunc)ep_class_init, NULL, NULL,
+ sizeof(EEvent), 0,
+ (GInstanceInitFunc)ep_init
+ };
+ ep_parent = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EEvent", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_event_construct:
+ * @ep: An instantiated but uninitialised EEvent.
+ * @id: Event manager id.
+ *
+ * Construct the base event instance with standard parameters.
+ *
+ * Return value: Returns @ep.
+ **/
+EEvent *e_event_construct(EEvent *ep, const char *id)
+{
+ ep->id = g_strdup(id);
+
+ return ep;
+}
+
+/**
+ * e_event_add_items:
+ * @emp: An initialised EEvent structure.
+ * @items: A list of EEventItems event listeners to register on this event manager.
+ * @freefunc: A function called when the @items list is no longer needed.
+ * @data: callback data for @freefunc and for item event handlers.
+ *
+ * Adds @items to the list of events listened to on the event manager @emp.
+ *
+ * Return value: An opaque key which can later be passed to remove_items.
+ **/
+void *
+e_event_add_items(EEvent *emp, GSList *items, EEventItemsFunc freefunc, void *data)
+{
+ struct _event_node *node;
+
+ node = g_malloc(sizeof(*node));
+ node->events = items;
+ node->freefunc = freefunc;
+ node->data = data;
+ e_dlist_addtail(&emp->priv->events, (EDListNode *)node);
+
+ if (emp->priv->sorted) {
+ g_slist_foreach(emp->priv->sorted, (GFunc)g_free, NULL);
+ g_slist_free(emp->priv->sorted);
+ emp->priv->sorted = NULL;
+ }
+
+ return (void *)node;
+}
+
+/**
+ * e_event_remove_items:
+ * @emp:
+ * @handle:
+ *
+ * Remove items previously added. They MUST have been previously
+ * added, and may only be removed once.
+ **/
+void
+e_event_remove_items(EEvent *emp, void *handle)
+{
+ struct _event_node *node = handle;
+
+ e_dlist_remove((EDListNode *)node);
+ if (node->freefunc)
+ node->freefunc(emp, node->events, node->data);
+ g_free(node);
+
+ if (emp->priv->sorted) {
+ g_slist_foreach(emp->priv->sorted, (GFunc)g_free, NULL);
+ g_slist_free(emp->priv->sorted);
+ emp->priv->sorted = NULL;
+ }
+}
+
+static int
+ee_cmp(const void *ap, const void *bp)
+{
+ int a = ((struct _event_info **)ap)[0]->item->priority;
+ int b = ((struct _event_info **)bp)[0]->item->priority;
+
+ if (a < b)
+ return 1;
+ else if (a > b)
+ return -1;
+ else
+ return 0;
+}
+
+/**
+ * e_event_emit:
+ * @ee: An initialised EEvent, potentially with registered event listeners.
+ * @id: Event name. This will be compared against EEventItem.id.
+ * @target: The target describing the event context. This will be implementation defined.
+ *
+ * Emit an event. @target will automatically be freed once its
+ * emission is complete.
+ **/
+void
+e_event_emit(EEvent *emp, const char *id, EEventTarget *target)
+{
+ struct _EEventPrivate *p = emp->priv;
+ GSList *events;
+
+ printf("emit event %s\n", id);
+
+ g_assert(emp->target == NULL);
+
+ emp->target = target;
+ events = p->sorted;
+ if (events == NULL) {
+ struct _event_node *node = (struct _event_node *)p->events.head;
+
+ for (;node->next;node=node->next) {
+ GSList *l = node->events;
+
+ for (;l;l=g_slist_next(l)) {
+ struct _event_info *info;
+
+ info = g_malloc0(sizeof(*info));
+ info->parent = node;
+ info->item = l->data;
+ events = g_slist_prepend(events, info);
+ }
+ }
+
+ p->sorted = events = g_slist_sort(events, ee_cmp);
+ }
+
+ for (;events;events=g_slist_next(events)) {
+ struct _event_info *info = events->data;
+ EEventItem *event = info->item;
+
+ printf("event '%s' mask %08x target %08x\n", event->id, event->enable, target->mask);
+
+ if (event->enable & target->mask)
+ continue;
+
+ if (strcmp(event->id, id) == 0) {
+ event->handle(emp, event, info->parent->data);
+
+ if (event->type == E_EVENT_SINK)
+ break;
+ }
+ }
+
+ e_event_target_free(emp, target);
+ emp->target = NULL;
+}
+
+/**
+ * e_event_target_new:
+ * @ep: An initialised EEvent instance.
+ * @type: type, up to implementor
+ * @size: The size of memory to allocate. This must be >= sizeof(EEventTarget).
+ *
+ * Allocate a new event target suitable for this class. It is up to
+ * the implementation to define the available target types and their
+ * structure.
+ **/
+void *e_event_target_new(EEvent *ep, int type, size_t size)
+{
+ EEventTarget *t;
+
+ g_assert(size >= sizeof(EEventTarget));
+
+ t = g_malloc0(size);
+ t->event = ep;
+ g_object_ref(ep);
+ t->type = type;
+
+ return t;
+}
+
+/**
+ * e_event_target_free:
+ * @ep: An initialised EEvent instance on which this target was allocated.
+ * @o: The target to free.
+ *
+ * Free a target. This invokes the virtual free method on the EEventClass.
+ **/
+void
+e_event_target_free(EEvent *ep, void *o)
+{
+ EEventTarget *t = o;
+
+ ((EEventClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
+}
+
+/* ********************************************************************** */
+
+/* Event menu plugin handler */
+
+/*
+<e-plugin
+ class="com.ximian.mail.plugin.event:1.0"
+ id="com.ximian.mail.plugin.event.item:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ name="imap"
+ description="IMAP4 and IMAP4v1 mail store">
+ <hook class="com.ximian.mail.eventMenu:1.0"
+ handler="HandleEvent">
+ <menu id="any" target="select">
+ <item
+ type="item|toggle|radio|image|submenu|bar"
+ active
+ path="foo/bar"
+ label="label"
+ icon="foo"
+ mask="select_one"
+ activate="ep_view_emacs"/>
+ </menu>
+ </extension>
+
+ <hook class="com.ximian.evolution.mail.events:1.0">
+ <event id=".folder.changed"
+ target=""
+ priority="0"
+ handle="gotevent"
+ enable="new"
+ />
+ <event id=".message.read"
+ priority="0"
+ handle="gotevent"
+ mask="new"
+ />
+ </hook>
+
+*/
+
+static void *emph_parent_class;
+#define emph ((EEventHook *)eph)
+
+/* must have 1:1 correspondence with e-event types in order */
+static const EPluginHookTargetKey emph_item_types[] = {
+ { "pass", E_EVENT_PASS },
+ { "sink", E_EVENT_SINK },
+ { 0 }
+};
+
+static void
+emph_event_handle(EEvent *ee, EEventItem *item, void *data)
+{
+ struct _EEventHook *hook = data;
+
+ /* FIXME: we could/should just remove the items we added to the event handler */
+ if (!hook->hook.plugin->enabled)
+ return;
+
+ e_plugin_invoke(hook->hook.plugin, (char *)item->user_data, ee->target);
+}
+
+static void
+emph_free_item(struct _EEventItem *item)
+{
+ g_free((char *)item->id);
+ g_free(item->user_data);
+ g_free(item);
+}
+
+static void
+emph_free_items(EEvent *ee, GSList *items, void *data)
+{
+ /*EPluginHook *eph = data;*/
+
+ g_slist_foreach(items, (GFunc)emph_free_item, NULL);
+ g_slist_free(items);
+}
+
+static struct _EEventItem *
+emph_construct_item(EPluginHook *eph, xmlNodePtr root, EEventHookClass *klass)
+{
+ struct _EEventItem *item;
+ EEventHookTargetMap *map;
+ char *tmp;
+
+ item = g_malloc0(sizeof(*item));
+
+ tmp = xmlGetProp(root, "target");
+ if (tmp == NULL)
+ goto error;
+ map = g_hash_table_lookup(klass->target_map, tmp);
+ xmlFree(tmp);
+ if (map == NULL)
+ goto error;
+ item->target_type = map->id;
+ item->type = e_plugin_hook_id(root, emph_item_types, "type");
+ if (item->type == -1)
+ item->type = E_EVENT_PASS;
+ item->priority = e_plugin_xml_int(root, "priority", 0);
+ item->id = e_plugin_xml_prop(root, "id");
+ item->enable = e_plugin_hook_mask(root, map->mask_bits, "enable");
+ item->user_data = e_plugin_xml_prop(root, "handle");
+
+ if (item->user_data == NULL || item->id == NULL)
+ goto error;
+
+ item->handle = emph_event_handle;
+
+ return item;
+error:
+ emph_free_item(item);
+ return NULL;
+}
+
+static int
+emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ EEventHookClass *klass;
+ GSList *items = NULL;
+
+ g_return_val_if_fail(((EEventHookClass *)G_OBJECT_GET_CLASS(eph))->event != NULL, -1);
+
+ printf("loading event hook\n");
+
+ if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
+ return -1;
+
+ klass = (EEventHookClass *)G_OBJECT_GET_CLASS(eph);
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "event") == 0) {
+ struct _EEventItem *item;
+
+ item = emph_construct_item(eph, node, klass);
+ if (item)
+ items = g_slist_prepend(items, item);
+ }
+ node = node->next;
+ }
+
+ eph->plugin = ep;
+
+ if (items)
+ e_event_add_items(klass->event, items, emph_free_items, eph);
+
+ return 0;
+}
+
+static void
+emph_finalise(GObject *o)
+{
+ /*EPluginHook *eph = (EPluginHook *)o;*/
+
+ ((GObjectClass *)emph_parent_class)->finalize(o);
+}
+
+static void
+emph_class_init(EPluginHookClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = emph_finalise;
+ klass->construct = emph_construct;
+
+ /* this is actually an abstract implementation but list it anyway */
+ klass->id = "com.ximian.evolution.event:1.0";
+
+ printf("EEventHook: init class %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ ((EEventHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+/**
+ * e_event_hook_get_type:
+ *
+ * Standard GObject function to get the EEvent object type. Used to
+ * subclass EEventHook.
+ *
+ * Return value: The type of the event hook class.
+ **/
+GType
+e_event_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EEventHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
+ sizeof(EEventHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
+ type = g_type_register_static(e_plugin_hook_get_type(), "EEventHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_event_hook_class_add_target_map:
+ * @klass: The derived EEventHook class.
+ * @map: A map used to describe a single EEventTarget type for this
+ * class.
+ *
+ * Add a target map to a concrete derived class of EEvent. The target
+ * map enumerates a single target type and th eenable mask bit names,
+ * so that the type can be loaded automatically by the base EEvent class.
+ **/
+void e_event_hook_class_add_target_map(EEventHookClass *klass, const EEventHookTargetMap *map)
+{
+ g_hash_table_insert(klass->target_map, (void *)map->type, (void *)map);
+}
diff --git a/e-util/e-event.h b/e-util/e-event.h
new file mode 100644
index 0000000000..0a26b66b09
--- /dev/null
+++ b/e-util/e-event.h
@@ -0,0 +1,238 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michel Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2004 Novell Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+/*
+ This a bit 'whipped together', so is likely to change mid-term
+*/
+
+#ifndef __E_EVENT_H__
+#define __E_EVENT_H__
+
+#include <glib-object.h>
+#include "e-util/e-msgport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+/* This is an abstract event management class. */
+
+typedef struct _EEvent EEvent;
+typedef struct _EEventClass EEventClass;
+
+typedef struct _EEventItem EEventItem;
+typedef struct _EEventFactory EEventFactory; /* anonymous type */
+typedef struct _EEventTarget EEventTarget;
+
+typedef void (*EEventItemsFunc)(EEvent *ee, GSList *items, void *data);
+typedef void (*EEventFunc)(EEvent *ee, EEventItem *item, void *data);
+typedef void (*EEventFactoryFunc)(EEvent *ee, void *);
+
+/**
+ * enum _e_event_t - Event type.
+ *
+ * @E_EVENT_PASS: A passthrough event handler which only receives the event.
+ * @E_EVENT_SINK: A sink event handler swallows all events it processes.
+ *
+ * The event type defines what type of event listener this is.
+ *
+ * Events should normally be @E_EVENT_PASS.
+ **/
+enum _e_event_t {
+ E_EVENT_PASS, /* passthrough */
+ E_EVENT_SINK, /* sink events */
+};
+
+/**
+ * struct _EEventItem - An event listener item.
+ *
+ * @type: The type of the event listener.
+ * @priority: A signed number signifying the priority of the event
+ * listener. 0 should be used normally. This is used to order event
+ * receipt when multiple listners are present.
+ * @id: The name of the event to listen to. By convention events are of the form
+ * "component.subcomponent". The target mask provides further
+ * sub-event type qualification.
+ * @target_type: Target type for this event. This is implementation
+ * specific.
+ * @handle: Event handler callback.
+ * @user_data: Callback data.
+ * @enable: Target-specific mask to qualify the receipt of events.
+ * This is target and implementation specific.
+ *
+ * An EEventItem defines a specific event listening point on a given
+ * EEvent object. When an event is broadcast onto an EEvent handler,
+ * any matching EEventItems will be invoked in priority order.
+ **/
+struct _EEventItem {
+ enum _e_event_t type;
+ int priority; /* priority of event */
+ const char *id; /* event id */
+ int target_type;
+ EEventFunc handle;
+ void *user_data;
+ guint32 enable; /* enable mask */
+};
+
+/**
+ * struct _EEventTarget - Base EventTarget.
+ *
+ * @event: Parent object.
+ * @type: Target type. Defined by the implementation.
+ * @mask: Mask of this target. This is defined by the implementation,
+ * the type, and the actual content of the target.
+ *
+ * This defined a base EventTarget. This must be subclassed by
+ * implementations to provide contextual data for events, and define
+ * the enablement qualifiers.
+ *
+ **/
+struct _EEventTarget {
+ struct _EEvent *event; /* used for virtual methods */
+
+ guint32 type; /* targe type, for implementors */
+ guint32 mask; /* depends on type, enable mask */
+
+ /* implementation fields follow */
+};
+
+/**
+ * struct _EEvent - An Event Manager.
+ *
+ * @object: Superclass.
+ * @priv: Private data.
+ * @id: Id of this event manager.
+ * @target: The current target, only set during event emission.
+ *
+ * The EEvent manager object. Each component which defines event
+ * types supplies a single EEvent manager object. This manager routes
+ * all events invoked on this object to all registered listeners based
+ * on their qualifiers.
+ **/
+struct _EEvent {
+ GObject object;
+
+ struct _EEventPrivate *priv;
+ char *id;
+ EEventTarget *target; /* current target during event emission */
+};
+
+/**
+ * struct _EEventClass - Event management type.
+ *
+ * @object_class: Superclass.
+ * @target_free: Virtual method to free the target.
+ *
+ * The EEvent class definition. This must be sub-classed for each
+ * component that wishes to provide hookable events. The subclass
+ * only needs to know how to allocate and free each target type it
+ * supports.
+ **/
+struct _EEventClass {
+ GObjectClass object_class;
+
+ void (*target_free)(EEvent *ep, EEventTarget *t);
+};
+
+GType e_event_get_type(void);
+
+EEvent *e_event_construct(EEvent *, const char *id);
+
+void *e_event_add_items(EEvent *emp, GSList *items, EEventItemsFunc freefunc, void *data);
+void e_event_remove_items(EEvent *emp, void *handle);
+
+void e_event_emit(EEvent *, const char *id, EEventTarget *);
+
+void *e_event_target_new(EEvent *, int type, size_t size);
+void e_event_target_free(EEvent *, void *);
+
+/* ********************************************************************** */
+
+/* event plugin target, they are closely integrated */
+
+/* To implement a basic event menu plugin, you just need to subclass
+ this and initialise the class target type tables */
+
+/* For events, the plugin item talks to a specific instance, rather than
+ a set of instances of the hook handler */
+
+#include "e-util/e-plugin.h"
+
+typedef struct _EEventHook EEventHook;
+typedef struct _EEventHookClass EEventHookClass;
+
+typedef struct _EPluginHookTargetMap EEventHookTargetMap;
+typedef struct _EPluginHookTargetKey EEventHookTargetMask;
+
+typedef void (*EEventHookFunc)(struct _EPlugin *plugin, EEventTarget *target);
+
+/**
+ * struct _EEventHook - An event hook.
+ *
+ * @hook: Superclass.
+ *
+ * The EEventHook class loads and manages the meta-data required to
+ * track event listeners. Unlike other hook types, there is a 1:1
+ * match between an EEventHook instance class and its EEvent instance.
+ *
+ * When the hook is loaded, all of its event hooks are stored directly
+ * on the corresponding EEvent which is stored in its class static area.
+ **/
+struct _EEventHook {
+ EPluginHook hook;
+};
+
+/**
+ * struct _EEventHookClass -
+ *
+ * @hook_class:
+ * @target_map: Table of EPluginHookTargetMaps which enumerate the
+ * target types and enable bits of the implementing class.
+ * @event: The EEvent instance on which all loaded events must be registered.
+ *
+ * The EEventHookClass is an empty event hooking class, which must be
+ * subclassed and initialised before use.
+ *
+ * The EPluginHookClass.id must be set to the name and version of the
+ * hook handler itself, and then the type must be registered with the
+ * EPlugin hook list before any plugins are loaded.
+ **/
+struct _EEventHookClass {
+ EPluginHookClass hook_class;
+
+ /* EEventHookTargetMap by .type */
+ GHashTable *target_map;
+ /* the event router these events's belong to */
+ EEvent *event;
+};
+
+GType e_event_hook_get_type(void);
+
+/* for implementors */
+void e_event_hook_class_add_target_map(EEventHookClass *klass, const EEventHookTargetMap *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E_EVENT_H__ */
diff --git a/e-util/e-menu.c b/e-util/e-menu.c
new file mode 100644
index 0000000000..209f0816a0
--- /dev/null
+++ b/e-util/e-menu.c
@@ -0,0 +1,901 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2004 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include "e-menu.h"
+
+#include <e-util/e-icon-factory.h>
+
+#include <libgnome/gnome-i18n.h>
+#include <bonobo/bonobo-ui-util.h>
+
+struct _EMenuFactory {
+ struct _EMenuFactory *next, *prev;
+
+ char *menuid;
+ EMenuFactoryFunc factory;
+ void *factory_data;
+};
+
+struct _item_node {
+ struct _item_node *next;
+
+ EMenuItem *item;
+ struct _menu_node *menu;
+};
+
+struct _menu_node {
+ struct _menu_node *next, *prev;
+
+ EMenu *parent;
+
+ GSList *items;
+ GSList *uis;
+ GSList *pixmaps;
+
+ EMenuItemsFunc freefunc;
+ void *data;
+
+ /* a copy of items wrapped in an item_node, for bonobo
+ * callback mapping */
+ struct _item_node *menu;
+};
+
+struct _EMenuPrivate {
+ EDList menus;
+};
+
+static GObjectClass *em_parent;
+
+static void
+em_init(GObject *o)
+{
+ EMenu *emp = (EMenu *)o;
+ struct _EMenuPrivate *p;
+
+ p = emp->priv = g_malloc0(sizeof(struct _EMenuPrivate));
+
+ e_dlist_init(&p->menus);
+}
+
+static void
+em_finalise(GObject *o)
+{
+ EMenu *em = (EMenu *)o;
+ struct _EMenuPrivate *p = em->priv;
+ struct _menu_node *mnode;
+
+ if (em->target)
+ e_menu_target_free(em, em->target);
+ g_free(em->menuid);
+
+ while ((mnode = (struct _menu_node *)e_dlist_remhead(&p->menus))) {
+ struct _item_node *inode;
+
+ if (mnode->freefunc)
+ mnode->freefunc(em, mnode->items, mnode->uis, mnode->pixmaps, mnode->data);
+
+ inode = mnode->menu;
+ while (inode) {
+ struct _item_node *nnode = inode->next;
+
+ g_free(inode);
+ inode = nnode;
+ }
+
+ g_free(mnode);
+ }
+
+ g_free(p);
+
+ ((GObjectClass *)em_parent)->finalize(o);
+}
+
+static void
+em_target_free(EMenu *ep, EMenuTarget *t)
+{
+ g_free(t);
+ /* look funny but t has a reference to us */
+ g_object_unref(ep);
+}
+
+static void
+em_class_init(GObjectClass *klass)
+{
+ printf("EMenu class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ printf("e menu class init\n");
+ klass->finalize = em_finalise;
+ ((EMenuClass *)klass)->target_free = em_target_free;
+}
+
+static void
+em_base_init(GObjectClass *klass)
+{
+ /* each class instance must have its own list, it isn't inherited */
+ printf("%p: list init\n", klass);
+ e_dlist_init(&((EMenuClass *)klass)->factories);
+}
+
+/**
+ * e_menu_get_type:
+ *
+ * Standard GObject type function. Used to subclass this type only.
+ *
+ * Return value: The EMenu object type.
+ **/
+GType
+e_menu_get_type(void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof(EMenuClass),
+ (GBaseInitFunc)em_base_init, NULL,
+ (GClassInitFunc)em_class_init,
+ NULL, NULL,
+ sizeof(EMenu), 0,
+ (GInstanceInitFunc)em_init
+ };
+ em_parent = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EMenu", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_menu_construct:
+ * @em: An instantiated but uninitislied EPopup.
+ * @menuid: The unique identifier for this menu.
+ *
+ * Construct the base menu instance based on the parameters.
+ *
+ * Return value: Returns @em.
+ **/
+EMenu *e_menu_construct(EMenu *em, const char *menuid)
+{
+ struct _EMenuFactory *f;
+ EMenuClass *klass;
+
+ printf("constructing menu '%s'\n", menuid);
+
+ klass = (EMenuClass *)G_OBJECT_GET_CLASS(em);
+
+ printf(" class is %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ em->menuid = g_strdup(menuid);
+
+ /* setup the menu itself based on factories */
+ f = (struct _EMenuFactory *)klass->factories.head;
+ if (f->next == NULL) {
+ printf("%p no factories registered on menu\n", klass);
+ }
+
+ while (f->next) {
+ if (f->menuid == NULL
+ || !strcmp(f->menuid, em->menuid)) {
+ printf(" calling factory\n");
+ f->factory(em, f->factory_data);
+ }
+ f = f->next;
+ }
+
+ return em;
+}
+
+/**
+ * e_menu_add_items:
+ * @emp: An initialised EMenu.
+ * @items: A list of EMenuItems or derived structures defining a group
+ * of menu items for this menu.
+ * @uifiles: A list of EMenuUIFile objects describing all ui files
+ * associated with the items.
+ * @pixmaps: A list of EMenuPixmap objects describing all pixmaps
+ * associated with the menus.
+ * @freefunc: If supplied, called when the menu items are no longer needed.
+ * @data: user-data passed to @freefunc and activate callbacks.
+ *
+ * Add new EMenuItems to the menu's. This may be called any number of
+ * times before the menu is first activated to hook onto any of the
+ * menu items defined for that view.
+ *
+ * Return value: A handle that can be passed to remove_items as required.
+ **/
+void *
+e_menu_add_items(EMenu *emp, GSList *items, GSList *uifiles, GSList *pixmaps, EMenuItemsFunc freefunc, void *data)
+{
+ struct _menu_node *node;
+ GSList *l;
+
+ node = g_malloc(sizeof(*node));
+ node->parent = emp;
+ node->items = items;
+ node->uis = uifiles;
+ node->pixmaps = pixmaps;
+ node->freefunc = freefunc;
+ node->data = data;
+
+ for (l=items;l;l=g_slist_next(l)) {
+ struct _item_node *inode = g_malloc0(sizeof(*inode));
+ EMenuItem *item = l->data;
+
+ inode->item = item;
+ inode->menu = node;
+ inode->next = node->menu;
+ node->menu = inode;
+ }
+
+ for (l=pixmaps;l;l=g_slist_next(l)) {
+ EMenuPixmap *pixmap = l->data;
+
+ if (pixmap->pixmap == NULL) {
+ GdkPixbuf *pixbuf;
+
+ pixbuf = e_icon_factory_get_icon(pixmap->name, pixmap->size);
+ if (pixbuf == NULL) {
+ g_warning("Unable to load icon '%s'", pixmap->name);
+ } else {
+ pixmap->pixmap = bonobo_ui_util_pixbuf_to_xml(pixbuf);
+ g_object_unref(pixbuf);
+ }
+ }
+ }
+
+ e_dlist_addtail(&emp->priv->menus, (EDListNode *)node);
+
+ /* FIXME: add the menu's to a running menu if it is there? */
+
+ return (void *)node;
+}
+
+/**
+ * e_menu_remove_items:
+ * @emp:
+ * @handle:
+ *
+ * Remove menu items previously added.
+ **/
+void
+e_menu_remove_items(EMenu *emp, void *handle)
+{
+ struct _menu_node *node = handle;
+ struct _item_node *inode;
+ GSList *l;
+
+ e_dlist_remove((EDListNode *)node);
+
+ if (emp->uic) {
+ for (l = node->items;l;l=g_slist_next(l)) {
+ EMenuItem *item = l->data;
+
+ bonobo_ui_component_remove_verb(emp->uic, item->verb);
+ }
+ }
+
+ if (node->freefunc)
+ node->freefunc(emp, node->items, node->uis, node->pixmaps, node->data);
+
+ inode = node->menu;
+ while (inode) {
+ struct _item_node *nnode = inode->next;
+
+ g_free(inode);
+ inode = nnode;
+ }
+
+ g_free(node);
+}
+
+static void
+em_activate_toggle(BonoboUIComponent *component, const char *path, Bonobo_UIComponent_EventType type, const char *state, void *data)
+{
+ struct _item_node *inode = data;
+
+ if (type != Bonobo_UIComponent_STATE_CHANGED)
+ return;
+
+ ((EMenuToggleActivateFunc)inode->item->activate)(inode->menu->parent, inode->item, state[0] != '0', inode->menu->data);
+}
+
+static void
+em_activate(BonoboUIComponent *uic, void *data, const char *cname)
+{
+ struct _item_node *inode = data;
+
+ ((EMenuActivateFunc)inode->item->activate)(inode->menu->parent, inode->item, inode->menu->data);
+}
+
+/**
+ * e_menu_activate:
+ * @em: An initialised EMenu.
+ * @uic: The BonoboUI component for this views menu's.
+ * @act: If %TRUE, then the control is being activated.
+ *
+ * This is called by the owner of the component, control, or view to
+ * pass on the activate or deactivate control signals. If the view is
+ * being activated then the callbacks and menu items are setup,
+ * otherwise they are removed.
+ *
+ * This should always be called in the strict sequence of activate, then
+ * deactivate, repeated any number of times.
+ **/
+void e_menu_activate(EMenu *em, struct _BonoboUIComponent *uic, int act)
+{
+ struct _EMenuPrivate *p = em->priv;
+ struct _menu_node *mw;
+ GSList *l;
+
+ if (act) {
+ GArray *verbs;
+ int i;
+
+ em->uic = uic;
+
+ verbs = g_array_new(TRUE, FALSE, sizeof(BonoboUIVerb));
+ for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
+ struct _item_node *inode;
+
+ for (l = mw->uis; l ; l = g_slist_next(l)) {
+ EMenuUIFile *ui = l->data;
+
+ bonobo_ui_util_set_ui(uic, ui->appdir, ui->filename, ui->appname, NULL);
+ }
+
+ for (l = mw->pixmaps; l ; l = g_slist_next(l)) {
+ EMenuPixmap *pm = l->data;
+
+ if (pm->pixmap)
+ bonobo_ui_component_set_prop(uic, pm->command, "pixmap", pm->pixmap, NULL);
+ }
+
+ for (inode = mw->menu; inode; inode=inode->next) {
+ EMenuItem *item = inode->item;
+ BonoboUIVerb *verb;
+
+ printf("adding menu verb '%s'\n", item->verb);
+
+ switch (item->type & E_MENU_TYPE_MASK) {
+ case E_MENU_ITEM:
+ i = verbs->len;
+ verbs = g_array_set_size(verbs, i+1);
+ verb = &((BonoboUIVerb *)verbs->data)[i];
+
+ verb->cname = item->verb;
+ verb->cb = em_activate;
+ verb->user_data = inode;
+ break;
+ case E_MENU_TOGGLE:
+ bonobo_ui_component_set_prop(uic, item->path, "state", item->type & E_MENU_ACTIVE?"1":"0", NULL);
+ bonobo_ui_component_add_listener(uic, item->verb, em_activate_toggle, inode);
+ break;
+ }
+ }
+ }
+
+ if (verbs->len)
+ bonobo_ui_component_add_verb_list(uic, (BonoboUIVerb *)verbs->data);
+
+ g_array_free(verbs, TRUE);
+ } else {
+ for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
+ for (l = mw->items;l;l=g_slist_next(l)) {
+ EMenuItem *item = l->data;
+
+ bonobo_ui_component_remove_verb(uic, item->verb);
+ }
+ }
+
+ em->uic = NULL;
+ }
+}
+
+/**
+ * e_menu_update_target:
+ * @em: An initialised EMenu.
+ * @tp: Target, after this call the menu owns the target.
+ *
+ * Change the target for the menu. Once the target is changed, the
+ * sensitivity state of the menu items managed by @em is re-evaluated
+ * and the physical menu's updated to reflect it.
+ *
+ * This is used by the owner of the menu and view to update the menu
+ * system based on user input or changed system state.
+ **/
+void e_menu_update_target(EMenu *em, void *tp)
+{
+ struct _EMenuPrivate *p = em->priv;
+ EMenuTarget *t = tp;
+ guint32 mask = ~0;
+ struct _menu_node *mw;
+ GSList *l;
+
+ if (em->target && em->target != t)
+ e_menu_target_free(em, em->target);
+
+ /* if we unset the target, should we disable/hide all the menu items? */
+ em->target = t;
+ if (t == NULL)
+ return;
+
+ mask = t->mask;
+
+ /* canna do any more capt'n */
+ if (em->uic == NULL)
+ return;
+
+ for (mw = (struct _menu_node *)p->menus.head;mw->next;mw=mw->next) {
+ for (l = mw->items;l;l=g_slist_next(l)) {
+ EMenuItem *item = l->data;
+ int state;
+
+ printf("checking item '%s' mask %08x against target %08x\n", item->verb, item->enable, mask);
+
+ state = (item->enable & mask) == 0;
+ bonobo_ui_component_set_prop(em->uic, item->path, "sensitive", state?"1":"0", NULL);
+ /* visible? */
+ }
+ }
+}
+
+/* ********************************************************************** */
+
+/**
+ * e_menu_class_add_factory:
+ * @klass: An EMenuClass type to which this factory applies.
+ * @menuid: The identifier of the menu for this factory, or NULL to be
+ * called on all menus.
+ * @func: An EMenuFactoryFunc callback.
+ * @data: Callback data for @func.
+ *
+ * Add a menu factory which will be called when the menu @menuid is
+ * created. The factory is free to add new items as it wishes to the
+ * menu provided in the callback.
+ *
+ * TODO: Make the menuid a pattern?
+ *
+ * Return value: A handle to the factory.
+ **/
+EMenuFactory *
+e_menu_class_add_factory(EMenuClass *klass, const char *menuid, EMenuFactoryFunc func, void *data)
+{
+ struct _EMenuFactory *f = g_malloc0(sizeof(*f));
+
+ printf("%p adding factory '%s' to class '%s'\n", klass, menuid, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ f->menuid = g_strdup(menuid);
+ f->factory = func;
+ f->factory_data = data;
+ e_dlist_addtail(&klass->factories, (EDListNode *)f);
+
+ /* setup the menu itself based on factories */
+ {
+ struct _EMenuFactory *j;
+
+ j = (struct _EMenuFactory *)klass->factories.head;
+ if (j->next == NULL) {
+ printf("%p no factories registered on menu???\n", klass);
+ }
+ }
+
+ return f;
+}
+
+/**
+ * e_menu_class_remove_factory:
+ * @klass: Class on which the factory was originally added.
+ * @f: Factory handle.
+ *
+ * Remove a popup factory. This must only be called once, and must
+ * only be called using a valid factory handle @f. After this call,
+ * @f is undefined.
+ **/
+void
+e_menu_class_remove_factory(EMenuClass *klass, EMenuFactory *f)
+{
+ e_dlist_remove((EDListNode *)f);
+ g_free(f->menuid);
+ g_free(f);
+}
+
+/**
+ * e_menu_target_new:
+ * @ep: An EMenu to which this target applies.
+ * @type: Target type, up to implementation.
+ * @size: Size of memory to allocate. Must be >= sizeof(EMenuTarget).
+ *
+ * Allocate a new menu target suitable for this class. @size is used
+ * to specify the actual target size, which may vary depending on the
+ * implementing class.
+ **/
+void *e_menu_target_new(EMenu *ep, int type, size_t size)
+{
+ EMenuTarget *t;
+
+ g_assert(size >= sizeof(EMenuTarget));
+
+ t = g_malloc0(size);
+ t->menu = ep;
+ g_object_ref(ep);
+ t->type = type;
+
+ return t;
+}
+
+/**
+ * e_menu_target_free:
+ * @ep: EMenu on which the target was allocated.
+ * @o: Tareget to free.
+ *
+ * Free a target.
+ **/
+void
+e_menu_target_free(EMenu *ep, void *o)
+{
+ EMenuTarget *t = o;
+
+ ((EMenuClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
+}
+
+/* ********************************************************************** */
+
+/* Main menu plugin handler */
+
+/* NB: This has significant overlap with EPopupHook */
+
+/*
+<e-plugin
+ class="com.ximian.mail.plugin.popup:1.0"
+ id="com.ximian.mail.plugin.popup.item:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ name="imap"
+ description="Main menu plugin">
+ <hook class="com.ximian.evolution.bonobomenu:1.0">
+ <menu id="any" target="select" view="com.ximian.mail">
+ <ui file="ui file1"/>
+ <ui file="ui file2"/>
+ <pixmap command="command" pixmap="stockname" size="menu|button|small_toolbar|large_toolbar|dnd|dialog"/>
+ <item
+ type="item|toggle"
+ verb="verb"
+ enable="select_one"
+ visible="select_one"
+ activate="doactivate"/>
+ </menu>
+ </hook>
+ </extension>
+
+*/
+
+static void *emph_parent_class;
+#define emph ((EMenuHook *)eph)
+
+/* must have 1:1 correspondence with e-menu types in order */
+static const EPluginHookTargetKey emph_item_types[] = {
+ { "item", E_MENU_ITEM },
+ { "toggle", E_MENU_TOGGLE },
+ { "radio", E_MENU_RADIO },
+ { 0 }
+};
+
+/* 1:1 with e-icon-factory sizes */
+static const EPluginHookTargetKey emph_pixmap_sizes[] = {
+ { "menu", 0 },
+ { "button", 1},
+ { "small_toolbar", 2},
+ { "large_toolbar", 3},
+ { "dnd", 4},
+ { "dialog", 5},
+ { 0 }
+};
+
+static void
+emph_menu_activate(EMenu *em, EMenuItem *item, void *data)
+{
+ EMenuHook *hook = data;
+
+ printf("invoking plugin hook '%s' %p\n", (char *)item->user_data, em->target);
+
+ e_plugin_invoke(hook->hook.plugin, item->user_data, em->target);
+}
+
+static void
+emph_menu_toggle_activate(EMenu *em, EMenuItem *item, int state, void *data)
+{
+ EMenuHook *hook = data;
+
+ /* FIXME: where does the toggle state go? */
+ printf("invoking plugin hook '%s' %p\n", (char *)item->user_data, em->target);
+
+ e_plugin_invoke(hook->hook.plugin, item->user_data, em->target);
+}
+
+static void
+emph_menu_factory(EMenu *emp, void *data)
+{
+ struct _EMenuHookMenu *menu = data;
+
+ printf("menu factory, adding %d items\n", g_slist_length(menu->items));
+
+ if (menu->items)
+ e_menu_add_items(emp, menu->items, menu->uis, menu->pixmaps, NULL, menu->hook);
+}
+
+static void
+emph_free_item(struct _EMenuItem *item)
+{
+ g_free(item->path);
+ g_free(item->verb);
+ g_free(item->user_data);
+ g_free(item);
+}
+
+static void
+emph_free_ui(struct _EMenuUIFile *ui)
+{
+ g_free(ui->appdir);
+ g_free(ui->appname);
+ g_free(ui->filename);
+}
+
+static void
+emph_free_pixmap(struct _EMenuPixmap *pixmap)
+{
+ g_free(pixmap->command);
+ g_free(pixmap->name);
+ g_free(pixmap->pixmap);
+ g_free(pixmap);
+}
+
+static void
+emph_free_menu(struct _EMenuHookMenu *menu)
+{
+ g_slist_foreach(menu->items, (GFunc)emph_free_item, NULL);
+ g_slist_free(menu->items);
+ g_slist_foreach(menu->uis, (GFunc)emph_free_ui, NULL);
+ g_slist_free(menu->uis);
+ g_slist_foreach(menu->pixmaps, (GFunc)emph_free_pixmap, NULL);
+ g_slist_free(menu->pixmaps);
+
+ g_free(menu->id);
+ g_free(menu);
+}
+
+static struct _EMenuItem *
+emph_construct_item(EPluginHook *eph, EMenuHookMenu *menu, xmlNodePtr root, EMenuHookTargetMap *map)
+{
+ struct _EMenuItem *item;
+
+ printf(" loading menu item\n");
+ item = g_malloc0(sizeof(*item));
+ item->type = e_plugin_hook_id(root, emph_item_types, "type");
+ item->path = e_plugin_xml_prop(root, "path");
+ item->verb = e_plugin_xml_prop(root, "verb");
+ item->visible = e_plugin_hook_mask(root, map->mask_bits, "visible");
+ item->enable = e_plugin_hook_mask(root, map->mask_bits, "enable");
+ item->user_data = e_plugin_xml_prop(root, "activate");
+ if ((item->type & E_MENU_TYPE_MASK) == E_MENU_TOGGLE)
+ item->activate = G_CALLBACK(emph_menu_toggle_activate);
+ else
+ item->activate = G_CALLBACK(emph_menu_activate);
+
+ if (item->type == -1 || item->user_data == NULL)
+ goto error;
+
+ printf(" path=%s\n", item->path);
+ printf(" verb=%s\n", item->verb);
+
+ return item;
+error:
+ printf("error!\n");
+ emph_free_item(item);
+ return NULL;
+}
+
+static struct _EMenuPixmap *
+emph_construct_pixmap(EPluginHook *eph, EMenuHookMenu *menu, xmlNodePtr root)
+{
+ struct _EMenuPixmap *pixmap;
+
+ printf(" loading menu pixmap\n");
+ pixmap = g_malloc0(sizeof(*pixmap));
+ pixmap->command = e_plugin_xml_prop(root, "command");
+ pixmap->name = e_plugin_xml_prop(root, "pixmap");
+ pixmap->size = e_plugin_hook_id(root, emph_pixmap_sizes, "size");
+
+ if (pixmap->command == NULL || pixmap->name == NULL || pixmap->size == -1)
+ goto error;
+
+ return pixmap;
+error:
+ printf("error!\n");
+ emph_free_pixmap(pixmap);
+ return NULL;
+}
+
+static struct _EMenuHookMenu *
+emph_construct_menu(EPluginHook *eph, xmlNodePtr root)
+{
+ struct _EMenuHookMenu *menu;
+ xmlNodePtr node;
+ EMenuHookTargetMap *map;
+ EMenuHookClass *klass = (EMenuHookClass *)G_OBJECT_GET_CLASS(eph);
+ char *tmp;
+
+ printf(" loading menu\n");
+ menu = g_malloc0(sizeof(*menu));
+ menu->hook = (EMenuHook *)eph;
+
+ tmp = xmlGetProp(root, "target");
+ if (tmp == NULL)
+ goto error;
+ map = g_hash_table_lookup(klass->target_map, tmp);
+ xmlFree(tmp);
+ if (map == NULL)
+ goto error;
+
+ menu->target_type = map->id;
+ menu->id = e_plugin_xml_prop(root, "id");
+ node = root->children;
+ while (node) {
+ if (0 == strcmp(node->name, "item")) {
+ struct _EMenuItem *item;
+
+ item = emph_construct_item(eph, menu, node, map);
+ if (item)
+ menu->items = g_slist_append(menu->items, item);
+ } else if (0 == strcmp(node->name, "ui")) {
+ tmp = xmlGetProp(node, "file");
+ if (tmp) {
+ EMenuUIFile *ui = g_malloc0(sizeof(*ui));
+
+ ui->filename = tmp;
+ ui->appdir = g_strdup("/tmp");
+ ui->appname = g_strdup("Evolution");
+ menu->uis = g_slist_append(menu->uis, ui);
+ }
+ } else if (0 == strcmp(node->name, "pixmap")) {
+ struct _EMenuPixmap *pixmap;
+
+ pixmap = emph_construct_pixmap(eph, menu, node);
+ if (pixmap)
+ menu->pixmaps = g_slist_append(menu->pixmaps, pixmap);
+ }
+ node = node->next;
+ }
+
+ return menu;
+error:
+ printf("error loading menu hook\n");
+ emph_free_menu(menu);
+ return NULL;
+}
+
+static int
+emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ EMenuClass *klass;
+
+ printf("loading menu hook\n");
+
+ if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
+ return -1;
+
+ klass = ((EMenuHookClass *)G_OBJECT_GET_CLASS(eph))->menu_class;
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "menu") == 0) {
+ struct _EMenuHookMenu *menu;
+
+ menu = emph_construct_menu(eph, node);
+ if (menu) {
+ printf(" plugin adding factory %p\n", klass);
+ e_menu_class_add_factory(klass, menu->id, emph_menu_factory, menu);
+ emph->menus = g_slist_append(emph->menus, menu);
+ }
+ }
+
+ node = node->next;
+ }
+
+ eph->plugin = ep;
+
+ return 0;
+}
+
+static void
+emph_finalise(GObject *o)
+{
+ EPluginHook *eph = (EPluginHook *)o;
+
+ g_slist_foreach(emph->menus, (GFunc)emph_free_menu, NULL);
+ g_slist_free(emph->menus);
+
+ ((GObjectClass *)emph_parent_class)->finalize(o);
+}
+
+static void
+emph_class_init(EPluginHookClass *klass)
+{
+ printf("EMenuHook class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ ((GObjectClass *)klass)->finalize = emph_finalise;
+ klass->construct = emph_construct;
+
+ /* this is actually an abstract implementation but list it anyway */
+ klass->id = "com.ximian.evolution.bonobomenu:1.0";
+
+ ((EMenuHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
+ ((EMenuHookClass *)klass)->menu_class = g_type_class_ref(e_menu_get_type());
+}
+
+/**
+ * e_menu_hook_get_type:
+ *
+ * Standard GObject function to get the object type. Used to subclass
+ * EMenuHook.
+ *
+ * Return value: The type of the menu hook class.
+ **/
+GType
+e_menu_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EMenuHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
+ sizeof(EMenuHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
+ type = g_type_register_static(e_plugin_hook_get_type(), "EMenuHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_menu_hook_class_add_target_map:
+ * @klass: The derived EMenuHook class.
+ * @map: A map used to describe a single EMenuTarget for this class.
+ *
+ * Adds a target map to a concrete derived class of EMenu. The target
+ * map enumerates a single target type, and the enable mask bit names,
+ * so that the type can be loaded automatically by the EMenu class.
+ **/
+void e_menu_hook_class_add_target_map(EMenuHookClass *klass, const EMenuHookTargetMap *map)
+{
+ g_hash_table_insert(klass->target_map, (void *)map->type, (void *)map);
+}
diff --git a/e-util/e-menu.h b/e-util/e-menu.h
new file mode 100644
index 0000000000..a6ff19c907
--- /dev/null
+++ b/e-util/e-menu.h
@@ -0,0 +1,323 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michel Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __E_MENU_H__
+#define __E_MENU_H__
+
+#include <glib-object.h>
+#include "e-util/e-msgport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+/* This is an abstract popup menu management/merging class.
+
+ To implement your own popup menu system, just create your own
+ target types and implement the target free method. */
+
+typedef struct _EMenu EMenu;
+typedef struct _EMenuClass EMenuClass;
+
+typedef struct _EMenuItem EMenuItem;
+typedef struct _EMenuUIFile EMenuUIFile;
+typedef struct _EMenuPixmap EMenuPixmap;
+
+typedef struct _EMenuFactory EMenuFactory; /* anonymous type */
+typedef struct _EMenuTarget EMenuTarget;
+
+typedef void (*EMenuFactoryFunc)(EMenu *emp, void *data);
+typedef void (*EMenuActivateFunc)(EMenu *, EMenuItem *, void *data);
+typedef void (*EMenuToggleActivateFunc)(EMenu *, EMenuItem *, int state, void *data);
+typedef void (*EMenuItemsFunc)(EMenu *, GSList *items, GSList *uifiles, GSList *pixmaps, void *data);
+
+/**
+ * enum _e_menu_t - Menu item type.
+ *
+ * @E_MENU_ITEM: Normal menu item.
+ * @E_MENU_TOGGLE: Toggle menu item.
+ * @E_MENU_RADIO: unimplemented.
+ * @E_MENU_TYPE_MASK: Mask used to separate item type from option bits.
+ * @E_MENU_ACTIVE: Whether a toggle item is active.
+ *
+ * The type of menu items which are supported by the menu system.
+ **/
+enum _e_menu_t {
+ E_MENU_ITEM = 0,
+ E_MENU_TOGGLE,
+ E_MENU_RADIO,
+ E_MENU_TYPE_MASK = 0xffff,
+ E_MENU_ACTIVE = 0x10000,
+};
+
+/**
+ * struct _EMenuItem - A BonoboUI menu item.
+ *
+ * @type: Menu item type. %E_MENU_ITEM or %E_MENU_TOGGLE.
+ * @path: BonoboUI Path to the menu item.
+ * @verb: BonoboUI verb for the menu item.
+ * @activate: Callback when the menu item is selected. This will be a
+ * EMenuToggleActivateFunc for toggle items or EMenuActivateFunc for
+ * normal items.
+ * @user_data: User data for item.
+ * @visible: Visibility mask, unimplemented.
+ * @enable: Sensitivity mask, combined with the target mask.
+ *
+ * An EMenuItem defines a single menu item. This menu item is used to
+ * hook onto callbacks from the bonobo menus, but not to build or
+ * merge the menu itself.
+ **/
+struct _EMenuItem {
+ enum _e_menu_t type;
+ char *path; /* full path? can we just create it from verb? */
+ char *verb; /* command verb */
+ GCallback activate; /* depends on type, the bonobo activate callback */
+ void *user_data; /* up to caller to use */
+ guint32 visible; /* is visible mask */
+ guint32 enable; /* is enable mask */
+};
+
+/**
+ * struct _EMenuPixmap - A menu icon holder.
+ *
+ * @command: The path to the command or verb to which this pixmap belongs.
+ * @name: The name of the icon. Either an icon-theme name or the full
+ * pathname of the icon.
+ * @size: The e-icon-factory icon size.
+ * @pixmap: The pixmap converted to XML format. If not set, then EMenu will
+ * create it as required. This must be freed if set in the free function.
+ *
+ * Used to track all pixmap items used in menus. These need to be
+ * supplied separately from the menu definition.
+ **/
+struct _EMenuPixmap {
+ char *command;
+ char *name;
+ int size;
+ char *pixmap;
+};
+
+/**
+ * struct _EMenuUIFile - A meu UI file holder.
+ *
+ * @appdir: TODO; should this be handled internally.
+ * @appname: TODO; should this be handled internally.
+ * @filename: The filename of the BonoboUI XML menu definition.
+ *
+ * These values are passed directly to bonobo_ui_util_set_ui() when
+ * the menu is activated.
+ **/
+struct _EMenuUIFile {
+ char *appdir;
+ char *appname;
+ char *filename;
+};
+
+/**
+ * struct _EMenuTarget - A BonoboUI menu target definition.
+ *
+ * @menu: The parent menu object, used for virtual methods on the target.
+ * @widget: The parent widget where available. In some cases the type
+ * of this object is part of the published api for the target, in
+ * others it is merely a GtkWidget from which you can find the
+ * toplevel widget.
+ * @type: Target type. This will be defined by the implementation.
+ * @mask: Target mask. This is used to sensitise show items based on
+ * their definition in EMenuItem.
+ *
+ * An EMenuTarget defines the context for a specific view instance.
+ * It is used to enable and show menu items, and to provide contextual
+ * data to menu invocations.
+ **/
+struct _EMenuTarget {
+ struct _EMenu *menu; /* used for virtual methods */
+
+ struct _GtkWidget *widget; /* used if you need a parent toplevel, if available */
+ guint32 type; /* for implementors */
+
+ guint32 mask; /* enable/visible mask */
+
+ /* implementation fields follow */
+};
+
+/**
+ * struct _EMenu - A BonoboUI menu manager object.
+ *
+ * @object: Superclass.
+ * @priv: Private data.
+ * @menuid: The id of this menu instance.
+ * @uic: The current BonoboUIComponent which stores the actual menu
+ * items this object manages.
+ * @target: The current target for the view.
+ *
+ * The EMenu manager object manages the mappings between EMenuItems
+ * and the BonoboUI menus loaded from UI files.
+ **/
+struct _EMenu {
+ GObject object;
+ struct _EMenuPrivate *priv;
+
+ char *menuid;
+ struct _BonoboUIComponent *uic;
+ EMenuTarget *target;
+};
+
+/**
+ * struct _EMenuClass -
+ *
+ * @object_class: Superclass type.
+ * @factories: A list of factories for this particular class of main menu.
+ * @target_free: Virtual method to free the menu target. The base
+ * class free method frees the allocation and unrefs the EMenu parent
+ * pointer.
+ *
+ * The EMenu class definition. This should be sub-classed for each
+ * component that wants to provide hookable main menus. The subclass
+ * only needs to know how to allocate and free the various target
+ * types it supports.
+ **/
+struct _EMenuClass {
+ GObjectClass object_class;
+
+ EDList factories;
+
+ void (*target_free)(EMenu *ep, EMenuTarget *t);
+};
+
+GType e_menu_get_type(void);
+
+/* Static class methods */
+EMenuFactory *e_menu_class_add_factory(EMenuClass *klass, const char *menuid, EMenuFactoryFunc func, void *data);
+void e_menu_class_remove_factory(EMenuClass *klass, EMenuFactory *f);
+
+EMenu *e_menu_construct(EMenu *menu, const char *menuid);
+
+void e_menu_add_ui(EMenu *, const char *appdir, const char *appname, const char *filename);
+void e_menu_add_pixmap(EMenu *, const char *cmd, const char *name, int size);
+
+void *e_menu_add_items(EMenu *emp, GSList *items, GSList *uifiles, GSList *pixmaps, EMenuItemsFunc freefunc, void *data);
+void e_menu_remove_items(EMenu *emp, void *handle);
+
+void e_menu_activate(EMenu *, struct _BonoboUIComponent *uic, int act);
+void e_menu_update_target(EMenu *, void *);
+
+void *e_menu_target_new(EMenu *, int type, size_t size);
+void e_menu_target_free(EMenu *, void *);
+
+/* ********************************************************************** */
+
+/* menu plugin, they are closely integrated */
+
+/* To implement a basic menu plugin, you just need to subclass
+ this and initialise the class target type tables */
+
+#include "e-util/e-plugin.h"
+
+typedef struct _EMenuHookPixmap EMenuHookPixmap;
+typedef struct _EMenuHookMenu EMenuHookMenu;
+typedef struct _EMenuHook EMenuHook;
+typedef struct _EMenuHookClass EMenuHookClass;
+
+typedef struct _EPluginHookTargetMap EMenuHookTargetMap;
+typedef struct _EPluginHookTargetKey EMenuHookTargetMask;
+
+typedef void (*EMenuHookFunc)(struct _EPlugin *plugin, EMenuTarget *target);
+
+/**
+ * struct _EMenuHookMenu - A group of items targetting a specific menu.
+ *
+ * @hook: Parent pointer.
+ * @id: The identifier of the menu or view to which these items belong.
+ * @target_type: The target number of the type of target these menu
+ * items expect. This will be defined by menu itself.
+ * @items: A list of EMenuItems.
+ * @uis: A list of filenames of the BonoboUI files that need to be
+ * loaded for an active view.
+ * @pixmaps: A list of EMenuHookPixmap structures for the menus.
+ *
+ * This structure is used to keep track of all of the items that a
+ * plugin wishes to add to specific menu. This is used internally by
+ * a factory method defined by the EMenuHook to add the right menu
+ * items to a given view.
+ **/
+struct _EMenuHookMenu {
+ struct _EMenuHook *hook; /* parent pointer */
+ char *id; /* target menu id for these menu items */
+ int target_type; /* target type, not used */
+ GSList *items; /* items to add to menu */
+ GSList *uis; /* ui files */
+ GSList *pixmaps; /* pixmap descriptors */
+};
+
+/**
+ * struct _EMenuHook - A BonoboUI menu hook.
+ *
+ * @hook: Superclass.
+ * @menus: A list of EMenuHookMenus for all menus registered on this
+ * hook type.
+ *
+ * The EMenuHook class loads and manages the meta-data to required to
+ * map plugin definitions to physical menus.
+ **/
+struct _EMenuHook {
+ EPluginHook hook;
+
+ GSList *menus;
+};
+
+/**
+ * struct _EMenuHookClass - Menu hook type.
+ *
+ * @hook_class: Superclass type.
+ * @target_map: Table of EluginHookTargetMaps which enumerate the
+ * target types and enable bits of the implementing class.
+ * @menu_class: The EMenuClass of the corresponding popup manager for
+ * implementing the class.
+ *
+ * The EMenuHookClass is an empty concrete class. It must be
+ * subclassed and initialised appropriately to perform useful work.
+ *
+ * The EPluginHookClass.id must be set to the name and version of the
+ * hook handler the implementation defines. The @target_map must be
+ * initialised with the data required to enumerate the target types
+ * and enable flags supported by the implementing class.
+ **/
+struct _EMenuHookClass {
+ EPluginHookClass hook_class;
+
+ /* EMenuHookTargetMap by .type */
+ GHashTable *target_map;
+ /* the menu class these menus belong to */
+ EMenuClass *menu_class;
+};
+
+GType e_menu_hook_get_type(void);
+
+/* for implementors */
+void e_menu_hook_class_add_target_map(EMenuHookClass *klass, const EMenuHookTargetMap *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E_MENU_H__ */
diff --git a/e-util/e-plugin-mono.c b/e-util/e-plugin-mono.c
new file mode 100644
index 0000000000..9b7c39a3f9
--- /dev/null
+++ b/e-util/e-plugin-mono.c
@@ -0,0 +1,198 @@
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+#include "e-plugin-mono.h"
+
+#include <mono/metadata/debug-helpers.h>
+#include <mono/metadata/object.h>
+#include <mono/metadata/appdomain.h>
+#include <mono/metadata/assembly.h>
+#include <mono/jit/jit.h>
+
+static MonoDomain *domain;
+
+/* ********************************************************************** */
+static void *epm_parent_class;
+
+typedef struct _EPluginMonoPrivate {
+ MonoAssembly *assembly;
+ MonoClass *klass;
+ MonoObject *plugin;
+ GHashTable *methods;
+} EPluginMonoPrivate;
+
+#define epm ((EPluginMono *)ep)
+
+static char *
+get_xml_prop(xmlNodePtr node, const char *id)
+{
+ char *p = xmlGetProp(node, id);
+ char *out = NULL;
+
+ if (p) {
+ out = g_strdup(p);
+ xmlFree(p);
+ }
+
+ return out;
+}
+
+/*
+ Two approaches:
+ You can have a Evolution.Plugin implementation which has every callback as methods on it.
+ Or you can just use static methods for everything.
+
+ All methods take a single (structured) argument.
+*/
+
+static void *
+epm_invoke(EPlugin *ep, const char *name, void *data)
+{
+ EPluginMonoPrivate *p = epm->priv;
+ MonoMethodDesc *d;
+ MonoMethod *m;
+ MonoObject *x = NULL, *res;
+ void **params;
+
+ /* we need to do this every time since we may be called from any thread for some uses */
+ mono_thread_attach(domain);
+
+ if (p->assembly == NULL) {
+ p->assembly = mono_domain_assembly_open(domain, epm->location);
+ if (p->assembly == NULL) {
+ g_warning("can't load assembly '%s'", epm->location);
+ return NULL;
+ }
+
+ if (epm->handler == NULL
+ || (p->klass = mono_class_from_name(mono_assembly_get_image(p->assembly), "", epm->handler)) == NULL) {
+ printf("Using static callbacks only");
+ } else {
+ p->plugin = mono_object_new(domain, p->klass);
+ /* could conceivably init with some context too */
+ mono_runtime_object_init(p->plugin);
+ }
+ }
+
+ m = g_hash_table_lookup(p->methods, name);
+ if (m == NULL) {
+ if (p->klass) {
+ printf("looking up method '%s' in class\n", name);
+ /* class method */
+ d = mono_method_desc_new(name, FALSE);
+ if (d == NULL) {
+ g_warning("Can't create method descriptor for '%s'", name);
+ return NULL;
+ }
+
+ m = mono_method_desc_search_in_class(d, p->klass);
+ if (m == NULL) {
+ g_warning("Can't find method callback '%s'", name);
+ return NULL;
+ }
+ } else {
+ printf("looking up static method '%s'\n", name);
+ /* static method */
+ d = mono_method_desc_new(name, FALSE);
+ if (d == NULL) {
+ g_warning("Can't create method descriptor for '%s'", name);
+ return NULL;
+ }
+
+ m = mono_method_desc_search_in_image(d, mono_assembly_get_image(p->assembly));
+ if (m == NULL) {
+ g_warning("Can't find method callback '%s'", name);
+ return NULL;
+ }
+ }
+
+ g_hash_table_insert(p->methods, g_strdup(name), m);
+ }
+
+ params = g_malloc0(sizeof(*params)*1);
+ params[0] = &data;
+ res = mono_runtime_invoke(m, p->plugin, params, &x);
+ /* do i need to free params?? */
+
+ if (x)
+ mono_print_unhandled_exception(x);
+
+ if (res) {
+ void **p = mono_object_unbox(res);
+ printf("mono method returned '%p' %ld\n", *p, (long int)*p);
+ return *p;
+ } else
+ return NULL;
+}
+
+static int
+epm_construct(EPlugin *ep, xmlNodePtr root)
+{
+ if (((EPluginClass *)epm_parent_class)->construct(ep, root) == -1)
+ return -1;
+
+ epm->location = get_xml_prop(root, "location");
+ epm->handler = get_xml_prop(root, "handler");
+
+ if (epm->location == NULL)
+ return -1;
+
+ return 0;
+}
+
+static void
+epm_finalise(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+ EPluginMonoPrivate *p = epm->priv;
+
+ g_free(epm->location);
+ g_free(epm->handler);
+
+ g_hash_table_foreach(p->methods, (GHFunc)g_free, NULL);
+ g_hash_table_destroy(p->methods);
+
+ g_free(epm->priv);
+
+ ((GObjectClass *)epm_parent_class)->finalize(o);
+}
+
+static void
+epm_class_init(EPluginClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = epm_finalise;
+ klass->construct = epm_construct;
+ klass->invoke = epm_invoke;
+ klass->type = "mono";
+}
+
+static void
+epm_init(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ epm->priv = g_malloc0(sizeof(*epm->priv));
+ epm->priv->methods = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+GType
+e_plugin_mono_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPluginMonoClass), NULL, NULL, (GClassInitFunc) epm_class_init, NULL, NULL,
+ sizeof(EPluginMono), 0, (GInstanceInitFunc) epm_init,
+ };
+
+ epm_parent_class = g_type_class_ref(e_plugin_get_type());
+ type = g_type_register_static(e_plugin_get_type(), "EPluginMono", &info, 0);
+ domain = mono_jit_init("Evolution");
+ mono_thread_attach(domain);
+ }
+
+ return type;
+}
diff --git a/e-util/e-plugin-mono.h b/e-util/e-plugin-mono.h
new file mode 100644
index 0000000000..4c49edf71c
--- /dev/null
+++ b/e-util/e-plugin-mono.h
@@ -0,0 +1,27 @@
+
+#ifndef _E_PLUGIN_MONO_H
+#define _E_PLUGIN_MONO_H
+
+#include "e-plugin.h"
+
+/* ********************************************************************** */
+
+typedef struct _EPluginMono EPluginMono;
+typedef struct _EPluginMonoClass EPluginMonoClass;
+
+struct _EPluginMono {
+ EPlugin plugin;
+
+ struct _EPluginMonoPrivate *priv;
+
+ char *location; /* location */
+ char *handler; /* handler class */
+};
+
+struct _EPluginMonoClass {
+ EPluginClass plugin_class;
+};
+
+GType e_plugin_mono_get_type(void);
+
+#endif /* ! _E_PLUGIN_MONO_H */
diff --git a/e-util/e-plugin.c b/e-util/e-plugin.c
new file mode 100644
index 0000000000..52f92cbea9
--- /dev/null
+++ b/e-util/e-plugin.c
@@ -0,0 +1,850 @@
+
+#include <sys/types.h>
+#include <dirent.h>
+#include <string.h>
+
+#include <glib/gi18n.h>
+
+#include "e-plugin.h"
+
+/* plugin debug */
+#define pd(x) x
+/* plugin hook debug */
+#define phd(x) x
+
+/*
+<camel-plugin
+ class="com.ximian.camel.plugin.provider:1.0"
+ id="com.ximian.camel.provider.imap:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ factory="camel_imap_provider_new">
+ <name>imap</name>
+ <description>IMAP4 and IMAP4v1 mail store</description>
+ <class-data class="com.ximian.camel.plugin.provider:1.0"
+ protocol="imap"
+ domain="mail"
+ flags="remote,source,storage,ssl"/>
+</camel-plugin>
+
+<camel-plugin
+ class="com.ximian.camel.plugin.sasl:1.0"
+ id="com.ximian.camel.sasl.plain:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelsasl.so"
+ factory="camel_sasl_plain_new">
+ <name>PLAIN</name>
+ <description>SASL PLAIN authentication mechanism</description>
+</camel-plugin>
+*/
+
+static GObjectClass *ep_parent_class;
+static GHashTable *ep_types;
+static GSList *ep_path;
+
+static int
+ep_construct(EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ int res = -1;
+
+ ep->domain = e_plugin_xml_prop(root, "domain");
+ ep->name = e_plugin_xml_prop_domain(root, "name", ep->domain);
+
+ printf("creating plugin '%s'\n", ep->name);
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "hook") == 0) {
+ struct _EPluginHook *hook;
+
+ hook = e_plugin_hook_new(ep, node);
+ if (hook)
+ ep->hooks = g_slist_prepend(ep->hooks, hook);
+ else {
+ char *tmp = xmlGetProp(node, "class");
+
+ g_warning("Plugin '%s' failed to load hook '%s'", ep->name, tmp?tmp:"unknown");
+ if (tmp)
+ xmlFree(tmp);
+ }
+ } else if (strcmp(node->name, "description") == 0) {
+ ep->description = e_plugin_xml_content_domain(node, ep->domain);
+ }
+ node = node->next;
+ }
+ res = 0;
+
+ return res;
+}
+
+static void
+ep_finalise(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ g_free(ep->description);
+ g_free(ep->name);
+ g_free(ep->domain);
+
+ g_slist_foreach(ep->hooks, (GFunc)g_object_unref, NULL);
+ g_slist_free(ep->hooks);
+
+ ((GObjectClass *)ep_parent_class)->finalize(o);
+}
+
+static void
+ep_init(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ ep->enabled = TRUE;
+}
+
+static void
+ep_class_init(EPluginClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = ep_finalise;
+ klass->construct = ep_construct;
+}
+
+/**
+ * e_plugin_get_type:
+ *
+ * Standard GObject type function. This is only an abstract class, so
+ * you can only use this to subclass EPlugin.
+ *
+ * Return value: The type.
+ **/
+GType
+e_plugin_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ char *path, *col, *p;
+
+ static const GTypeInfo info = {
+ sizeof(EPluginClass), NULL, NULL, (GClassInitFunc)ep_class_init, NULL, NULL,
+ sizeof(EPlugin), 0, (GInstanceInitFunc)ep_init,
+ };
+
+ ep_parent_class = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EPlugin", &info, 0);
+
+ /* Add paths in the environment variable or default global and user specific paths */
+ path = g_strdup(getenv("EVOLUTION_PLUGIN_PATH"));
+ if (path == NULL) {
+ /* Add the global path */
+ e_plugin_add_load_path(EVOLUTION_PLUGINDIR);
+
+ path = g_build_filename(g_get_home_dir(), ".eplug", NULL);
+ }
+
+ p = path;
+ while ((col = strchr(p, ':'))) {
+ *col++ = 0;
+ e_plugin_add_load_path(p);
+ p = col;
+ }
+ e_plugin_add_load_path(p);
+ g_free(path);
+ }
+
+ return type;
+}
+
+static int
+ep_load(const char *filename)
+{
+ xmlDocPtr doc;
+ xmlNodePtr root;
+ int res = -1;
+ EPlugin *ep;
+
+ doc = xmlParseFile(filename);
+ if (doc == NULL) {
+ return -1;
+ }
+
+ root = xmlDocGetRootElement(doc);
+ if (strcmp(root->name, "e-plugin-list") != 0)
+ goto fail;
+
+ root = root->children;
+ while (root) {
+ if (strcmp(root->name, "e-plugin") == 0) {
+ char *prop;
+ EPluginClass *klass;
+
+ prop = xmlGetProp(root, "type");
+ if (prop == NULL)
+ goto fail;
+
+ klass = g_hash_table_lookup(ep_types, prop);
+ if (klass == NULL) {
+ g_warning("can't find plugin type '%s'\n", prop);
+ xmlFree(prop);
+ goto fail;
+ }
+
+ xmlFree(prop);
+
+ ep = g_object_new(G_TYPE_FROM_CLASS(klass), NULL);
+ if (e_plugin_construct(ep, root) == -1) {
+ g_object_unref(ep);
+ } else {
+ /* ... */
+ }
+ }
+ root = root->next;
+ }
+
+ res = 0;
+fail:
+ xmlFreeDoc(doc);
+ return res;
+}
+
+/**
+ * e_plugin_add_load_path:
+ * @path: The path to add to search for plugins.
+ *
+ * Add a path to be searched when e_plugin_load_plugins() is called.
+ * By default ~/.eplug is used as the search path unless overriden by
+ * the environmental variable %EVOLUTION_PLUGIN_PATH.
+ *
+ * %EVOLUTION_PLUGIN_PATH is a : separated list of paths to search for
+ * plugin definitions in order.
+ *
+ * Plugin definitions are XML files ending in the extension ".eplug".
+ **/
+void
+e_plugin_add_load_path(const char *path)
+{
+ ep_path = g_slist_append(ep_path, g_strdup(path));
+}
+
+/**
+ * e_plugin_load_plugins:
+ *
+ * Scan the search path, looking for plugin definitions, and load them
+ * into memory.
+ *
+ * Return value: Returns -1 if an error occured.
+ **/
+int
+e_plugin_load_plugins(void)
+{
+ GSList *l;
+
+ if (ep_types == NULL) {
+ g_warning("no plugin types defined");
+ return 0;
+ }
+
+
+ for (l = ep_path;l;l = g_slist_next(l)) {
+ DIR *dir;
+ struct dirent *d;
+ char *path = l->data;
+
+ printf("scanning plugin dir '%s'\n", path);
+
+ dir = opendir(path);
+ if (dir == NULL) {
+ g_warning("Could not find plugin path: %s", path);
+ continue;
+ }
+
+ while ( (d = readdir(dir)) ) {
+ if (strlen(d->d_name) > 6
+ && !strcmp(d->d_name + strlen(d->d_name) - 6, ".eplug")) {
+ char * name = g_build_filename(path, d->d_name, NULL);
+
+ ep_load(name);
+ g_free(name);
+ }
+ }
+
+ closedir(dir);
+ }
+
+ return 0;
+}
+
+/**
+ * e_plugin_register_type:
+ * @type: The GObject type of the plugin loader.
+ *
+ * Register a new plugin type with the plugin system. Each type must
+ * subclass EPlugin and must override the type member of the
+ * EPluginClass with a unique name.
+ **/
+void
+e_plugin_register_type(GType type)
+{
+ EPluginClass *klass;
+
+ if (ep_types == NULL)
+ ep_types = g_hash_table_new(g_str_hash, g_str_equal);
+
+ klass = g_type_class_ref(type);
+
+ pd(printf("register plugin type '%s'\n", klass->type));
+
+ g_hash_table_insert(ep_types, (void *)klass->type, klass);
+}
+
+/**
+ * e_plugin_construct:
+ * @ep: An EPlugin derived object.
+ * @root: The XML root node of the sub-tree containing the plugin
+ * definition.
+ *
+ * Helper to invoke the construct virtual method.
+ *
+ * Return value: The return from the construct virtual method.
+ **/
+int
+e_plugin_construct(EPlugin *ep, xmlNodePtr root)
+{
+ return ((EPluginClass *)G_OBJECT_GET_CLASS(ep))->construct(ep, root);
+}
+
+/**
+ * e_plugin_invoke:
+ * @ep:
+ * @name: The name of the function to invoke. The format of this name
+ * will depend on the EPlugin type and its language conventions.
+ * @data: The argument to the function. Its actual type depends on
+ * the hook on which the function resides. It is up to the called
+ * function to get this right.
+ *
+ * Helper to invoke the invoke virtual method.
+ *
+ * Return value: The return of the plugin invocation.
+ **/
+void *
+e_plugin_invoke(EPlugin *ep, const char *name, void *data)
+{
+ if (!ep->enabled)
+ g_warning("Invoking method on disabled plugin");
+
+ return ((EPluginClass *)G_OBJECT_GET_CLASS(ep))->invoke(ep, name, data);
+}
+
+/**
+ * e_plugin_enable:
+ * @ep:
+ * @state:
+ *
+ * Set the enable state of a plugin.
+ *
+ * THIS IS NOT FULLY IMPLEMENTED YET
+ **/
+void
+e_plugin_enable(EPlugin *ep, int state)
+{
+ GSList *l;
+
+ if ((ep->enabled == 0) == (state == 0))
+ return;
+
+ ep->enabled = state;
+ for (l=ep->hooks;l;l = g_slist_next(l)) {
+ EPluginHook *eph = l->data;
+
+ e_plugin_hook_enable(eph, state);
+ }
+}
+
+/**
+ * e_plugin_xml_prop:
+ * @node: An XML node.
+ * @id: The name of the property to retrieve.
+ *
+ * A static helper function to look up a property on an XML node, and
+ * ensure it is allocated in GLib system memory. If GLib isn't using
+ * the system malloc then it must copy the property value.
+ *
+ * Return value: The property, allocated in GLib memory, or NULL if no
+ * such property exists.
+ **/
+char *
+e_plugin_xml_prop(xmlNodePtr node, const char *id)
+{
+ char *p = xmlGetProp(node, id);
+
+ if (g_mem_is_system_malloc()) {
+ return p;
+ } else {
+ char * out = g_strdup(p);
+
+ if (p)
+ xmlFree(p);
+ return out;
+ }
+}
+
+/**
+ * e_plugin_xml_prop_domain:
+ * @node: An XML node.
+ * @id: The name of the property to retrieve.
+ * @domain: The translation domain for this string.
+ *
+ * A static helper function to look up a property on an XML node, and
+ * translate it based on @domain.
+ *
+ * Return value: The property, allocated in GLib memory, or NULL if no
+ * such property exists.
+ **/
+char *
+e_plugin_xml_prop_domain(xmlNodePtr node, const char *id, const char *domain)
+{
+ char *p, *out;
+
+ p = xmlGetProp(node, id);
+ if (p == NULL)
+ return NULL;
+
+ out = g_strdup(dgettext(domain, p));
+ xmlFree(p);
+
+ return out;
+}
+
+/**
+ * e_plugin_xml_int:
+ * @node: An XML node.
+ * @id: The name of the property to retrieve.
+ * @def: A default value if the property doesn't exist. Can be used
+ * to determine if the property isn't set.
+ *
+ * A static helper function to look up a property on an XML node as an
+ * integer. If the property doesn't exist, then @def is returned as a
+ * default value instead.
+ *
+ * Return value: The value if set, or @def if not.
+ **/
+int
+e_plugin_xml_int(xmlNodePtr node, const char *id, int def)
+{
+ char *p = xmlGetProp(node, id);
+
+ if (p)
+ return atoi(p);
+ else
+ return def;
+}
+
+/**
+ * e_plugin_xml_content:
+ * @node:
+ *
+ * A static helper function to retrieve the entire textual content of
+ * an XML node, and ensure it is allocated in GLib system memory. If
+ * GLib isn't using the system malloc them it must copy the content.
+ *
+ * Return value: The node content, allocated in GLib memory.
+ **/
+char *
+e_plugin_xml_content(xmlNodePtr node)
+{
+ char *p = xmlNodeGetContent(node);
+
+ if (g_mem_is_system_malloc()) {
+ return p;
+ } else {
+ char * out = g_strdup(p);
+
+ if (p)
+ xmlFree(p);
+ return out;
+ }
+}
+
+/**
+ * e_plugin_xml_content_domain:
+ * @node:
+ * @domain:
+ *
+ * A static helper function to retrieve the entire textual content of
+ * an XML node, and ensure it is allocated in GLib system memory. If
+ * GLib isn't using the system malloc them it must copy the content.
+ *
+ * Return value: The node content, allocated in GLib memory.
+ **/
+char *
+e_plugin_xml_content_domain(xmlNodePtr node, const char *domain)
+{
+ char *p, *out;
+
+ p = xmlNodeGetContent(node);
+ if (p == NULL)
+ return NULL;
+
+ out = g_strdup(dgettext(domain, p));
+ xmlFree(p);
+
+ return out;
+}
+
+/* ********************************************************************** */
+static void *epl_parent_class;
+
+#define epl ((EPluginLib *)ep)
+
+/* TODO:
+ We need some way to manage lifecycle.
+ We need some way to manage state.
+
+ Maybe just the g module init method will do, or we could add
+ another which returns context.
+
+ There is also the question of per-instance context, e.g. for config
+ pages.
+*/
+
+static void *
+epl_invoke(EPlugin *ep, const char *name, void *data)
+{
+ void *(*cb)(EPlugin *ep, void *data);
+
+ if (epl->module == NULL
+ && (epl->module = g_module_open(epl->location, 0)) == NULL) {
+ g_warning("can't load plugin '%s'", g_module_error());
+ return NULL;
+ }
+
+ if (!g_module_symbol(epl->module, name, (void *)&cb))
+ return NULL;
+
+ return cb(ep, data);
+}
+
+static int
+epl_construct(EPlugin *ep, xmlNodePtr root)
+{
+ if (((EPluginClass *)epl_parent_class)->construct(ep, root) == -1)
+ return -1;
+
+ epl->location = e_plugin_xml_prop(root, "location");
+
+ if (epl->location == NULL)
+ return -1;
+
+ return 0;
+}
+
+static void
+epl_finalise(GObject *o)
+{
+ EPlugin *ep = (EPlugin *)o;
+
+ g_free(epl->location);
+
+ if (epl->module)
+ g_module_close(epl->module);
+
+ ((GObjectClass *)epl_parent_class)->finalize(o);
+}
+
+static void
+epl_class_init(EPluginClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = epl_finalise;
+ klass->construct = epl_construct;
+ klass->invoke = epl_invoke;
+ klass->type = "shlib";
+}
+
+/**
+ * e_plugin_lib_get_type:
+ *
+ * Standard GObject function to retrieve the EPluginLib type. Use to
+ * register the type with the plugin system if you want to use shared
+ * library plugins.
+ *
+ * Return value: The EPluginLib type.
+ **/
+GType
+e_plugin_lib_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPluginLibClass), NULL, NULL, (GClassInitFunc) epl_class_init, NULL, NULL,
+ sizeof(EPluginLib), 0, (GInstanceInitFunc) NULL,
+ };
+
+ epl_parent_class = g_type_class_ref(e_plugin_get_type());
+ type = g_type_register_static(e_plugin_get_type(), "EPluginLib", &info, 0);
+ }
+
+ return type;
+}
+
+/* ********************************************************************** */
+static void *eph_parent_class;
+static GHashTable *eph_types;
+
+static int
+eph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ eph->plugin = ep;
+
+ return 0;
+}
+
+static void
+eph_enable(EPluginHook *eph, int state)
+{
+ /* NOOP */
+}
+
+static void
+eph_finalise(GObject *o)
+{
+ ((GObjectClass *)eph_parent_class)->finalize((GObject *)o);
+}
+
+static void
+eph_class_init(EPluginHookClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = eph_finalise;
+ klass->construct = eph_construct;
+ klass->enable = eph_enable;
+}
+
+/**
+ * e_plugin_hook_get_type:
+ *
+ * Standard GObject function to retrieve the EPluginHook type. Since
+ * EPluginHook is an abstract class, this is only used to subclass it.
+ *
+ * Return value: The EPluginHook type.
+ **/
+GType
+e_plugin_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPluginHookClass), NULL, NULL, (GClassInitFunc) eph_class_init, NULL, NULL,
+ sizeof(EPluginHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ eph_parent_class = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EPluginHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_plugin_hook_new:
+ * @ep: The parent EPlugin this hook belongs to.
+ * @root: The XML node of the root of the hook definition.
+ *
+ * This is a static factory method to instantiate a new EPluginHook
+ * object to represent a plugin hook.
+ *
+ * Return value: The EPluginHook appropriate for the XML definition at
+ * @root. NULL is returned if a syntax error is encountered.
+ **/
+EPluginHook *
+e_plugin_hook_new(EPlugin *ep, xmlNodePtr root)
+{
+ EPluginHookClass *type;
+ char *class;
+ EPluginHook *hook;
+
+ /* FIXME: Keep a list of all plugin hooks */
+
+ if (eph_types == NULL)
+ return NULL;
+
+ class = xmlGetProp(root, "class");
+ if (class == NULL)
+ return NULL;
+
+ type = g_hash_table_lookup(eph_types, class);
+ g_free(class);
+ if (type == NULL)
+ return NULL;
+
+ hook = g_object_new(G_OBJECT_CLASS_TYPE(type), NULL);
+ if (type->construct(hook, ep, root) == -1) {
+ g_object_unref(hook);
+ hook = NULL;
+ }
+
+ return hook;
+}
+
+/**
+ * e_plugin_hook_enable: Set hook enabled state.
+ * @eph:
+ * @state:
+ *
+ * Set the enabled state of the plugin hook. This is called by the
+ * plugin code.
+ *
+ * THIS IS NOT FULY IMEPLEMENTED YET
+ **/
+void
+e_plugin_hook_enable(EPluginHook *eph, int state)
+{
+ ((EPluginHookClass *)G_OBJECT_GET_CLASS(eph))->enable(eph, state);
+}
+
+/**
+ * e_plugin_hook_register_type:
+ * @type:
+ *
+ * Register a new plugin hook type with the plugin system. Each type
+ * must subclass EPluginHook and must override the id member of the
+ * EPluginHookClass with a unique identification string.
+ **/
+void
+e_plugin_hook_register_type(GType type)
+{
+ EPluginHookClass *klass;
+
+ if (eph_types == NULL)
+ eph_types = g_hash_table_new(g_str_hash, g_str_equal);
+
+ klass = g_type_class_ref(type);
+
+ phd(printf("register plugin hook type '%s'\n", klass->id));
+
+ g_hash_table_insert(eph_types, (void *)klass->id, klass);
+}
+
+/**
+ * e_plugin_hook_mask:
+ * @root: An XML node.
+ * @map: A zero-fill terminated array of EPluginHookTargeKeys used to
+ * map a string with a bit value.
+ * @prop: The property name.
+ *
+ * This is a static helper function which looks up a property @prop on
+ * the XML node @root, and then uses the @map table to convert it into
+ * a bitmask. The property value is a comma separated list of
+ * enumeration strings which are indexed into the @map table.
+ *
+ * Return value: A bitmask representing the inclusive-or of all of the
+ * integer values of the corresponding string id's stored in the @map.
+ **/
+guint32
+e_plugin_hook_mask(xmlNodePtr root, const struct _EPluginHookTargetKey *map, const char *prop)
+{
+ char *val, *p, *start, c;
+ guint32 mask = 0;
+
+ val = xmlGetProp(root, prop);
+ if (val == NULL)
+ return 0;
+
+ p = val;
+ do {
+ start = p;
+ while (*p && *p != ',')
+ p++;
+ c = *p;
+ *p = 0;
+ if (start != p) {
+ int i;
+
+ for (i=0;map[i].key;i++) {
+ if (!strcmp(map[i].key, start)) {
+ mask |= map[i].value;
+ break;
+ }
+ }
+ }
+ *p++ = c;
+ } while (c);
+
+ xmlFree(val);
+
+ return mask;
+}
+
+/**
+ * e_plugin_hook_id:
+ * @root:
+ * @map:
+ * @prop:
+ *
+ * This is a static helper function which looks up a property @prop on
+ * the XML node @root, and then uses the @map table to convert it into
+ * an integer.
+ *
+ * This is used as a helper wherever you need to represent an
+ * enumerated value in the XML.
+ *
+ * Return value: If the @prop value is in @map, then the corresponding
+ * integer value, if not, then ~0.
+ **/
+guint32
+e_plugin_hook_id(xmlNodePtr root, const struct _EPluginHookTargetKey *map, const char *prop)
+{
+ char *val;
+ int i;
+
+ val = xmlGetProp(root, prop);
+ if (val == NULL)
+ return ~0;
+
+ for (i=0;map[i].key;i++) {
+ if (!strcmp(map[i].key, val)) {
+ xmlFree(val);
+ return map[i].value;
+ }
+ }
+
+ xmlFree(val);
+
+ return ~0;
+}
+
+#if 0
+/*
+ e-mail-format-handler
+ mime_type
+ target
+*/
+struct _EMFormatPlugin {
+ EPlugin plugin;
+
+ char *target;
+ char *mime_type;
+ struct _EMFormatHandler *(*get_handler)(void);
+};
+
+struct _EMFormatPluginClass {
+ EPluginClass plugin_class;
+};
+
+#endif
+
+#if 0
+void em_setup_plugins(void);
+
+void
+em_setup_plugins(void)
+{
+ GType *e_plugin_mono_get_type(void);
+
+ e_plugin_register_type(e_plugin_lib_get_type());
+ e_plugin_register_type(e_plugin_mono_get_type());
+
+ e_plugin_hook_register_type(em_popup_hook_get_type());
+
+ e_plugin_load_plugins(".");
+}
+#endif
diff --git a/e-util/e-plugin.h b/e-util/e-plugin.h
new file mode 100644
index 0000000000..c6e132ac6f
--- /dev/null
+++ b/e-util/e-plugin.h
@@ -0,0 +1,221 @@
+
+#ifndef _E_PLUGIN_H
+#define _E_PLUGIN_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libxml/tree.h>
+
+/* ********************************************************************** */
+
+typedef struct _EPlugin EPlugin;
+typedef struct _EPluginClass EPluginClass;
+
+#define E_PLUGIN_CLASSID "com.ximian.evolution.plugin"
+
+/**
+ * struct _EPlugin -
+ *
+ * @object: Superclass.
+ * @description: A description of the plugin's purpose.
+ * @name: The name of the plugin.
+ * @domain: The translation domain for this plugin.
+ * @hooks: A list of the EPluginHooks this plugin requires.
+ * @enabled: Whether the plugin is enabled or not. This is not fully
+ * implemented.
+ *
+ * The base EPlugin object is used to represent each plugin directly.
+ * All of the plugin's hooks are loaded and managed through this
+ * object.
+ **/
+struct _EPlugin {
+ GObject object;
+
+ char *description;
+ char *name;
+ char *domain;
+ GSList *hooks;
+
+ int enabled:1;
+};
+
+/**
+ * struct _EPluginClass -
+ *
+ * @class: Superclass.
+ * @type: The plugin type. This is used by the plugin loader to
+ * determine which plugin object to instantiate to handle the plugin.
+ * This must be overriden by each subclass to provide a unique name.
+ * @construct: The construct virtual method scans the XML tree to
+ * initialise itself.
+ * @invoke: The invoke virtual method loads the plugin code, resolves
+ * the function name, and marshals a simple pointer to execute the
+ * plugin.
+ *
+ * The EPluginClass represents each plugin type. The type of each class is
+ * registered in a global table and is used to instantiate a
+ * container for each plugin.
+ *
+ * It provides two main functions, to load the plugin definition, and
+ * to invoke a function. Each plugin class is used to handle mappings
+ * to different languages.
+ **/
+struct _EPluginClass {
+ GObjectClass class;
+
+ const char *type;
+
+ int (*construct)(EPlugin *, xmlNodePtr root);
+ void *(*invoke)(EPlugin *, const char *name, void *data);
+};
+
+GType e_plugin_get_type(void);
+
+int e_plugin_construct(EPlugin *ep, xmlNodePtr root);
+void e_plugin_add_load_path(const char *);
+int e_plugin_load_plugins(void);
+
+void e_plugin_register_type(GType type);
+
+void *e_plugin_invoke(EPlugin *ep, const char *name, void *data);
+void e_plugin_enable(EPlugin *eph, int state);
+
+/* static helpers */
+/* maps prop or content to 'g memory' */
+char *e_plugin_xml_prop(xmlNodePtr node, const char *id);
+char *e_plugin_xml_prop_domain(xmlNodePtr node, const char *id, const char *domain);
+int e_plugin_xml_int(xmlNodePtr node, const char *id, int def);
+char *e_plugin_xml_content(xmlNodePtr node);
+char *e_plugin_xml_content_domain(xmlNodePtr node, const char *domain);
+
+/* ********************************************************************** */
+#include <gmodule.h>
+
+typedef struct _EPluginLib EPluginLib;
+typedef struct _EPluginLibClass EPluginLibClass;
+
+/**
+ * struct _EPluginLib -
+ *
+ * @plugin: Superclass.
+ * @location: The filename of the shared object.
+ * @module: The GModule once it is loaded.
+ *
+ * This is a concrete EPlugin class. It loads and invokes dynamically
+ * loaded libraries using GModule. The shared object isn't loaded
+ * until the first callback is invoked.
+ **/
+struct _EPluginLib {
+ EPlugin plugin;
+
+ char *location;
+ GModule *module;
+};
+
+/**
+ * struct _EPluginLibClass -
+ *
+ * @plugin_class: Superclass.
+ *
+ * The plugin library needs no additional class data.
+ **/
+struct _EPluginLibClass {
+ EPluginClass plugin_class;
+};
+
+GType e_plugin_lib_get_type(void);
+
+/* ********************************************************************** */
+
+typedef struct _EPluginHook EPluginHook;
+typedef struct _EPluginHookClass EPluginHookClass;
+
+/* utilities for subclasses to use */
+typedef struct _EPluginHookTargetMap EPluginHookTargetMap;
+typedef struct _EPluginHookTargetKey EPluginHookTargetKey;
+
+/**
+ * struct _EPluginHookTargetKey -
+ *
+ * @key: Enumeration value as a string.
+ * @value: Enumeration value as an integer.
+ *
+ * A multi-purpose string to id mapping structure used with various
+ * helper functions to simplify plugin hook subclassing.
+ **/
+struct _EPluginHookTargetKey {
+ const char *key;
+ guint32 value;
+};
+
+/**
+ * struct _EPluginHookTargetMap -
+ *
+ * @type: The string id of the target.
+ * @id: The integer id of the target. Maps directly to the type field
+ * of the various plugin type target id's.
+ * @mask_bits: A zero-fill terminated array of EPluginHookTargetKeys.
+ *
+ * Used by EPluginHook to define mappings of target type enumerations
+ * to and from strings. Also used to define the mask option names
+ * when reading the XML plugin hook definitions.
+ **/
+struct _EPluginHookTargetMap {
+ const char *type;
+ int id;
+ const struct _EPluginHookTargetKey *mask_bits; /* null terminated array */
+};
+
+/**
+ * struct _EPluginHook - A plugin hook.
+ *
+ * @object: Superclass.
+ * @plugin: The parent object.
+ *
+ * An EPluginHook is used as a container for each hook a given plugin
+ * is listening to.
+ **/
+struct _EPluginHook {
+ GObject object;
+
+ struct _EPlugin *plugin;
+};
+
+/**
+ * struct _EPluginHookClass -
+ *
+ * @class: Superclass.
+ * @id: The plugin hook type. This must be overriden by each subclass
+ * and is used as a key when loading hook definitions. This string
+ * should contain a globally unique name followed by a : and a version
+ * specification. This is to ensure plugins only hook into hooks with
+ * the right API.
+ * @construct: Virtual method used to initialise the object when
+ * loaded.
+ * @enable: Virtual method used to enable or disable the hook.
+ *
+ * The EPluginHookClass represents each hook type. The type of the
+ * class is registered in a global table and is used to instantiate a
+ * container for each hook.
+ **/
+struct _EPluginHookClass {
+ GObjectClass class;
+
+ const char *id;
+
+ int (*construct)(EPluginHook *eph, EPlugin *ep, xmlNodePtr root);
+ void (*enable)(EPluginHook *eph, int state);
+};
+
+GType e_plugin_hook_get_type(void);
+
+void e_plugin_hook_register_type(GType type);
+
+EPluginHook * e_plugin_hook_new(EPlugin *ep, xmlNodePtr root);
+void e_plugin_hook_enable(EPluginHook *eph, int state);
+
+/* static methods */
+guint32 e_plugin_hook_mask(xmlNodePtr root, const struct _EPluginHookTargetKey *map, const char *prop);
+guint32 e_plugin_hook_id(xmlNodePtr root, const struct _EPluginHookTargetKey *map, const char *prop);
+
+#endif /* ! _E_PLUGIN_H */
diff --git a/e-util/e-popup.c b/e-util/e-popup.c
new file mode 100644
index 0000000000..c8d7f79ce0
--- /dev/null
+++ b/e-util/e-popup.c
@@ -0,0 +1,799 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2004 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <glib.h>
+
+#include <gtk/gtkmenu.h>
+#include <gtk/gtkmenuitem.h>
+#include <gtk/gtkimagemenuitem.h>
+#include <gtk/gtkcheckmenuitem.h>
+#include <gtk/gtkradiomenuitem.h>
+#include <gtk/gtkseparatormenuitem.h>
+#include <gtk/gtklabel.h>
+#include <gtk/gtkimage.h>
+
+#include "e-popup.h"
+
+#include <e-util/e-icon-factory.h>
+
+#include <libgnome/gnome-i18n.h>
+
+struct _EPopupFactory {
+ struct _EPopupFactory *next, *prev;
+
+ char *menuid;
+ EPopupFactoryFunc factory;
+ void *factory_data;
+};
+
+/* Used for the "activate" signal callback data to re-map to the api */
+struct _item_node {
+ struct _item_node *next;
+
+ EPopupItem *item;
+ struct _menu_node *menu;
+};
+
+/* Stores all the items added */
+struct _menu_node {
+ struct _menu_node *next, *prev;
+
+ EPopup *popup;
+
+ GSList *menu;
+ EPopupItemsFunc freefunc;
+ void *data;
+
+ struct _item_node *items;
+};
+
+struct _EPopupPrivate {
+ EDList menus;
+};
+
+static GObjectClass *ep_parent;
+
+static void
+ep_init(GObject *o)
+{
+ EPopup *emp = (EPopup *)o;
+ struct _EPopupPrivate *p;
+
+ p = emp->priv = g_malloc0(sizeof(struct _EPopupPrivate));
+
+ e_dlist_init(&p->menus);
+}
+
+static void
+ep_finalise(GObject *o)
+{
+ EPopup *emp = (EPopup *)o;
+ struct _EPopupPrivate *p = emp->priv;
+ struct _menu_node *mnode, *nnode;
+
+ mnode = (struct _menu_node *)p->menus.head;
+ nnode = mnode->next;
+ while (nnode) {
+ struct _item_node *inode;
+
+ if (mnode->freefunc)
+ mnode->freefunc(emp, mnode->menu, mnode->data);
+
+ /* free item activate callback data */
+ inode = mnode->items;
+ while (inode) {
+ struct _item_node *nnode = inode->next;
+
+ g_free(inode);
+ inode = nnode;
+ }
+
+ g_free(mnode);
+ mnode = nnode;
+ nnode = nnode->next;
+ }
+
+ if (emp->target)
+ e_popup_target_free(emp, emp->target);
+
+ g_free(emp->menuid);
+
+ g_free(p);
+
+ ((GObjectClass *)ep_parent)->finalize(o);
+}
+
+static void
+ep_target_free(EPopup *ep, EPopupTarget *t)
+{
+ g_free(t);
+ g_object_unref(ep);
+}
+
+static void
+ep_class_init(GObjectClass *klass)
+{
+ printf("EPopup class init %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ klass->finalize = ep_finalise;
+ ((EPopupClass *)klass)->target_free = ep_target_free;
+}
+
+static void
+ep_base_init(GObjectClass *klass)
+{
+ e_dlist_init(&((EPopupClass *)klass)->factories);
+}
+
+/**
+ * e_popup_get_type:
+ *
+ * Standard GObject type function.
+ *
+ * Return value: The EPopup object type.
+ **/
+GType
+e_popup_get_type(void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof(EPopupClass),
+ (GBaseInitFunc)ep_base_init, NULL,
+ (GClassInitFunc)ep_class_init, NULL, NULL,
+ sizeof(EPopup), 0,
+ (GInstanceInitFunc)ep_init
+ };
+ ep_parent = g_type_class_ref(G_TYPE_OBJECT);
+ type = g_type_register_static(G_TYPE_OBJECT, "EPopup", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_popup_construct:
+ * @ep: An instantiated but uninitialised EPopup.
+ * @menuid: The menu identifier.
+ *
+ * Construct the base popup instance with standard parameters.
+ *
+ * Return value: Returns @ep.
+ **/
+EPopup *e_popup_construct(EPopup *ep, const char *menuid)
+{
+ ep->menuid = g_strdup(menuid);
+
+ return ep;
+}
+
+/**
+ * e_popup_add_items:
+ * @emp: An EPopup derived object.
+ * @items: A list of EPopupItem's to add to the current popup menu.
+ * @freefunc: A function which will be called when the items are no
+ * longer needed.
+ * @data: user-data passed to @freefunc, and passed to all activate
+ * methods.
+ *
+ * Add new EPopupItems to the menus. Any with the same path
+ * will override previously defined menu items, at menu building
+ * time. This may be called any number of times before the menu is
+ * built to create a complex heirarchy of menus.
+ **/
+void
+e_popup_add_items(EPopup *emp, GSList *items, EPopupItemsFunc freefunc, void *data)
+{
+ struct _menu_node *node;
+
+ node = g_malloc0(sizeof(*node));
+ node->menu = items;
+ node->freefunc = freefunc;
+ node->data = data;
+ node->popup = emp;
+
+ e_dlist_addtail(&emp->priv->menus, (EDListNode *)node);
+}
+
+static void
+ep_add_static_items(EPopup *emp)
+{
+ struct _EPopupFactory *f;
+ EPopupClass *klass = (EPopupClass *)G_OBJECT_GET_CLASS(emp);
+
+ if (emp->menuid == NULL || emp->target == NULL)
+ return;
+
+ /* setup the menu itself */
+ f = (struct _EPopupFactory *)klass->factories.head;
+ while (f->next) {
+ if (f->menuid == NULL
+ || !strcmp(f->menuid, emp->menuid)) {
+ f->factory(emp, f->factory_data);
+ }
+ f = f->next;
+ }
+}
+
+static int
+ep_cmp(const void *ap, const void *bp)
+{
+ struct _item_node *a = *((void **)ap);
+ struct _item_node *b = *((void **)bp);
+
+ return strcmp(a->item->path, b->item->path);
+}
+
+static void
+ep_activate(GtkWidget *w, struct _item_node *inode)
+{
+ inode->item->activate(inode->menu->popup, inode->item, inode->menu->data);
+}
+
+/**
+ * e_popup_create:
+ * @emp: An EPopup derived object.
+ * @target: popup target, if set, then factories will be invoked.
+ * This is then owned by the menu.
+ * @hide_mask: used to hide menu items, not sure of it's utility,
+ * since you could just 'not add them' in the first place. Saves
+ * copying logic anyway.
+ * @disable_mask: used to disable menu items.
+ *
+ * All of the menu items registered on @emp are sorted by path, and
+ * then converted into a menu heirarchy.
+ *
+ * Return value: A GtkMenu which can be popped up when ready.
+ **/
+GtkMenu *
+e_popup_create_menu(EPopup *emp, EPopupTarget *target, guint32 hide_mask, guint32 disable_mask)
+{
+ struct _EPopupPrivate *p = emp->priv;
+ struct _menu_node *mnode, *nnode;
+ GPtrArray *items = g_ptr_array_new();
+ GSList *l;
+ GString *ppath = g_string_new("");
+ GtkMenu *topmenu;
+ GHashTable *menu_hash = g_hash_table_new(g_str_hash, g_str_equal),
+ *group_hash = g_hash_table_new(g_str_hash, g_str_equal);
+ int i;
+
+ emp->target = target;
+ ep_add_static_items(emp);
+
+ /* FIXME: need to override old ones with new names */
+ mnode = (struct _menu_node *)p->menus.head;
+ nnode = mnode->next;
+ while (nnode) {
+ for (l=mnode->menu; l; l = l->next) {
+ struct _item_node *inode = g_malloc0(sizeof(*inode));
+
+ inode->item = l->data;
+ inode->menu = mnode;
+ inode->next = mnode->items;
+ mnode->items = inode;
+
+ g_ptr_array_add(items, inode);
+ }
+ mnode = nnode;
+ nnode = nnode->next;
+ }
+
+ qsort(items->pdata, items->len, sizeof(items->pdata[0]), ep_cmp);
+
+ topmenu = (GtkMenu *)gtk_menu_new();
+ for (i=0;i<items->len;i++) {
+ GtkWidget *label;
+ struct _item_node *inode = items->pdata[i];
+ struct _EPopupItem *item = inode->item;
+ GtkMenu *thismenu;
+ GtkMenuItem *menuitem;
+ char *tmp;
+
+ /* for bar's, the mask is exclusive or */
+ if (item->visible) {
+ if ((item->type & E_POPUP_TYPE_MASK) == E_POPUP_BAR) {
+ if ((item->visible & hide_mask) == item->visible)
+ continue;
+ } else if (item->visible & hide_mask)
+ continue;
+ }
+
+ g_string_truncate(ppath, 0);
+ tmp = strrchr(item->path, '/');
+ if (tmp) {
+ g_string_append_len(ppath, item->path, tmp-item->path);
+ thismenu = g_hash_table_lookup(menu_hash, ppath->str);
+ g_assert(thismenu != NULL);
+ } else {
+ thismenu = topmenu;
+ }
+
+ switch (item->type & E_POPUP_TYPE_MASK) {
+ case E_POPUP_ITEM:
+ if (item->image) {
+ GdkPixbuf *pixbuf;
+ GtkWidget *image;
+
+ pixbuf = e_icon_factory_get_icon ((char *)item->image, E_ICON_SIZE_MENU);
+ image = gtk_image_new_from_pixbuf (pixbuf);
+ g_object_unref (pixbuf);
+
+ gtk_widget_show(image);
+ menuitem = (GtkMenuItem *)gtk_image_menu_item_new();
+ gtk_image_menu_item_set_image((GtkImageMenuItem *)menuitem, image);
+ } else {
+ menuitem = (GtkMenuItem *)gtk_menu_item_new();
+ }
+ break;
+ case E_POPUP_TOGGLE:
+ menuitem = (GtkMenuItem *)gtk_check_menu_item_new();
+ gtk_check_menu_item_set_active((GtkCheckMenuItem *)menuitem, item->type & E_POPUP_ACTIVE);
+ break;
+ case E_POPUP_RADIO:
+ menuitem = (GtkMenuItem *)gtk_radio_menu_item_new(g_hash_table_lookup(group_hash, ppath->str));
+ /* FIXME: need to strdup the string */
+ g_hash_table_insert(group_hash, ppath->str, gtk_radio_menu_item_get_group((GtkRadioMenuItem *)menuitem));
+ gtk_check_menu_item_set_active((GtkCheckMenuItem *)menuitem, item->type & E_POPUP_ACTIVE);
+ break;
+ case E_POPUP_IMAGE:
+ menuitem = (GtkMenuItem *)gtk_image_menu_item_new();
+ gtk_image_menu_item_set_image((GtkImageMenuItem *)menuitem, item->image);
+ break;
+ case E_POPUP_SUBMENU: {
+ GtkMenu *submenu = (GtkMenu *)gtk_menu_new();
+
+ g_hash_table_insert(menu_hash, item->path, submenu);
+ menuitem = (GtkMenuItem *)gtk_menu_item_new();
+ gtk_menu_item_set_submenu(menuitem, (GtkWidget *)submenu);
+ break; }
+ case E_POPUP_BAR:
+ /* TODO: double-bar, end-bar stuff? */
+ menuitem = (GtkMenuItem *)gtk_separator_menu_item_new();
+ break;
+ default:
+ continue;
+ }
+
+ if (item->label) {
+ label = gtk_label_new_with_mnemonic(_(item->label));
+ gtk_misc_set_alignment((GtkMisc *)label, 0.0, 0.5);
+ gtk_widget_show(label);
+ gtk_container_add((GtkContainer *)menuitem, label);
+ }
+
+ if (item->activate)
+ g_signal_connect(menuitem, "activate", G_CALLBACK(ep_activate), inode);
+
+ gtk_menu_shell_append((GtkMenuShell *)thismenu, (GtkWidget *)menuitem);
+
+ if (item->visible & disable_mask)
+ gtk_widget_set_sensitive((GtkWidget *)menuitem, FALSE);
+
+ gtk_widget_show((GtkWidget *)menuitem);
+ }
+
+ g_string_free(ppath, TRUE);
+ g_ptr_array_free(items, TRUE);
+ g_hash_table_destroy(menu_hash);
+ g_hash_table_destroy(group_hash);
+
+ return topmenu;
+}
+
+static void
+ep_popup_done(GtkWidget *w, EPopup *emp)
+{
+ gtk_widget_destroy(w);
+ if (emp->target) {
+ e_popup_target_free(emp, emp->target);
+ emp->target = NULL;
+ }
+ g_object_unref(emp);
+}
+
+/**
+ * e_popup_create_menu_once:
+ * @emp: EPopup, once the menu is shown, this cannot be
+ * considered a valid pointer.
+ * @target: If set, the target of the selection. Static menu
+ * items will be added. The target will be freed once complete.
+ * @hide_mask:
+ * @disable_mask:
+ *
+ * Like popup_create_menu, but automatically sets up the menu
+ * so that it is destroyed once a selection takes place, and
+ * the EPopup is unreffed. This is the normal entry point as it
+ * automates most memory management for popup menus.
+ *
+ * Return value: A menu, to popup.
+ **/
+GtkMenu *
+e_popup_create_menu_once(EPopup *emp, EPopupTarget *target, guint32 hide_mask, guint32 disable_mask)
+{
+ GtkMenu *menu;
+
+ menu = e_popup_create_menu(emp, target, hide_mask, disable_mask);
+
+ g_signal_connect(menu, "selection_done", G_CALLBACK(ep_popup_done), emp);
+
+ return menu;
+}
+
+/* ********************************************************************** */
+
+/**
+ * e_popup_class_add_factory:
+ * @klass: The EPopup derived class which you're interested in.
+ * @menuid: The identifier of the menu you're interested in, or NULL
+ * to be called for all menus on this class.
+ * @func: The factory called when the menu @menuid is being created.
+ * @data: User-data for the factory callback.
+ *
+ * This is a class-static method used to register factory callbacks
+ * against specific menu's.
+ *
+ * The factory method will be invoked before the menu is created.
+ * This way, the factory may add any additional menu items it wishes
+ * based on the context supplied in the @target.
+ *
+ * Return value: A handle to the factory which can be used to remove
+ * it later.
+ **/
+EPopupFactory *
+e_popup_class_add_factory(EPopupClass *klass, const char *menuid, EPopupFactoryFunc func, void *data)
+{
+ struct _EPopupFactory *f = g_malloc0(sizeof(*f));
+
+ f->menuid = g_strdup(menuid);
+ f->factory = func;
+ f->factory_data = data;
+ e_dlist_addtail(&klass->factories, (EDListNode *)f);
+
+ return f;
+}
+
+/**
+ * e_popup_class_remove_factory:
+ * @klass: The EPopup derived class.
+ * @f: The factory handle returned by e_popup_class_add_factory().
+ *
+ * Remove a popup menu factory. If it has not been added, or it has
+ * already been removed, then the result is undefined (i.e. it will
+ * crash).
+ *
+ * Generally factories are static for the life of the application, and
+ * so do not need to be removed.
+ **/
+void
+e_popup_class_remove_factory(EPopupClass *klass, EPopupFactory *f)
+{
+ e_dlist_remove((EDListNode *)f);
+ g_free(f->menuid);
+ g_free(f);
+}
+
+/**
+ * e_popup_target_new:
+ * @ep: An EPopup derived object.
+ * @type: type, defined by the implementing class.
+ * @size: The size of memory to allocate for the target. It must be
+ * equal or greater than the size of EPopupTarget.
+ *
+ * Allocate a new popup target suitable for this popup type.
+ **/
+void *e_popup_target_new(EPopup *ep, int type, size_t size)
+{
+ EPopupTarget *t;
+
+ g_assert(size >= sizeof(EPopupTarget));
+
+ t = g_malloc0(size);
+ t->popup = ep;
+ g_object_ref(ep);
+ t->type = type;
+
+ return t;
+}
+
+/**
+ * e_popup_target_free:
+ * @ep: An EPopup derived object.
+ * @o: The target, previously allocated by e_popup_target_new().
+ *
+ * Free the target against @ep. Note that targets are automatically
+ * freed if they are passed to the menu creation functions, so this is
+ * only required if you are using the target for other purposes.
+ **/
+void
+e_popup_target_free(EPopup *ep, void *o)
+{
+ EPopupTarget *t = o;
+
+ ((EPopupClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
+}
+
+/* ********************************************************************** */
+
+/* Popup menu plugin handler */
+
+/*
+<e-plugin
+ class="com.ximian.mail.plugin.popup:1.0"
+ id="com.ximian.mail.plugin.popup.item:1.0"
+ type="shlib"
+ location="/opt/gnome2/lib/camel/1.0/libcamelimap.so"
+ name="imap"
+ description="IMAP4 and IMAP4v1 mail store">
+ <hook class="com.ximian.mail.popupMenu:1.0"
+ handler="HandlePopup">
+ <menu id="any" target="select">
+ <item
+ type="item|toggle|radio|image|submenu|bar"
+ active
+ path="foo/bar"
+ label="label"
+ icon="foo"
+ visible="select_one"
+ activate="ep_view_emacs"/>
+ </menu>
+ </extension>
+
+*/
+
+static void *emph_parent_class;
+#define emph ((EPopupHook *)eph)
+
+/* must have 1:1 correspondence with e-popup types in order */
+static const EPluginHookTargetKey emph_item_types[] = {
+ { "item", E_POPUP_ITEM },
+ { "toggle", E_POPUP_TOGGLE },
+ { "radio", E_POPUP_RADIO },
+ { "image", E_POPUP_IMAGE },
+ { "submenu", E_POPUP_SUBMENU },
+ { "bar", E_POPUP_BAR },
+ { 0 }
+};
+
+static void
+emph_popup_activate(EPopup *ep, EPopupItem *item, void *data)
+{
+ EPopupHook *hook = data;
+
+ e_plugin_invoke(hook->hook.plugin, (char *)item->user_data, ep->target);
+}
+
+static void
+emph_popup_factory(EPopup *emp, void *data)
+{
+ struct _EPopupHookMenu *menu = data;
+
+ printf("popup factory called %s mask %08x\n", menu->id?menu->id:"all menus", emp->target->mask);
+
+ /* If we're disabled, then don't add the menu's. */
+ if (emp->target->type != menu->target_type
+ || !menu->hook->hook.plugin->enabled)
+ return;
+
+ if (menu->items)
+ e_popup_add_items(emp, menu->items, NULL, menu->hook);
+}
+
+static void
+emph_free_item(struct _EPopupItem *item)
+{
+ g_free(item->path);
+ g_free(item->label);
+ g_free(item->image);
+ g_free(item->user_data);
+ g_free(item);
+}
+
+static void
+emph_free_menu(struct _EPopupHookMenu *menu)
+{
+ g_slist_foreach(menu->items, (GFunc)emph_free_item, NULL);
+ g_slist_free(menu->items);
+
+ g_free(menu->id);
+ g_free(menu);
+}
+
+static struct _EPopupItem *
+emph_construct_item(EPluginHook *eph, EPopupHookMenu *menu, xmlNodePtr root, EPopupHookTargetMap *map)
+{
+ struct _EPopupItem *item;
+
+ printf(" loading menu item\n");
+ item = g_malloc0(sizeof(*item));
+ if ((item->type = e_plugin_hook_id(root, emph_item_types, "type")) == -1
+ || item->type == E_POPUP_IMAGE)
+ goto error;
+ item->path = e_plugin_xml_prop(root, "path");
+ item->label = e_plugin_xml_prop_domain(root, "label", eph->plugin->domain);
+ item->image = e_plugin_xml_prop(root, "icon");
+ item->visible = e_plugin_hook_mask(root, map->mask_bits, "visible");
+ item->enable = e_plugin_hook_mask(root, map->mask_bits, "enable");
+ item->user_data = e_plugin_xml_prop(root, "activate");
+
+ item->activate = emph_popup_activate;
+
+ if (item->user_data == NULL)
+ goto error;
+
+ printf(" path=%s\n", item->path);
+ printf(" label=%s\n", item->label);
+
+ return item;
+error:
+ printf("error!\n");
+ emph_free_item(item);
+ return NULL;
+}
+
+static struct _EPopupHookMenu *
+emph_construct_menu(EPluginHook *eph, xmlNodePtr root)
+{
+ struct _EPopupHookMenu *menu;
+ xmlNodePtr node;
+ EPopupHookTargetMap *map;
+ EPopupHookClass *klass = (EPopupHookClass *)G_OBJECT_GET_CLASS(eph);
+ char *tmp;
+
+ printf(" loading menu\n");
+ menu = g_malloc0(sizeof(*menu));
+ menu->hook = (EPopupHook *)eph;
+
+ tmp = xmlGetProp(root, "target");
+ if (tmp == NULL)
+ goto error;
+ map = g_hash_table_lookup(klass->target_map, tmp);
+ xmlFree(tmp);
+ if (map == NULL)
+ goto error;
+
+ menu->target_type = map->id;
+ menu->id = e_plugin_xml_prop(root, "id");
+ node = root->children;
+ while (node) {
+ if (0 == strcmp(node->name, "item")) {
+ struct _EPopupItem *item;
+
+ item = emph_construct_item(eph, menu, node, map);
+ if (item)
+ menu->items = g_slist_append(menu->items, item);
+ }
+ node = node->next;
+ }
+
+ return menu;
+error:
+ emph_free_menu(menu);
+ return NULL;
+}
+
+static int
+emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
+{
+ xmlNodePtr node;
+ EPopupClass *klass;
+
+ printf("loading popup hook\n");
+
+ if (((EPluginHookClass *)emph_parent_class)->construct(eph, ep, root) == -1)
+ return -1;
+
+ klass = ((EPopupHookClass *)G_OBJECT_GET_CLASS(eph))->popup_class;
+
+ node = root->children;
+ while (node) {
+ if (strcmp(node->name, "menu") == 0) {
+ struct _EPopupHookMenu *menu;
+
+ menu = emph_construct_menu(eph, node);
+ if (menu) {
+ e_popup_class_add_factory(klass, menu->id, emph_popup_factory, menu);
+ emph->menus = g_slist_append(emph->menus, menu);
+ }
+ }
+ node = node->next;
+ }
+
+ eph->plugin = ep;
+
+ return 0;
+}
+
+static void
+emph_finalise(GObject *o)
+{
+ EPluginHook *eph = (EPluginHook *)o;
+
+ g_slist_foreach(emph->menus, (GFunc)emph_free_menu, NULL);
+ g_slist_free(emph->menus);
+
+ ((GObjectClass *)emph_parent_class)->finalize(o);
+}
+
+static void
+emph_class_init(EPluginHookClass *klass)
+{
+ ((GObjectClass *)klass)->finalize = emph_finalise;
+ klass->construct = emph_construct;
+
+ /* this is actually an abstract implementation but list it anyway */
+ klass->id = "com.ximian.evolution.popup:1.0";
+
+ printf("EPopupHook: init class %p '%s'\n", klass, g_type_name(((GObjectClass *)klass)->g_type_class.g_type));
+
+ ((EPopupHookClass *)klass)->target_map = g_hash_table_new(g_str_hash, g_str_equal);
+ ((EPopupHookClass *)klass)->popup_class = g_type_class_ref(e_popup_get_type());
+}
+
+/**
+ * e_popup_hook_get_type:
+ *
+ * Standard GObject function to get the object type. Used to subclass
+ * EPopupHook.
+ *
+ * Return value: The type of the popup hook class.
+ **/
+GType
+e_popup_hook_get_type(void)
+{
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo info = {
+ sizeof(EPopupHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL,
+ sizeof(EPopupHook), 0, (GInstanceInitFunc) NULL,
+ };
+
+ emph_parent_class = g_type_class_ref(e_plugin_hook_get_type());
+ type = g_type_register_static(e_plugin_hook_get_type(), "EPopupHook", &info, 0);
+ }
+
+ return type;
+}
+
+/**
+ * e_popup_hook_class_add_target_map:
+ * @klass: The derived EPopupHook class.
+ * @map: A map used to describe a single EPopupTarget type for this
+ * class.
+ *
+ * Add a target map to a concrete derived class of EPopup. The target
+ * map enumerates a single target type and the enable mask bit names,
+ * so that the type can be loaded automatically by the EPopup class.
+ **/
+void e_popup_hook_class_add_target_map(EPopupHookClass *klass, const EPopupHookTargetMap *map)
+{
+ g_hash_table_insert(klass->target_map, (void *)map->type, (void *)map);
+}
diff --git a/e-util/e-popup.h b/e-util/e-popup.h
new file mode 100644
index 0000000000..70729f211e
--- /dev/null
+++ b/e-util/e-popup.h
@@ -0,0 +1,300 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Authors: Michel Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2003 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __E_POPUP_H__
+#define __E_POPUP_H__
+
+#include <glib-object.h>
+#include "e-util/e-msgport.h"
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+/* This is an abstract popup menu management/merging class.
+
+ To implement your own popup menu system, just create your own
+ target types and implement the target free method. */
+
+typedef struct _EPopup EPopup;
+typedef struct _EPopupClass EPopupClass;
+
+typedef struct _EPopupItem EPopupItem;
+typedef struct _EPopupFactory EPopupFactory; /* anonymous type */
+typedef struct _EPopupTarget EPopupTarget;
+
+typedef void (*EPopupActivateFunc)(EPopup *ep, EPopupItem *item, void *data);
+typedef void (*EPopupFactoryFunc)(EPopup *emp, void *data);
+typedef void (*EPopupItemsFunc)(EPopup *ep, GSList *items, void *data);
+
+/**
+ * enum _e_popup_t - Popup item type enumeration.
+ * @E_POPUP_ITEM: A simple menu item.
+ * @E_POPUP_TOGGLE: A toggle menu item.
+ * @E_POPUP_RADIO: A radio menu item. Note that the radio group is
+ * global for the entire (sub) menu. i.e. submenu's must be used to
+ * separate radio button menu items.
+ * @E_POPUP_IMAGE: A &GtkImage menu item. In this case the @image
+ * field of &struct _EPopupItem points to the &GtkImage directly.
+ * @E_POPUP_SUBMENU: A sub-menu header. It is up to the application
+ * to define the @path properly so that the submenu comes before the
+ * submenu items.
+ * @E_POPUP_BAR: A menu separator bar.
+ * @E_POPUP_TYPE_MASK: Mask used to separate item type from option bits.
+ * @E_POPUP_ACTIVE: An option bit to signify that the radio button or
+ * toggle button is active.
+ */
+enum _e_popup_t {
+ E_POPUP_ITEM = 0,
+ E_POPUP_TOGGLE,
+ E_POPUP_RADIO,
+ E_POPUP_IMAGE,
+ E_POPUP_SUBMENU,
+ E_POPUP_BAR,
+ E_POPUP_TYPE_MASK = 0xffff,
+ E_POPUP_ACTIVE = 0x10000,
+};
+
+/* FIXME: activate passes back no context data apart from that provided.
+ FIXME: It should pass the target at the least. The menu widget is
+ useless */
+
+/**
+ * struct _EPopupItem - A popup menu item definition.
+ * @type: The type of the popup. See the &enum _epopup_t definition
+ * for possible values.
+ * @path: An absolute path, which when sorted using a simple ASCII
+ * sort, will put the menu item in the right place in the menu
+ * heirarchy. '/' is used to separate menus from submenu items.
+ * @label: The text of the menyu item.
+ * @activate: A function conforming to &EPopupActivateFunc which will
+ * be called when the menu item is activated.
+ * @user_data: Extra per-item user-data available to the
+ * application. This is not passed to the @data field of @activate.
+ * @image: For most types, the name of the icon in the icon theme to
+ * display next to the menu item, if required. For the %E_POPUP_IMAGE
+ * type, it is a pointer to the &GtkWidget instead.
+ * @visible: Visibility mask. Used together with the &EPopupTarget mask
+ * to determine if the item should be part of the menu or not.
+ * @enable: Sensitivity mask. Similar to the visibility mask, but
+ * currently unimplemented.
+ * @popup: Used by e-popup to reference the parent object from
+ * callbacks.
+ *
+ * The EPopupItem defines a single popup menu item, or submenu item,
+ * or menu separator based on the @type. Any number of these are
+ * merged at popup display type to form the popup menu.
+ *
+ * The application may extend this structure using simple C structure
+ * containers to add any additional fields it may require.
+ */
+struct _EPopupItem {
+ enum _e_popup_t type;
+ char *path; /* absolute path! must sort ascii-lexographically into the right spot */
+ char *label;
+ EPopupActivateFunc activate;
+ void *user_data; /* user data, not passed directly to @activate */
+ void *image; /* char* for item type, GtkWidget * for image type */
+ guint32 visible; /* visibility mask */
+ guint32 enable; /* sensitivity mask, unimplemented */
+};
+
+/**
+ * struct EPopupTarget - A popup menu target definition.
+ *
+ * @popup: The parent popup object, used for virtual methods on the target.
+ * @widget: The parent widget, where available. In some cases the
+ * type of this object is part of the published api for the target.
+ * @type: The target type. This will be defined by the
+ * implementation.
+ * @mask: Target mask. This is used to sensitise and show items
+ * based on their definition in EPopupItem.
+ *
+ * An EPopupTarget defines the context for a specific popup menu
+ * instance. The root target object is abstract, and it is up to
+ * sub-classes of &EPopup to define the additional fields required to
+ * make it usable.
+ */
+struct _EPopupTarget {
+ struct _EPopup *popup; /* used for virtual methods */
+
+ struct _GtkWidget *widget; /* used if you need a parent toplevel, if available */
+ guint32 type; /* targe type, for implementors */
+
+ guint32 mask; /* depends on type, visibility mask */
+
+ /* implementation fields follow */
+};
+
+/**
+ * struct _EPopup - A Popup menu manager.
+ *
+ * @object: Superclass, GObject.
+ * @priv: Private data.
+ * @menuid: The id of this menu instance.
+ * @target: The current target during the display of the popup menu.
+ *
+ * The EPopup manager object. Each popup menu is built using this
+ * one-off object which is created each time the popup is invoked.
+ */
+struct _EPopup {
+ GObject object;
+
+ struct _EPopupPrivate *priv;
+
+ char *menuid;
+
+ EPopupTarget *target;
+};
+
+/**
+ * struct _EPopupClass -
+ *
+ * @object_class: Superclass type.
+ * @factories: A list of factories for this particular class of popup
+ * menu.
+ * @target_free: Virtual method to free the popup target. The base
+ * class frees the allocation and unrefs the popup pointer
+ * structure.
+ *
+ * The EPopup class definition. This should be sub-classed for each
+ * component that wants to provide hookable popup menus. The
+ * sub-class only needs to know how to allocate and free the various target
+ * types it supports.
+ */
+struct _EPopupClass {
+ GObjectClass object_class;
+
+ EDList factories;
+
+ void (*target_free)(EPopup *ep, EPopupTarget *t);
+};
+
+GType e_popup_get_type(void);
+
+/* Static class methods */
+EPopupFactory *e_popup_class_add_factory(EPopupClass *klass, const char *menuid, EPopupFactoryFunc func, void *data);
+void e_popup_class_remove_factory(EPopupClass *klass, EPopupFactory *f);
+
+EPopup *e_popup_construct(EPopup *, const char *menuid);
+
+void e_popup_add_items(EPopup *, GSList *items, EPopupItemsFunc freefunc, void *data);
+
+void e_popup_add_static_items(EPopup *emp, EPopupTarget *target);
+/* do not call e_popup_create_menu, it can leak structures if not used right */
+struct _GtkMenu *e_popup_create_menu(EPopup *, EPopupTarget *, guint32 hide_mask, guint32 disable_mask);
+struct _GtkMenu *e_popup_create_menu_once(EPopup *emp, EPopupTarget *, guint32 hide_mask, guint32 disable_mask);
+
+void *e_popup_target_new(EPopup *, int type, size_t size);
+void e_popup_target_free(EPopup *, void *);
+
+/* ********************************************************************** */
+
+/* popup plugin target, they are closely integrated */
+
+/* To implement a basic popup menu plugin, you just need to subclass
+ this and initialise the class target type tables */
+
+#include "e-util/e-plugin.h"
+
+typedef struct _EPopupHookMenu EPopupHookMenu;
+typedef struct _EPopupHook EPopupHook;
+typedef struct _EPopupHookClass EPopupHookClass;
+
+typedef struct _EPluginHookTargetMap EPopupHookTargetMap;
+typedef struct _EPluginHookTargetKey EPopupHookTargetMask;
+
+typedef void (*EPopupHookFunc)(struct _EPlugin *plugin, EPopupTarget *target);
+
+/**
+ * struct _EPopupHookMenu -
+ *
+ * @hook: Parent pointer.
+ * @id: The identifier of the menu to which these items belong.
+ * @target_type: The target number of the type of target these menu
+ * items expect. It will generally also be defined by the menu id.
+ * @items: A list of EPopupItems.
+ *
+ * The structure used to keep track of all of the items that a plugin
+ * wishes to add to a given menu. This is used internally by a factory
+ * method set on EPlugin to add the right menu items to a given menu.
+ */
+struct _EPopupHookMenu {
+ struct _EPopupHook *hook; /* parent pointer */
+ char *id; /* target menu id for these menu items */
+ int target_type; /* target type of this menu */
+ GSList *items; /* items to add to menu */
+};
+
+/**
+ * struct _EPopupHook - A popup menu hook.
+ *
+ * @hook: Superclass.
+ * @menus: A list of EPopupHookMenus, for all menus registered on
+ * this hook type.
+ *
+ * The EPopupHook class loads and manages the meta-data required to
+ * map plugin definitions to physical menus.
+ */
+struct _EPopupHook {
+ EPluginHook hook;
+
+ GSList *menus;
+};
+
+/**
+ * struct _EPopupHookClass -
+ *
+ * @hook_class: Superclass.
+ * @target_map: Table of EPluginHookTargetMaps which enumerate the
+ * target types and enable bits of the implementing class.
+ * @popup_class: The EPopupClass of the corresponding popup manager
+ * for the implementing class.
+ *
+ * The EPopupHookClass is a concrete class, however it is empty on its
+ * own. It needs to be sub-classed and initialised appropriately.
+ *
+ * The EPluginHookClass.id must be set to the name and version of the
+ * hook handler itself. The @target_map must be initialised with the
+ * data required to enumerate the target types and enable flags
+ * supported by the implementing class.
+ */
+struct _EPopupHookClass {
+ EPluginHookClass hook_class;
+
+ /* EPopupHookTargetMap by .type */
+ GHashTable *target_map;
+ /* the popup class these popups belong to */
+ EPopupClass *popup_class;
+};
+
+GType e_popup_hook_get_type(void);
+
+/* for implementors */
+void e_popup_hook_class_add_target_map(EPopupHookClass *klass, const EPopupHookTargetMap *);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __E_POPUP_H__ */