diff options
author | Michael Zucci <zucchi@src.gnome.org> | 2004-09-20 13:59:55 +0800 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2004-09-20 13:59:55 +0800 |
commit | ef6a3af717132e0750f226fa8a0ee0f3c98e19f0 (patch) | |
tree | f4ed25f7a247a8ccb855a8d940777e7a3d21a3e3 /e-util | |
parent | 96111b1f1487ca3fe454b340a73ba927cc6bfb83 (diff) | |
download | gsoc2013-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/ChangeLog | 179 | ||||
-rw-r--r-- | e-util/Makefile.am | 22 | ||||
-rw-r--r-- | e-util/e-account.c | 196 | ||||
-rw-r--r-- | e-util/e-account.h | 13 | ||||
-rw-r--r-- | e-util/e-config.c | 1415 | ||||
-rw-r--r-- | e-util/e-config.h | 374 | ||||
-rw-r--r-- | e-util/e-event.c | 557 | ||||
-rw-r--r-- | e-util/e-event.h | 238 | ||||
-rw-r--r-- | e-util/e-menu.c | 901 | ||||
-rw-r--r-- | e-util/e-menu.h | 323 | ||||
-rw-r--r-- | e-util/e-plugin-mono.c | 198 | ||||
-rw-r--r-- | e-util/e-plugin-mono.h | 27 | ||||
-rw-r--r-- | e-util/e-plugin.c | 850 | ||||
-rw-r--r-- | e-util/e-plugin.h | 221 | ||||
-rw-r--r-- | e-util/e-popup.c | 799 | ||||
-rw-r--r-- | e-util/e-popup.h | 300 |
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__ */ |