diff options
157 files changed, 16248 insertions, 1896 deletions
@@ -1,3 +1,7 @@ +2004-09-17 William Jon McCann <mccann@jhu.edu> + + * configure.in: Fix typos in gal dependency. + 2004-09-16 JP Rosevear <jpr@novell.com> * configure.in: use AC_DEFINE properly @@ -111,6 +115,19 @@ * configure.in: bump version, requirements +2004-08-24 JP Rosevear <jpr@novell.com> + + * configure.in (plugindir): set a plugin dir so we can easily + install to the same place everywhere + +2004-08-24 JP Rosevear <jpr@novell.com> + + * configure.in: Check for mono support properly + +2004-07-05 Not Zed <NotZed@Ximian.com> + + * configure.in: add some mono checks. + 2004-06-24 Pablo Saratxaga <pablo@mandrakesoft.com> * configure.in: Added Walloon (wa) to ALL_LINGUAS. diff --git a/addressbook/ChangeLog b/addressbook/ChangeLog index d39695a5fb..0839ed0d34 100644 --- a/addressbook/ChangeLog +++ b/addressbook/ChangeLog @@ -414,6 +414,11 @@ Don't set empty default value on an int, this causes gconf warning. (bug #60859) +2004-09-04 Not Zed <NotZed@Ximian.com> + + * util/e-destination.c: include gnome-i18n.h since camel-object no + longer does(!). + 2004-07-02 Hans Petter Jansson <hpj@ximian.com> * gui/contact-list-editor/e-contact-list-editor.c diff --git a/addressbook/util/e-destination.c b/addressbook/util/e-destination.c index d81cbd7fbb..3b72ee8673 100644 --- a/addressbook/util/e-destination.c +++ b/addressbook/util/e-destination.c @@ -61,6 +61,7 @@ #include <glib.h> #include <libxml/xmlmemory.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-internet-address.h> #define d(x) diff --git a/camel/ChangeLog b/camel/ChangeLog index f2e820ad58..cb8f2adc3e 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -310,6 +310,60 @@ CAMEL_GROUPWISE_CFLAGS. (libcamelgroupwise_la_LIBADD): use CAMEL_GROUPWISE_LIBS. +2004-09-07 Not Zed <NotZed@Ximian.com> + + * providers/groupwise/camel-groupwise-provider.c: put the options + in their own section. + +2004-09-06 Not Zed <NotZed@Ximian.com> + + * camel-search-private.c (utf8_get): remove this, and make the + code use camel_utf8_getc instead. Quite a bit faster esp if + optimising with inlines. + + * camel-object.c (check_magic_fail): change the check_magic code + to mostly run from a macro, its worth it. + +2004-09-04 Not Zed <NotZed@Ximian.com> + + * providers/local/camel-mbox-summary.c (message_info_new): fix + cast issue. + + * providers/pop3/camel-pop3-engine.c (get_capabilities): remove + some unused variables. + + * camel-object.h: remove camel-i18n.h, fixed all c files where + appropriate. + +2004-08-25 Not Zed <NotZed@Ximian.com> + + * camel-list-utils.[ch]: Copied e_dlist* stuff to here, part of + removing e-util dependency on camel. + +2004-08-05 Not Zed <NotZed@Ximian.com> + + * providers/local/camel-local-provider.c: same. + + * providers/imap/camel-imap-provider.c: put the extra stuff into a + 'general' section. + + * providers/imapp/camel-imapp-provider.c: same. + + * providers/pop3/camel-pop3-provider.c: make sure each section + start has a name. + +2004-08-03 Not Zed <NotZed@Ximian.com> + + * camel-url.c (camel_url_set_param): when clearing a param, use + remove_data instead of set_data_full. craptatious glib at it + again. + + * providers/nntp/camel-nntp-provider.c: turn on ssl option. + +2004-07-07 Not Zed <NotZed@Ximian.com> + + * camel-folder.c: removed filter-rule.h ugh. + 2004-07-02 Christian Neumair <chris@gnome-de.org> * camel-smime-context.c: s/Can't/Cannot/. diff --git a/camel/Makefile.am b/camel/Makefile.am index 2811d95d56..51ec97cfcb 100644 --- a/camel/Makefile.am +++ b/camel/Makefile.am @@ -76,6 +76,7 @@ libcamel_la_SOURCES = \ camel-multipart.c \ camel-multipart-encrypted.c \ camel-multipart-signed.c \ + camel-list-utils.c \ camel-object.c \ camel-operation.c \ camel-partition-table.c \ @@ -181,6 +182,7 @@ libcamelinclude_HEADERS = \ camel-multipart.h \ camel-multipart-encrypted.h \ camel-multipart-signed.h \ + camel-list-utils.h \ camel-object.h \ camel-operation.h \ camel-partition-table.h \ diff --git a/camel/camel-cipher-context.c b/camel/camel-cipher-context.c index f2c017e9c3..212120a5f5 100644 --- a/camel/camel-cipher-context.c +++ b/camel/camel-cipher-context.c @@ -29,6 +29,7 @@ #include <glib.h> +#include "camel-i18n.h" #include "camel-cipher-context.h" #include "camel-stream.h" #include "camel-operation.h" diff --git a/camel/camel-data-cache.c b/camel/camel-data-cache.c index 3a2586e3a9..c531741009 100644 --- a/camel/camel-data-cache.c +++ b/camel/camel-data-cache.c @@ -34,6 +34,7 @@ #include <alloca.h> #endif +#include "camel-i18n.h" #include "camel-data-cache.h" #include "camel-exception.h" #include "camel-stream-fs.h" diff --git a/camel/camel-disco-diary.c b/camel/camel-disco-diary.c index 5080ddea8e..b7bc3d10da 100644 --- a/camel/camel-disco-diary.c +++ b/camel/camel-disco-diary.c @@ -29,6 +29,7 @@ #include <stdio.h> #include <errno.h> +#include "camel-i18n.h" #include "camel-disco-diary.h" #include "camel-disco-folder.h" #include "camel-disco-store.h" diff --git a/camel/camel-disco-folder.c b/camel/camel-disco-folder.c index d45a4da850..7e4e653492 100644 --- a/camel/camel-disco-folder.c +++ b/camel/camel-disco-folder.c @@ -30,6 +30,7 @@ #include "camel-exception.h" #include "camel-session.h" +#include "camel-i18n.h" #define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS (o))) #define CDF_CLASS(o) (CAMEL_DISCO_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS (o))) diff --git a/camel/camel-disco-store.c b/camel/camel-disco-store.c index a4e2e4990e..c602c1cab8 100644 --- a/camel/camel-disco-store.c +++ b/camel/camel-disco-store.c @@ -32,6 +32,7 @@ #include "camel-disco-folder.h" #include "camel-exception.h" #include "camel-session.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/camel-filter-driver.c b/camel/camel-filter-driver.c index fb54e4ad6f..cf9889491e 100644 --- a/camel/camel-filter-driver.c +++ b/camel/camel-filter-driver.c @@ -44,8 +44,8 @@ #include "camel-stream-fs.h" #include "camel-stream-mem.h" #include "camel-mime-message.h" - #include "camel-debug.h" +#include "camel-i18n.h" #include "e-util/e-sexp.h" #include "e-util/e-memory.h" diff --git a/camel/camel-filter-search.c b/camel/camel-filter-search.c index a279b9dd96..2d057efc36 100644 --- a/camel/camel-filter-search.c +++ b/camel/camel-filter-search.c @@ -55,7 +55,7 @@ #include "camel-stream-mem.h" #include "camel-stream-fs.h" #include "camel-search-private.h" - +#include "camel-i18n.h" #include "camel-url.h" #define d(x) diff --git a/camel/camel-folder-search.c b/camel/camel-folder-search.c index ab901f3cc8..bb570b8e44 100644 --- a/camel/camel-folder-search.c +++ b/camel/camel-folder-search.c @@ -45,6 +45,7 @@ #include "camel-stream-mem.h" #include "e-util/e-memory.h" #include "camel-search-private.h" +#include "camel-i18n.h" #define d(x) #define r(x) diff --git a/camel/camel-folder.c b/camel/camel-folder.c index b7b01edf5c..30e5ab6b77 100644 --- a/camel/camel-folder.c +++ b/camel/camel-folder.c @@ -33,15 +33,13 @@ #include "camel-store.h" #include "camel-mime-message.h" #include "camel-debug.h" - #include "e-util/e-memory.h" #include "camel-operation.h" - #include "camel-session.h" #include "camel-filter-driver.h" #include "camel-private.h" #include "camel-vtrash-folder.h" -#include "filter/filter-rule.h" +#include "camel-i18n.h" #define d(x) #define w(x) @@ -1822,7 +1820,7 @@ folder_changed (CamelObject *obj, gpointer event_data) && changed->uid_recent->len > 0) driver = camel_session_get_filter_driver(session, (folder->folder_flags & CAMEL_FOLDER_FILTER_RECENT) - ? FILTER_SOURCE_INCOMING : FILTER_SOURCE_JUNKTEST, NULL); + ? "incoming":"junktest", NULL); if (driver) { recents = g_ptr_array_new(); diff --git a/camel/camel-gpg-context.c b/camel/camel-gpg-context.c index c03b095e6e..5dee244fd4 100644 --- a/camel/camel-gpg-context.c +++ b/camel/camel-gpg-context.c @@ -56,9 +56,9 @@ #include "camel-operation.h" #include "camel-mime-part.h" #include "camel-mime-filter-canon.h" - #include "camel-multipart-signed.h" #include "camel-multipart-encrypted.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/camel-list-utils.c b/camel/camel-list-utils.c new file mode 100644 index 0000000000..36818099e5 --- /dev/null +++ b/camel/camel-list-utils.c @@ -0,0 +1,271 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2004 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include "camel-list-utils.h" + +/** + * camel_dlist_init: + * @v: + * + * Initialise a double-linked list header. All list headers must be + * initialised before use. + **/ +void camel_dlist_init(CamelDList *v) +{ + v->head = (CamelDListNode *)&v->tail; + v->tail = 0; + v->tailpred = (CamelDListNode *)&v->head; +} + +/** + * camel_dlist_addhead: + * @l: An initialised list header. + * @n: A node, the next and prev pointers will be overwritten. + * + * Add the list node @n to the head (start) of the list @l. + * + * Return value: @n. + **/ +CamelDListNode *camel_dlist_addhead(CamelDList *l, CamelDListNode *n) +{ + n->next = l->head; + n->prev = (CamelDListNode *)&l->head; + l->head->prev = n; + l->head = n; + return n; +} + +/** + * camel_dlist_addtail: + * @l: An intialised list header. + * @n: A node, the next and prev pointers will be overwritten. + * + * Add the list onde @n to the tail (end) of the list @l. + * + * Return value: @n. + **/ +CamelDListNode *camel_dlist_addtail(CamelDList *l, CamelDListNode *n) +{ + n->next = (CamelDListNode *)&l->tail; + n->prev = l->tailpred; + l->tailpred->next = n; + l->tailpred = n; + return n; +} + +/** + * camel_dlist_remove: + * @n: A node which is part of a list. + * + * Remove @n from the list it's in. @n must belong to a list. + * + * Return value: @n. + **/ +CamelDListNode *camel_dlist_remove(CamelDListNode *n) +{ + n->next->prev = n->prev; + n->prev->next = n->next; + return n; +} + +/** + * camel_dlist_remhead: + * @l: An initialised list, maybe containing items. + * + * Remove the head node (start) of the list. + * + * xReturn value: The previously first-node in the list, or NULLif @l + * is an empty list. + **/ +CamelDListNode *camel_dlist_remhead(CamelDList *l) +{ + CamelDListNode *n, *nn; + + n = l->head; + nn = n->next; + if (nn) { + nn->prev = n->prev; + l->head = nn; + return n; + } + return NULL; +} + +/** + * camel_dlist_remtail: + * @l: An initialised list, maybe containing items. + * + * Remove the last node in the list. + * + * Return value: The previously last-node in the list, or NULL if @l + * is an empty list. + **/ +CamelDListNode *camel_dlist_remtail(CamelDList *l) +{ + CamelDListNode *n, *np; + + n = l->tailpred; + np = n->prev; + if (np) { + np->next = n->next; + l->tailpred = np; + return n; + } + return NULL; +} + +/** + * camel_dlist_empty: + * @l: An initialised list header. + * + * Returns %TRUE if @l is an empty list. + * + * Return value: %TRUE if @l is an empty list, %FALSE otherwise. + **/ +int camel_dlist_empty(CamelDList *l) +{ + return (l->head == (CamelDListNode *)&l->tail); +} + +/** + * camel_dlist_length: + * @l: An initialised list header. + * + * Returns the number of nodes in the list @l. + * + * Return value: The number of nodes. + **/ +int camel_dlist_length(CamelDList *l) +{ + CamelDListNode *n, *nn; + int count = 0; + + n = l->head; + nn = n->next; + while (nn) { + count++; + n = nn; + nn = n->next; + } + + return count; +} + +/* This is just for orthogonal completeness */ + +void camel_slist_init(CamelSList *v) +{ + v->head = NULL; +} + +CamelSListNode *camel_slist_addhead(CamelSList *l, CamelSListNode *n) +{ + n->next = l->head; + l->head = n; + + return n; +} + +CamelSListNode *camel_slist_addtail(CamelSList *l, CamelSListNode *n) +{ + CamelSListNode *p; + + p = (CamelSListNode *)l; + while (p->next) + p = p->next; + n->next = NULL; + p->next = n; + + return n; +} + +CamelSListNode *camel_slist_remove(CamelSList *l, CamelSListNode *n) +{ + CamelSListNode *p, *q; + + p = (CamelSListNode *)l; + while ( (q = p->next) ) { + if (q == n) { + p->next = n->next; + return n; + } + p = q; + } + + g_warning("Trying to remove SList node not present in SList"); + + return NULL; +} + +CamelSListNode *camel_slist_remhead(CamelSList *l) +{ + CamelSListNode *n; + + n = l->head; + if (n) + l->head = n->next; + + return n; +} + +CamelSListNode *camel_slist_remtail(CamelSList *l) +{ + CamelSListNode *n, *p; + + n = l->head; + if (l->head == NULL) + return NULL; + p = (CamelSListNode *)l; + while (n->next) { + p = n; + n = n->next; + } + p->next = NULL; + + return n; +} + +int camel_slist_empty(CamelSList *l) +{ + return (l->head == NULL); +} + +int camel_slist_length(CamelSList *l) +{ + CamelSListNode *n; + int count = 0; + + n = l->head; + while (n) { + count++; + n = n->next; + } + + return count; +} + diff --git a/camel/camel-list-utils.h b/camel/camel-list-utils.h new file mode 100644 index 0000000000..6613e15dd0 --- /dev/null +++ b/camel/camel-list-utils.h @@ -0,0 +1,126 @@ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2004 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef _CAMEL_LIST_UTILS_H +#define _CAMEL_LIST_UTILS_H + +/* This is a copy of Amiga's Exec lists, the head and tail nodes are + * overlapped and merged into a single list header. All operations + * are O(1), including node removal and addition from/to either end of + * the list. It can be used to implement O(1) queues, fifo's and + * normal lists. You don't need the list header to remove the node. */ + +typedef struct _CamelDList CamelDList; +typedef struct _CamelDListNode CamelDListNode; + +/** + * struct _CamelDListNode - A double-linked list node. + * + * @next: The next node link. + * @prev: The previous node link. + * + * A double-linked list node header. Put this at the start of the + * list node structure. Data is stored in the list by subclassing the + * node header rather than using a pointer. Or more normally by just + * duplicating the next and previous pointers for better type safety. + **/ +struct _CamelDListNode { + struct _CamelDListNode *next; + struct _CamelDListNode *prev; +}; + +/** + * struct _CamelDList - A double-linked list header. + * + * @head: The head node's next pointer. + * @tail: The tail node's next pointer. + * @tailpred: The previous node to the tail node. + * + * This is the merging of two separate head and tail nodes into a + * single structure. i.e. if you ahve a NULL terminated head and tail + * node such as head = { first, NULL } and tail = { NULL, last } then + * overlap them at the common NULL, you get this structure. + * + * The list header must be initialised with camel_dlist_init, or by + * using the static CAMEL_DLIST_INITIALISER macro. + **/ +struct _CamelDList { + struct _CamelDListNode *head; + struct _CamelDListNode *tail; + struct _CamelDListNode *tailpred; +}; + +#define CAMEL_DLIST_INITIALISER(l) { (CamelDListNode *)&l.tail, 0, (CamelDListNode *)&l.head } + +void camel_dlist_init(CamelDList *v); +CamelDListNode *camel_dlist_addhead(CamelDList *l, CamelDListNode *n); +CamelDListNode *camel_dlist_addtail(CamelDList *l, CamelDListNode *n); +CamelDListNode *camel_dlist_remove(CamelDListNode *n); +CamelDListNode *camel_dlist_remhead(CamelDList *l); +CamelDListNode *camel_dlist_remtail(CamelDList *l); +int camel_dlist_empty(CamelDList *l); +int camel_dlist_length(CamelDList *l); + +/* This is provided mostly for orthogonality with the dlist structure. + * By making the nodes contain all of the data themselves it + * simplifies memory management. Removing and adding from/to the head + * of the list is O(1), the rest of the operations are O(n). */ + +typedef struct _CamelSListNode CamelSListNode; +typedef struct _CamelSList CamelSList; + +/** + * struct _CamelSListNode - A single-linked list node. + * + * @next: The next node in the list. + * + * A single-linked list node header. Put this at hte start of the + * actual list node structure, or more commonly, just a next pointer. + * Data is stored in the list node by subclassing the node-header + * rather than using a pointer. + **/ +struct _CamelSListNode { + struct _CamelSListNode *next; +}; + +/** + * struct _CamelSList - A single-linked list header. + * + * @head: The head of the list. + * + * This is the header of a single-linked list. + **/ +struct _CamelSList { + struct _CamelSListNode *head; +}; + +#define CAMEL_SLIST_INITIALISER(l) { 0 } + +void camel_slist_init(CamelSList *l); +CamelSListNode *camel_slist_addhead(CamelSList *l, CamelSListNode *n); +CamelSListNode *camel_slist_addtail(CamelSList *l, CamelSListNode *n); +CamelSListNode *camel_slist_remove(CamelSList *l, CamelSListNode *n); +CamelSListNode *camel_slist_remhead(CamelSList *l); +CamelSListNode *camel_slist_remtail(CamelSList *l); +int camel_slist_empty(CamelSList *l); +int camel_slist_length(CamelSList *l); + +#endif diff --git a/camel/camel-movemail.c b/camel/camel-movemail.c index 70807195e5..521d21d0ca 100644 --- a/camel/camel-movemail.c +++ b/camel/camel-movemail.c @@ -42,12 +42,11 @@ #include "camel-movemail.h" #include "camel-exception.h" - #include "camel-mime-parser.h" #include "camel-mime-filter.h" #include "camel-mime-filter-from.h" - #include "camel-lock-client.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/camel-multipart-encrypted.c b/camel/camel-multipart-encrypted.c index aa036bec23..44ff8a0262 100644 --- a/camel/camel-multipart-encrypted.c +++ b/camel/camel-multipart-encrypted.c @@ -35,6 +35,7 @@ #include "camel-stream-fs.h" #include "camel-mime-utils.h" #include "camel-mime-part.h" +#include "camel-i18n.h" static void camel_multipart_encrypted_class_init (CamelMultipartEncryptedClass *klass); static void camel_multipart_encrypted_init (gpointer object, gpointer klass); diff --git a/camel/camel-multipart-signed.c b/camel/camel-multipart-signed.c index 5dca448a7f..e25f1c46a4 100644 --- a/camel/camel-multipart-signed.c +++ b/camel/camel-multipart-signed.c @@ -49,6 +49,7 @@ #include "camel-seekable-substream.h" #include "camel-mime-filter-crlf.h" #include "camel-mime-filter-canon.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x)) #include <stdio.h>;*/ diff --git a/camel/camel-object.c b/camel/camel-object.c index ed62737c23..a866ae95a2 100644 --- a/camel/camel-object.c +++ b/camel/camel-object.c @@ -863,7 +863,7 @@ CamelType camel_name_to_type(const char *name) } static char * -desc_data(CamelObject *o, int ok) +desc_data(CamelObject *o, guint32 ok) { char *what; @@ -885,17 +885,22 @@ desc_data(CamelObject *o, int ok) return what; } +#define check_magic(o, ctype, omagic) \ + ( ((CamelObject *)(o))->magic == (omagic) \ + && (ctype)->magic == CAMEL_OBJECT_CLASS_MAGIC) \ + ? 1 : check_magic_fail(o, ctype, omagic) + static gboolean -check_magic(void *o, CamelType ctype, int isob) +check_magic_fail(void *o, CamelType ctype, guint32 omagic) { char *what, *to; - what = desc_data(o, isob?CAMEL_OBJECT_MAGIC:CAMEL_OBJECT_CLASS_MAGIC); + what = desc_data(o, omagic); to = desc_data((CamelObject *)ctype, CAMEL_OBJECT_CLASS_MAGIC); if (what || to) { if (what == NULL) { - if (isob) + if (omagic == CAMEL_OBJECT_MAGIC) what = g_strdup_printf("OBJECT '%s'", ((CamelObject *)o)->klass->name); else what = g_strdup_printf("OBJECT '%s'", ((CamelObjectClass *)o)->name); @@ -917,7 +922,7 @@ camel_object_is(CamelObject *o, CamelType ctype) { CamelObjectClass *k; - g_return_val_if_fail(check_magic(o, ctype, TRUE), FALSE); + g_return_val_if_fail(check_magic(o, ctype, CAMEL_OBJECT_MAGIC), FALSE); k = o->klass; while (k) { @@ -932,7 +937,7 @@ camel_object_is(CamelObject *o, CamelType ctype) gboolean camel_object_class_is(CamelObjectClass *k, CamelType ctype) { - g_return_val_if_fail(check_magic(k, ctype, FALSE), FALSE); + g_return_val_if_fail(check_magic(k, ctype, CAMEL_OBJECT_CLASS_MAGIC), FALSE); while (k) { if (k == ctype) @@ -948,7 +953,7 @@ camel_object_cast(CamelObject *o, CamelType ctype) { CamelObjectClass *k; - g_return_val_if_fail(check_magic(o, ctype, TRUE), NULL); + g_return_val_if_fail(check_magic(o, ctype, CAMEL_OBJECT_MAGIC), NULL); k = o->klass; while (k) { @@ -967,7 +972,7 @@ camel_object_class_cast(CamelObjectClass *k, CamelType ctype) { CamelObjectClass *r = k; - g_return_val_if_fail(check_magic(k, ctype, FALSE), NULL); + g_return_val_if_fail(check_magic(k, ctype, CAMEL_OBJECT_CLASS_MAGIC), NULL); while (k) { if (k == ctype) @@ -1330,6 +1335,55 @@ int camel_object_get(void *vo, CamelException *ex, ...) return ret; } +void *camel_object_get_ptr(void *vo, CamelException *ex, int tag) +{ + CamelObject *o = vo; + CamelArgGetV args; + CamelObjectClass *klass = o->klass; + int ret = 0; + void *val = NULL; + + g_return_val_if_fail(CAMEL_IS_OBJECT(o), NULL); + g_return_val_if_fail((tag & CAMEL_ARG_TYPE) == CAMEL_ARG_OBJ + || (tag & CAMEL_ARG_TYPE) == CAMEL_ARG_STR + || (tag & CAMEL_ARG_TYPE) == CAMEL_ARG_PTR, 0); + + /* woefully inefficient, *shrug */ + args.argc = 1; + args.argv[0].tag = tag; + args.argv[0].ca_ptr = &val; + + ret = klass->getv(o, ex, &args); + if (ret != 0) + return NULL; + else + return val; +} + +int camel_object_get_int(void *vo, CamelException *ex, int tag) +{ + CamelObject *o = vo; + CamelArgGetV args; + CamelObjectClass *klass = o->klass; + int ret = 0; + int val = 0; + + g_return_val_if_fail(CAMEL_IS_OBJECT(o), 0); + g_return_val_if_fail((tag & CAMEL_ARG_TYPE) == CAMEL_ARG_INT + || (tag & CAMEL_ARG_TYPE) == CAMEL_ARG_BOO, 0); + + /* woefully inefficient, *shrug */ + args.argc = 1; + args.argv[0].tag = tag; + args.argv[0].ca_int = &val; + + ret = klass->getv(o, ex, &args); + if (ret != 0) + return 0; + else + return val; +} + int camel_object_getv(void *vo, CamelException *ex, CamelArgGetV *args) { g_return_val_if_fail(CAMEL_IS_OBJECT(vo), -1); diff --git a/camel/camel-object.h b/camel/camel-object.h index cbdf9fdb2c..9ee9c45345 100644 --- a/camel/camel-object.h +++ b/camel/camel-object.h @@ -39,9 +39,6 @@ extern "C" { #include <camel/camel-arg.h> #include <camel/camel-types.h> /* this is a @##$@#SF stupid header */ -/* this crap shouldn't be here */ -#include <camel/camel-i18n.h> - /* turn on so that camel_object_class_dump_tree() dumps object instances as well */ #define CAMEL_OBJECT_TRACK_INSTANCES @@ -235,6 +232,10 @@ int camel_object_setv(void *obj, struct _CamelException *ex, CamelArgV *); int camel_object_get(void *obj, struct _CamelException *ex, ...); int camel_object_getv(void *obj, struct _CamelException *ex, CamelArgGetV *); +/* not very efficient one-time calls */ +void *camel_object_get_ptr(void *vo, CamelException *ex, int tag); +int camel_object_get_int(void *vo, CamelException *ex, int tag); + /* meta-data for user-specific data */ char *camel_object_meta_get(void *vo, const char * name); gboolean camel_object_meta_set(void *vo, const char * name, const char *value); diff --git a/camel/camel-provider.c b/camel/camel-provider.c index e2cc71523c..889188f672 100644 --- a/camel/camel-provider.c +++ b/camel/camel-provider.c @@ -41,8 +41,8 @@ #include "camel-exception.h" #include "camel-string-utils.h" #include "camel-vee-store.h" - #include "e-util/e-msgport.h" +#include "camel-i18n.h" /* table of CamelProviderModule's */ static GHashTable *module_table; diff --git a/camel/camel-provider.h b/camel/camel-provider.h index 46977d4844..c61398133d 100644 --- a/camel/camel-provider.h +++ b/camel/camel-provider.h @@ -160,6 +160,9 @@ typedef struct { /* Flags describing the provider, flags describing its URLs */ int flags, url_flags; + + /* The ConfEntry and AutoDetect functions will probably be + * DEPRECATED in a future release */ /* Extra configuration information */ CamelProviderConfEntry *extra_conf; diff --git a/camel/camel-sasl-anonymous.c b/camel/camel-sasl-anonymous.c index 9b97fdbc07..c0957831b2 100644 --- a/camel/camel-sasl-anonymous.c +++ b/camel/camel-sasl-anonymous.c @@ -29,7 +29,7 @@ #include "camel-sasl-anonymous.h" #include "camel-internet-address.h" #include <string.h> - +#include "camel-i18n.h" CamelServiceAuthType camel_sasl_anonymous_authtype = { N_("Anonymous"), diff --git a/camel/camel-sasl-cram-md5.c b/camel/camel-sasl-cram-md5.c index f544d48a22..611c54178b 100644 --- a/camel/camel-sasl-cram-md5.c +++ b/camel/camel-sasl-cram-md5.c @@ -30,6 +30,7 @@ #include "camel-mime-utils.h" #include "camel-service.h" #include <e-util/md5-utils.h> +#include "camel-i18n.h" CamelServiceAuthType camel_sasl_cram_md5_authtype = { N_("CRAM-MD5"), diff --git a/camel/camel-sasl-digest-md5.c b/camel/camel-sasl-digest-md5.c index e421bd5294..36fd2f5808 100644 --- a/camel/camel-sasl-digest-md5.c +++ b/camel/camel-sasl-digest-md5.c @@ -37,6 +37,7 @@ #include "camel-charset-map.h" #include "camel-mime-utils.h" #include "camel-sasl-digest-md5.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/camel-sasl-login.c b/camel/camel-sasl-login.c index f6c3c9e5f8..ffe8f645bd 100644 --- a/camel/camel-sasl-login.c +++ b/camel/camel-sasl-login.c @@ -27,6 +27,7 @@ #include <string.h> #include "camel-sasl-login.h" #include "camel-service.h" +#include "camel-i18n.h" CamelServiceAuthType camel_sasl_login_authtype = { N_("Login"), diff --git a/camel/camel-sasl-ntlm.c b/camel/camel-sasl-ntlm.c index ad341353c4..32c47aebd1 100644 --- a/camel/camel-sasl-ntlm.c +++ b/camel/camel-sasl-ntlm.c @@ -22,11 +22,12 @@ #include <config.h> #endif -#include "camel-sasl-ntlm.h" - #include <ctype.h> #include <string.h> +#include "camel-sasl-ntlm.h" +#include "camel-i18n.h" + CamelServiceAuthType camel_sasl_ntlm_authtype = { N_("NTLM / SPA"), diff --git a/camel/camel-sasl-plain.c b/camel/camel-sasl-plain.c index 48d1fc9a01..debff64abd 100644 --- a/camel/camel-sasl-plain.c +++ b/camel/camel-sasl-plain.c @@ -27,6 +27,7 @@ #include <string.h> #include "camel-sasl-plain.h" #include "camel-service.h" +#include "camel-i18n.h" CamelServiceAuthType camel_sasl_plain_authtype = { N_("PLAIN"), diff --git a/camel/camel-sasl-popb4smtp.c b/camel/camel-sasl-popb4smtp.c index fb940b012a..6eebf5873e 100644 --- a/camel/camel-sasl-popb4smtp.c +++ b/camel/camel-sasl-popb4smtp.c @@ -31,6 +31,7 @@ #include "camel-sasl-popb4smtp.h" #include "camel-service.h" #include "camel-session.h" +#include "camel-i18n.h" CamelServiceAuthType camel_sasl_popb4smtp_authtype = { N_("POP before SMTP"), diff --git a/camel/camel-search-private.c b/camel/camel-search-private.c index 2ba908bea3..d3805f6a7f 100644 --- a/camel/camel-search-private.c +++ b/camel/camel-search-private.c @@ -41,13 +41,50 @@ #include "camel-multipart.h" #include "camel-stream-mem.h" #include "e-util/e-sexp.h" - #include "camel-search-private.h" +#include "camel-i18n.h" #include <glib/gunicode.h> #define d(x) +static inline guint32 +camel_utf8_getc(const unsigned char **ptr) +{ + register unsigned char *p = (unsigned char *)*ptr; + register unsigned char c, r; + register guint32 v, m; + +again: + r = *p++; +loop: + if (r < 0x80) { + *ptr = p; + v = r; + } else if (r < 0xfe) { /* valid start char? */ + v = r; + m = 0x7f80; /* used to mask out the length bits */ + do { + c = *p++; + if ((c & 0xc0) != 0x80) { + r = c; + goto loop; + } + v = (v<<6) | (c & 0x3f); + r<<=1; + m<<=5; + } while (r & 0x40); + + *ptr = p; + + v &= ~m; + } else { + goto again; + } + + return v; +} + /* builds the regex into pattern */ /* taken from camel-folder-search, with added isregex & exception parameter */ /* Basically, we build a new regex, either based on subset regex's, or substrings, @@ -195,28 +232,12 @@ header_soundex (const char *header, const char *match) return truth; } -/* FIXME: This is stupidly slow and needs to be removed */ -static gunichar -utf8_get (const char **inp) -{ - const unsigned char *p = *inp; - gunichar c; - - if (p == NULL) - return 0; - - c = g_utf8_get_char (p); - *inp = g_unichar_validate (c) ? g_utf8_next_char (p) : NULL; - - return c; -} - const char * camel_ustrstrcase (const char *haystack, const char *needle) { gunichar *nuni, *puni; gunichar u; - const char *p; + const unsigned char *p; g_return_val_if_fail (haystack != NULL, NULL); g_return_val_if_fail (needle != NULL, NULL); @@ -229,25 +250,25 @@ camel_ustrstrcase (const char *haystack, const char *needle) puni = nuni = g_alloca (sizeof (gunichar) * strlen (needle)); p = needle; - while ((u = utf8_get (&p))) + while ((u = camel_utf8_getc(&p))) *puni++ = g_unichar_tolower (u); /* NULL means there was illegal utf-8 sequence */ if (!p) return NULL; - p = haystack; - while ((u = utf8_get (&p))) { + p = (const unsigned char *)haystack; + while ((u = camel_utf8_getc(&p))) { gunichar c; c = g_unichar_tolower (u); /* We have valid stripped char */ if (c == nuni[0]) { - const gchar *q = p; + const unsigned char *q = p; gint npos = 1; while (nuni + npos < puni) { - u = utf8_get (&q); + u = camel_utf8_getc(&q); if (!q || !u) return NULL; @@ -283,8 +304,8 @@ camel_ustrcasecmp (const char *s1, const char *s2) CAMEL_SEARCH_COMPARE (s1, s2, NULL); - u1 = utf8_get (&s1); - u2 = utf8_get (&s2); + u1 = camel_utf8_getc((const unsigned char **)&s1); + u2 = camel_utf8_getc((const unsigned char **)&s2); while (u1 && u2) { u1 = g_unichar_tolower (u1); u2 = g_unichar_tolower (u2); @@ -293,8 +314,8 @@ camel_ustrcasecmp (const char *s1, const char *s2) else if (u1 > u2) return 1; - u1 = utf8_get (&s1); - u2 = utf8_get (&s2); + u1 = camel_utf8_getc((const unsigned char **)&s1); + u2 = camel_utf8_getc((const unsigned char **)&s2); } /* end of one of the strings ? */ @@ -313,8 +334,8 @@ camel_ustrncasecmp (const char *s1, const char *s2, size_t len) CAMEL_SEARCH_COMPARE (s1, s2, NULL); - u1 = utf8_get (&s1); - u2 = utf8_get (&s2); + u1 = camel_utf8_getc((const unsigned char **)&s1); + u2 = camel_utf8_getc((const unsigned char **)&s2); while (len > 0 && u1 && u2) { u1 = g_unichar_tolower (u1); u2 = g_unichar_tolower (u2); @@ -324,8 +345,8 @@ camel_ustrncasecmp (const char *s1, const char *s2, size_t len) return 1; len--; - u1 = utf8_get (&s1); - u2 = utf8_get (&s2); + u1 = camel_utf8_getc((const unsigned char **)&s1); + u2 = camel_utf8_getc((const unsigned char **)&s2); } if (len == 0) @@ -344,7 +365,7 @@ camel_ustrncasecmp (const char *s1, const char *s2, size_t len) static int header_match(const char *value, const char *match, camel_search_match_t how) { - const char *p; + const unsigned char *p; int vlen, mlen; if (how == CAMEL_SEARCH_MATCH_SOUNDEX) @@ -357,7 +378,7 @@ header_match(const char *value, const char *match, camel_search_match_t how) /* from dan the man, if we have mixed case, perform a case-sensitive match, otherwise not */ - p = match; + p = (const unsigned char *)match; while (*p) { if (isupper(*p)) { switch (how) { @@ -495,43 +516,6 @@ camel_search_message_body_contains (CamelDataWrapper *object, regex_t *pattern) return truth; } -static inline guint32 -camel_utf8_getc(const unsigned char **ptr) -{ - register unsigned char *p = (unsigned char *)*ptr; - register unsigned char c, r; - register guint32 v, m; - -again: - r = *p++; -loop: - if (r < 0x80) { - *ptr = p; - v = r; - } else if (r < 0xfe) { /* valid start char? */ - v = r; - m = 0x7f80; /* used to mask out the length bits */ - do { - c = *p++; - if ((c & 0xc0) != 0x80) { - r = c; - goto loop; - } - v = (v<<6) | (c & 0x3f); - r<<=1; - m<<=5; - } while (r & 0x40); - - *ptr = p; - - v &= ~m; - } else { - goto again; - } - - return v; -} - static void output_c(GString *w, guint32 c, int *type) { diff --git a/camel/camel-service.c b/camel/camel-service.c index d108f124c0..11f97cd2a6 100644 --- a/camel/camel-service.c +++ b/camel/camel-service.c @@ -35,7 +35,6 @@ #include <errno.h> #include "e-util/e-msgport.h" - #include "e-util/e-host-utils.h" #include "camel-service.h" @@ -43,6 +42,7 @@ #include "camel-exception.h" #include "camel-operation.h" #include "camel-private.h" +#include "camel-i18n.h" #define d(x) #define w(x) diff --git a/camel/camel-session.c b/camel/camel-session.c index 5a1b8799f1..73cfe72afd 100644 --- a/camel/camel-session.c +++ b/camel/camel-session.c @@ -41,6 +41,7 @@ #include "camel-file-utils.h" #include "camel-string-utils.h" #include "camel-url.h" +#include "camel-i18n.h" #include "camel-private.h" diff --git a/camel/camel-smime-context.c b/camel/camel-smime-context.c index 019d16d1d9..d1c228a4a9 100644 --- a/camel/camel-smime-context.c +++ b/camel/camel-smime-context.c @@ -56,6 +56,7 @@ #include "camel-smime-context.h" #include "camel-operation.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/camel-store.c b/camel/camel-store.c index 677e92a7cb..4075170eb8 100644 --- a/camel/camel-store.c +++ b/camel/camel-store.c @@ -39,6 +39,7 @@ #include "camel-vtrash-folder.h" #include "camel-exception.h" #include "camel-private.h" +#include "camel-i18n.h" #define d(x) #define w(x) diff --git a/camel/camel-tcp-stream-ssl.c b/camel/camel-tcp-stream-ssl.c index 791083186c..ef89d8c88e 100644 --- a/camel/camel-tcp-stream-ssl.c +++ b/camel/camel-tcp-stream-ssl.c @@ -61,6 +61,7 @@ #include "camel-session.h" #include "camel-certdb.h" #include "camel-operation.h" +#include "camel-i18n.h" /* from md5-utils.h */ void md5_get_digest (const char *buffer, int buffer_size, unsigned char digest[16]); diff --git a/camel/camel-url.c b/camel/camel-url.c index 7033913ea0..4e3e600981 100644 --- a/camel/camel-url.c +++ b/camel/camel-url.c @@ -37,6 +37,7 @@ #include "camel-mime-utils.h" #include "camel-object.h" #include "camel-string-utils.h" +#include "camel-i18n.h" static void copy_param (GQuark key_id, gpointer data, gpointer user_data); static void output_param (GQuark key_id, gpointer data, gpointer user_data); @@ -428,7 +429,10 @@ camel_url_set_port (CamelURL *url, int port) void camel_url_set_param (CamelURL *url, const char *name, const char *value) { - g_datalist_set_data_full (&url->params, name, value ? g_strdup (value) : NULL, g_free); + if (value) + g_datalist_set_data_full (&url->params, name, g_strdup(value), g_free); + else + g_datalist_remove_data(&url->params, name); } const char * diff --git a/camel/camel-vee-folder.c b/camel/camel-vee-folder.c index c74b001cc9..1475c1da25 100644 --- a/camel/camel-vee-folder.c +++ b/camel/camel-vee-folder.c @@ -38,6 +38,7 @@ #include "camel-vee-store.h" /* for open flags */ #include "camel-private.h" #include "camel-debug.h" +#include "camel-i18n.h" #include "e-util/md5-utils.h" diff --git a/camel/camel-vee-store.c b/camel/camel-vee-store.c index d0ef62aff4..770a0239c1 100644 --- a/camel/camel-vee-store.c +++ b/camel/camel-vee-store.c @@ -27,6 +27,7 @@ #include "camel-vee-folder.h" #include "camel-private.h" +#include "camel-i18n.h" #include <string.h> diff --git a/camel/camel-vtrash-folder.c b/camel/camel-vtrash-folder.c index 667b574646..91e4b8a3a1 100644 --- a/camel/camel-vtrash-folder.c +++ b/camel/camel-vtrash-folder.c @@ -27,6 +27,7 @@ #include "camel-store.h" #include "camel-vee-store.h" #include "camel-mime-message.h" +#include "camel-i18n.h" #include <string.h> diff --git a/camel/providers/groupwise/camel-groupwise-provider.c b/camel/providers/groupwise/camel-groupwise-provider.c index 4a426bc3d5..cd6f2e8071 100644 --- a/camel/providers/groupwise/camel-groupwise-provider.c +++ b/camel/providers/groupwise/camel-groupwise-provider.c @@ -37,6 +37,7 @@ #include "camel-url.h" #include "camel-sasl.h" #include "camel-gw-listener.h" +#include "camel-i18n.h" static void add_hash (guint *hash, char *s); static guint groupwise_url_hash (gconstpointer key); @@ -55,6 +56,7 @@ CamelProviderConfEntry groupwise_conf_entries[] = { N_("Check for new messages in all folders"), "1" }, { CAMEL_PROVIDER_CONF_SECTION_END }, + { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL, N_("Apply filters to new messages in Inbox on this server"), "0" }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter_junk", NULL, @@ -63,6 +65,7 @@ CamelProviderConfEntry groupwise_conf_entries[] = { N_("Only check for Junk messages in the INBOX folder"), "0" }, { CAMEL_PROVIDER_CONF_CHECKBOX, "offline_sync", NULL, N_("Automatically synchronize remote mail locally"), "0" }, + { CAMEL_PROVIDER_CONF_SECTION_END }, /* extra Groupwise configuration settings */ {CAMEL_PROVIDER_CONF_SECTION_START, "soapport", NULL, @@ -82,7 +85,6 @@ CamelProviderConfEntry groupwise_conf_entries[] = { { CAMEL_PROVIDER_CONF_SECTION_END }, - { CAMEL_PROVIDER_CONF_END } }; diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c index e7cfc051a5..0e8db51896 100644 --- a/camel/providers/imap/camel-imap-command.c +++ b/camel/providers/imap/camel-imap-command.c @@ -43,7 +43,7 @@ #include <camel/camel-private.h> #include <camel/camel-utf8.h> #include <camel/camel-session.h> - +#include "camel-i18n.h" extern int camel_verbose_debug; diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c index f7a33e6ab4..5eaafcbf13 100644 --- a/camel/providers/imap/camel-imap-folder.c +++ b/camel/providers/imap/camel-imap-folder.c @@ -23,7 +23,6 @@ * USA */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -70,6 +69,7 @@ #include "camel-string-utils.h" #include "camel-file-utils.h" #include "camel-debug.h" +#include "camel-i18n.h" #define d(x) x diff --git a/camel/providers/imap/camel-imap-message-cache.c b/camel/providers/imap/camel-imap-message-cache.c index 559c6d2f88..17fa9ca5c8 100644 --- a/camel/providers/imap/camel-imap-message-cache.c +++ b/camel/providers/imap/camel-imap-message-cache.c @@ -36,6 +36,7 @@ #include "camel-data-wrapper.h" #include "camel-exception.h" #include "camel-stream-fs.h" +#include "camel-i18n.h" static void finalize (CamelImapMessageCache *cache); static void stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data); diff --git a/camel/providers/imap/camel-imap-provider.c b/camel/providers/imap/camel-imap-provider.c index bef15f681f..3539fa2cd7 100644 --- a/camel/providers/imap/camel-imap-provider.c +++ b/camel/providers/imap/camel-imap-provider.c @@ -32,6 +32,7 @@ #include "camel-session.h" #include "camel-url.h" #include "camel-sasl.h" +#include "camel-i18n.h" static void add_hash (guint *hash, char *s); static guint imap_url_hash (gconstpointer key); @@ -60,6 +61,7 @@ CamelProviderConfEntry imap_conf_entries[] = { { CAMEL_PROVIDER_CONF_ENTRY, "namespace", "override_namespace", N_("Namespace") }, { CAMEL_PROVIDER_CONF_SECTION_END }, + { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL, N_("Apply filters to new messages in INBOX on this server"), "0" }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter_junk", NULL, @@ -68,6 +70,7 @@ CamelProviderConfEntry imap_conf_entries[] = { N_("Only check for Junk messages in the INBOX folder"), "0" }, { CAMEL_PROVIDER_CONF_CHECKBOX, "offline_sync", NULL, N_("Automatically synchronize remote mail locally"), "0" }, + { CAMEL_PROVIDER_CONF_SECTION_END }, { CAMEL_PROVIDER_CONF_END } }; diff --git a/camel/providers/imap/camel-imap-store.c b/camel/providers/imap/camel-imap-store.c index af031bdc47..85e8b8fc04 100644 --- a/camel/providers/imap/camel-imap-store.c +++ b/camel/providers/imap/camel-imap-store.c @@ -24,7 +24,6 @@ * */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -59,11 +58,10 @@ #include "camel-sasl.h" #include "camel-utf8.h" #include "camel-string-utils.h" - #include "camel-imap-private.h" #include "camel-private.h" - #include "camel-debug.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/providers/imapp/camel-imapp-provider.c b/camel/providers/imapp/camel-imapp-provider.c index b8d6e53374..21ec24cb77 100644 --- a/camel/providers/imapp/camel-imapp-provider.c +++ b/camel/providers/imapp/camel-imapp-provider.c @@ -31,11 +31,12 @@ #include "camel/camel-session.h" #include "camel/camel-url.h" #include "camel/camel-sasl.h" +#include "camel/camel-i18n.h" #include "camel-imapp-store.h" CamelProviderConfEntry imapp_conf_entries[] = { - { CAMEL_PROVIDER_CONF_SECTION_START, NULL, NULL, + { CAMEL_PROVIDER_CONF_SECTION_START, "storage", NULL, N_("Message storage") }, { CAMEL_PROVIDER_CONF_SECTION_END }, { CAMEL_PROVIDER_CONF_END } diff --git a/camel/providers/imapp/camel-imapp-store.c b/camel/providers/imapp/camel-imapp-store.c index 290d3c0a17..3c0dac5456 100644 --- a/camel/providers/imapp/camel-imapp-store.c +++ b/camel/providers/imapp/camel-imapp-store.c @@ -48,6 +48,7 @@ #ifdef HAVE_SSL #include "camel/camel-tcp-stream-ssl.h" #endif +#include "camel/camel-i18n.h" #include "camel-imapp-store-summary.h" #include "camel-imapp-store.h" diff --git a/camel/providers/local/camel-local-folder.c b/camel/providers/local/camel-local-folder.c index 2f5cc53223..f18e6c1015 100644 --- a/camel/providers/local/camel-local-folder.c +++ b/camel/providers/local/camel-local-folder.c @@ -48,6 +48,7 @@ #include "camel-stream-filter.h" #include "camel-mime-filter-from.h" #include "camel-exception.h" +#include "camel-i18n.h" #include "camel-local-private.h" diff --git a/camel/providers/local/camel-local-provider.c b/camel/providers/local/camel-local-provider.c index 27fae76695..7411d8f639 100644 --- a/camel/providers/local/camel-local-provider.c +++ b/camel/providers/local/camel-local-provider.c @@ -34,13 +34,16 @@ #include "camel-mbox-store.h" #include "camel-maildir-store.h" #include "camel-spool-store.h" +#include "camel-i18n.h" #define d(x) static CamelProviderConfEntry mh_conf_entries[] = { CAMEL_PROVIDER_CONF_DEFAULT_PATH, + { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "dotfolders", NULL, N_("Use the `.folders' folder summary file (exmh)"), "0" }, + { CAMEL_PROVIDER_CONF_SECTION_END }, { CAMEL_PROVIDER_CONF_END } }; @@ -73,8 +76,10 @@ static CamelProvider mbox_provider = { static CamelProviderConfEntry maildir_conf_entries[] = { CAMEL_PROVIDER_CONF_DEFAULT_PATH, + { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL, N_("Apply filters to new messages in INBOX"), "0" }, + { CAMEL_PROVIDER_CONF_SECTION_END }, { CAMEL_PROVIDER_CONF_END } }; @@ -91,8 +96,10 @@ static CamelProvider maildir_provider = { static CamelProviderConfEntry spool_conf_entries[] = { CAMEL_PROVIDER_CONF_DEFAULT_PATH, + { CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL, N_("Apply filters to new messages in INBOX"), "0" }, { CAMEL_PROVIDER_CONF_CHECKBOX, "xstatus", NULL, N_("Store status headers in Elm/Pine/Mutt format"), "0" }, + { CAMEL_PROVIDER_CONF_SECTION_END }, { CAMEL_PROVIDER_CONF_END } }; diff --git a/camel/providers/local/camel-local-store.c b/camel/providers/local/camel-local-store.c index f39f597f08..a89fee16c5 100644 --- a/camel/providers/local/camel-local-store.c +++ b/camel/providers/local/camel-local-store.c @@ -36,6 +36,7 @@ #include "camel-local-store.h" #include "camel-exception.h" #include "camel-url.h" +#include "camel-i18n.h" #include "camel-local-folder.h" #include <camel/camel-text-index.h> diff --git a/camel/providers/local/camel-local-summary.c b/camel/providers/local/camel-local-summary.c index a1f4831675..e515dd54c6 100644 --- a/camel/providers/local/camel-local-summary.c +++ b/camel/providers/local/camel-local-summary.c @@ -35,6 +35,7 @@ #include "camel/camel-mime-message.h" #include "camel/camel-stream-null.h" #include "camel/camel-file-utils.h" +#include "camel/camel-i18n.h" #define w(x) #define io(x) diff --git a/camel/providers/local/camel-maildir-folder.c b/camel/providers/local/camel-maildir-folder.c index 17bc081f94..bd38dcf3d7 100644 --- a/camel/providers/local/camel-maildir-folder.c +++ b/camel/providers/local/camel-maildir-folder.c @@ -39,6 +39,7 @@ #include "camel-data-wrapper.h" #include "camel-mime-message.h" #include "camel-exception.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/local/camel-maildir-store.c b/camel/providers/local/camel-maildir-store.c index 40cdc07e75..9c4097a3cb 100644 --- a/camel/providers/local/camel-maildir-store.c +++ b/camel/providers/local/camel-maildir-store.c @@ -37,6 +37,7 @@ #include "camel-url.h" #include "camel-private.h" #include "camel-maildir-summary.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/providers/local/camel-maildir-summary.c b/camel/providers/local/camel-maildir-summary.c index edc81a5230..8b15c2b991 100644 --- a/camel/providers/local/camel-maildir-summary.c +++ b/camel/providers/local/camel-maildir-summary.c @@ -42,6 +42,7 @@ #include "camel-private.h" #include "e-util/e-memory.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ @@ -279,7 +280,7 @@ static CamelMessageInfo *message_info_new(CamelFolderSummary * s, struct _camel_ if (info) { d(printf("already seen uid '%s', just summarising instead\n", uid)); camel_folder_summary_info_free(s, mi); - mdi = (CamelMaildirMessageInfo *)mi = info; + mdi = (CamelMaildirMessageInfo *)(mi = info); } /* with maildir we know the real received date, from the filename */ diff --git a/camel/providers/local/camel-mbox-folder.c b/camel/providers/local/camel-mbox-folder.c index 0d86b6d766..a28db10951 100644 --- a/camel/providers/local/camel-mbox-folder.c +++ b/camel/providers/local/camel-mbox-folder.c @@ -42,6 +42,7 @@ #include "camel-stream-filter.h" #include "camel-mime-filter-from.h" #include "camel-exception.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/local/camel-mbox-store.c b/camel/providers/local/camel-mbox-store.c index a2bafd4ab0..76811949fd 100644 --- a/camel/providers/local/camel-mbox-store.c +++ b/camel/providers/local/camel-mbox-store.c @@ -37,6 +37,7 @@ #include "camel-text-index.h" #include "camel-exception.h" #include "camel-url.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/providers/local/camel-mbox-summary.c b/camel/providers/local/camel-mbox-summary.c index c34df06114..60dcf34c1c 100644 --- a/camel/providers/local/camel-mbox-summary.c +++ b/camel/providers/local/camel-mbox-summary.c @@ -41,6 +41,7 @@ #include "camel/camel-file-utils.h" #include "camel/camel-mime-message.h" #include "camel/camel-operation.h" +#include "camel-i18n.h" #define io(x) #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ @@ -255,7 +256,7 @@ message_info_new(CamelFolderSummary *s, struct _camel_header_raw *h) if ((info->flags & CAMEL_MESSAGE_FOLDER_NOTSEEN)) { info->flags &= ~CAMEL_MESSAGE_FOLDER_NOTSEEN; camel_folder_summary_info_free(s, mi); - mbi = (CamelMboxMessageInfo *)mi = info; + mbi = (CamelMboxMessageInfo *)(mi = info); } else { add = 7; d(printf("seen '%s' before, adding anew\n", uid)); diff --git a/camel/providers/local/camel-mh-folder.c b/camel/providers/local/camel-mh-folder.c index 83a11e24e2..5f71ab09ea 100644 --- a/camel/providers/local/camel-mh-folder.c +++ b/camel/providers/local/camel-mh-folder.c @@ -39,6 +39,7 @@ #include "camel-data-wrapper.h" #include "camel-mime-message.h" #include "camel-exception.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/local/camel-mh-store.c b/camel/providers/local/camel-mh-store.c index ee35d8ec25..cc2f3ce9c9 100644 --- a/camel/providers/local/camel-mh-store.c +++ b/camel/providers/local/camel-mh-store.c @@ -34,6 +34,7 @@ #include "camel-exception.h" #include "camel-url.h" #include "camel-private.h" +#include "camel-i18n.h" #include <camel/camel-stream-fs.h> #include <camel/camel-stream-buffer.h> diff --git a/camel/providers/local/camel-mh-summary.c b/camel/providers/local/camel-mh-summary.c index 5dd26ac9ba..d51bf7a108 100644 --- a/camel/providers/local/camel-mh-summary.c +++ b/camel/providers/local/camel-mh-summary.c @@ -40,6 +40,7 @@ #include <camel/camel-mime-message.h> #include "camel-private.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/local/camel-spool-folder.c b/camel/providers/local/camel-spool-folder.c index a1b1901142..026d99402e 100644 --- a/camel/providers/local/camel-spool-folder.c +++ b/camel/providers/local/camel-spool-folder.c @@ -41,10 +41,9 @@ #include "camel-stream-filter.h" #include "camel-mime-filter-from.h" #include "camel-exception.h" - #include "camel-lock-client.h" - #include "camel-local-private.h" +#include "camel-i18n.h" #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/local/camel-spool-store.c b/camel/providers/local/camel-spool-store.c index 96278bf538..f1f715e404 100644 --- a/camel/providers/local/camel-spool-store.c +++ b/camel/providers/local/camel-spool-store.c @@ -41,6 +41,7 @@ #include "camel-exception.h" #include "camel-url.h" #include "camel-private.h" +#include "camel-i18n.h" #define d(x) diff --git a/camel/providers/local/camel-spool-summary.c b/camel/providers/local/camel-spool-summary.c index 7991a881cc..3e4fb533ab 100644 --- a/camel/providers/local/camel-spool-summary.c +++ b/camel/providers/local/camel-spool-summary.c @@ -38,6 +38,7 @@ #include "camel-mime-message.h" #include "camel-file-utils.h" #include "camel-operation.h" +#include "camel-i18n.h" #define io(x) #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/ diff --git a/camel/providers/nntp/camel-nntp-folder.c b/camel/providers/nntp/camel-nntp-folder.c index 04855a2a78..40abfcf5f2 100644 --- a/camel/providers/nntp/camel-nntp-folder.c +++ b/camel/providers/nntp/camel-nntp-folder.c @@ -21,7 +21,6 @@ * USA */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -50,6 +49,7 @@ #include "camel/camel-multipart.h" #include "camel/camel-mime-part.h" #include "camel/camel-stream-buffer.h" +#include "camel/camel-i18n.h" #include "camel-nntp-summary.h" #include "camel-nntp-store.h" diff --git a/camel/providers/nntp/camel-nntp-provider.c b/camel/providers/nntp/camel-nntp-provider.c index f2b4c1a24d..5780c41f9d 100644 --- a/camel/providers/nntp/camel-nntp-provider.c +++ b/camel/providers/nntp/camel-nntp-provider.c @@ -30,6 +30,7 @@ #include "camel-nntp-store.h" #include "camel-provider.h" #include "camel-session.h" +#include "camel-i18n.h" static void add_hash (guint *hash, char *s); static guint nntp_url_hash (gconstpointer key); @@ -57,7 +58,7 @@ static CamelProvider news_provider = { "news", CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE | - CAMEL_PROVIDER_IS_STORAGE, + CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_SUPPORTS_SSL, CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_USER | CAMEL_URL_ALLOW_PASSWORD | CAMEL_URL_ALLOW_AUTH, diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c index 31048e7a12..6d8490d453 100644 --- a/camel/providers/nntp/camel-nntp-store.c +++ b/camel/providers/nntp/camel-nntp-store.c @@ -21,7 +21,6 @@ * USA */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -52,6 +51,7 @@ #include "camel-nntp-folder.h" #include "camel-nntp-private.h" #include "camel-nntp-resp-codes.h" +#include "camel-i18n.h" #define w(x) extern int camel_verbose_debug; diff --git a/camel/providers/nntp/camel-nntp-summary.c b/camel/providers/nntp/camel-nntp-summary.c index d30fdc6267..6a1d208760 100644 --- a/camel/providers/nntp/camel-nntp-summary.c +++ b/camel/providers/nntp/camel-nntp-summary.c @@ -36,6 +36,7 @@ #include "camel/camel-stream-null.h" #include "camel/camel-operation.h" #include "camel/camel-data-cache.h" +#include "camel/camel-i18n.h" #include "camel-nntp-summary.h" #include "camel-nntp-folder.h" diff --git a/camel/providers/pop3/camel-pop3-engine.c b/camel/providers/pop3/camel-pop3-engine.c index 9c3f8423e9..e79a382359 100644 --- a/camel/providers/pop3/camel-pop3-engine.c +++ b/camel/providers/pop3/camel-pop3-engine.c @@ -33,8 +33,9 @@ #include "camel-pop3-engine.h" #include "camel-pop3-stream.h" -#include <camel/camel-service.h> -#include <camel/camel-sasl.h> +#include "camel-service.h" +#include "camel-sasl.h" +#include "camel-i18n.h" /* max 'outstanding' bytes in output stream, so we can't deadlock waiting for the server to accept our data when pipelining */ @@ -224,8 +225,6 @@ static void get_capabilities(CamelPOP3Engine *pe) { CamelPOP3Command *pc; - unsigned char *line; - unsigned int len; if (!(pe->flags & CAMEL_POP3_ENGINE_DISABLE_EXTENSIONS)) { pc = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, "CAPA\r\n"); diff --git a/camel/providers/pop3/camel-pop3-folder.c b/camel/providers/pop3/camel-pop3-folder.c index 1c17079006..23a5bc7d88 100644 --- a/camel/providers/pop3/camel-pop3-folder.c +++ b/camel/providers/pop3/camel-pop3-folder.c @@ -37,6 +37,7 @@ #include "camel-mime-message.h" #include "camel-operation.h" #include "camel-data-cache.h" +#include "camel-i18n.h" #include <e-util/md5-utils.h> diff --git a/camel/providers/pop3/camel-pop3-provider.c b/camel/providers/pop3/camel-pop3-provider.c index 7c3db155a7..7253edfbf4 100644 --- a/camel/providers/pop3/camel-pop3-provider.c +++ b/camel/providers/pop3/camel-pop3-provider.c @@ -32,9 +32,10 @@ #include "camel-session.h" #include "camel-url.h" #include "camel-sasl.h" +#include "camel-i18n.h" CamelProviderConfEntry pop3_conf_entries[] = { - { CAMEL_PROVIDER_CONF_SECTION_START, NULL, NULL, + { CAMEL_PROVIDER_CONF_SECTION_START, "storage", NULL, N_("Message storage") }, { CAMEL_PROVIDER_CONF_CHECKBOX, "keep_on_server", NULL, N_("Leave messages on server"), "0" }, diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c index 96f9295b52..a63aa137e1 100644 --- a/camel/providers/pop3/camel-pop3-store.c +++ b/camel/providers/pop3/camel-pop3-store.c @@ -54,6 +54,7 @@ #ifdef HAVE_SSL #include "camel-tcp-stream-ssl.h" #endif +#include "camel-i18n.h" /* Specified in RFC 1939 */ #define POP3_PORT 110 diff --git a/camel/providers/sendmail/camel-sendmail-provider.c b/camel/providers/sendmail/camel-sendmail-provider.c index 36cbf88a77..0aeb367cf3 100644 --- a/camel/providers/sendmail/camel-sendmail-provider.c +++ b/camel/providers/sendmail/camel-sendmail-provider.c @@ -30,6 +30,7 @@ #include "camel-sendmail-transport.h" #include "camel-session.h" #include "camel-url.h" +#include "camel-i18n.h" static CamelProvider sendmail_provider = { "sendmail", diff --git a/camel/providers/sendmail/camel-sendmail-transport.c b/camel/providers/sendmail/camel-sendmail-transport.c index fbf6f08a6b..a776b9729e 100644 --- a/camel/providers/sendmail/camel-sendmail-transport.c +++ b/camel/providers/sendmail/camel-sendmail-transport.c @@ -40,6 +40,7 @@ #include "camel-data-wrapper.h" #include "camel-stream-fs.h" #include "camel-exception.h" +#include "camel-i18n.h" static char *get_name (CamelService *service, gboolean brief); diff --git a/camel/providers/smtp/camel-smtp-provider.c b/camel/providers/smtp/camel-smtp-provider.c index e9eff37a9d..bff1d847d0 100644 --- a/camel/providers/smtp/camel-smtp-provider.c +++ b/camel/providers/smtp/camel-smtp-provider.c @@ -31,6 +31,7 @@ #include "camel-session.h" #include "camel-url.h" #include "camel-sasl.h" +#include "camel-i18n.h" static CamelProvider smtp_provider = { "smtp", diff --git a/camel/providers/smtp/camel-smtp-transport.c b/camel/providers/smtp/camel-smtp-transport.c index 3245cc68a1..53a2d2cb68 100644 --- a/camel/providers/smtp/camel-smtp-transport.c +++ b/camel/providers/smtp/camel-smtp-transport.c @@ -55,7 +55,7 @@ #include "camel-session.h" #include "camel-exception.h" #include "camel-sasl.h" - +#include "camel-i18n.h" extern int camel_verbose_debug; #define d(x) (camel_verbose_debug ? (x) : 0) diff --git a/composer/ChangeLog b/composer/ChangeLog index fcc4b95045..9efd0c7769 100644 --- a/composer/ChangeLog +++ b/composer/ChangeLog @@ -44,6 +44,17 @@ * e-msg-composer-attachment.c: include config.h. See #61395. +2004-09-04 Not Zed <NotZed@Ximian.com> + + * e-msg-composer-*.c: include gnome-i18n.h if required. + +2004-08-30 Not Zed <NotZed@Ximian.com> + + * e-msg-composer-attachment-bar.c (emcab_popup): em-popup based + popup menu to replace the gnome one. + (popup_menu_event): use new func above. + (button_press_event): ", some cleanup. + 2004-06-18 Not Zed <NotZed@Ximian.com> ** Fix for #60387. diff --git a/composer/e-msg-composer-attachment-bar.c b/composer/e-msg-composer-attachment-bar.c index ea953c83b3..5bc912baed 100644 --- a/composer/e-msg-composer-attachment-bar.c +++ b/composer/e-msg-composer-attachment-bar.c @@ -31,11 +31,8 @@ #include <gconf/gconf.h> #include <gconf/gconf-client.h> #include <gdk/gdkkeysyms.h> -#include <libgnome/gnome-util.h> -#include <libgnomeui/gnome-app.h> -#include <libgnomeui/gnome-app-helper.h> -#include <libgnomeui/gnome-popup-menu.h> #include <libgnomevfs/gnome-vfs-mime-handlers.h> +#include <libgnome/gnome-i18n.h> #include "e-msg-composer.h" #include "e-msg-composer-select-file.h" @@ -55,6 +52,7 @@ #include "e-util/e-gui-utils.h" #include "e-util/e-icon-factory.h" #include "widgets/misc/e-error.h" +#include "mail/em-popup.h" #define ICON_WIDTH 64 #define ICON_SEPARATORS " /-_" @@ -70,9 +68,6 @@ static GnomeIconListClass *parent_class = NULL; struct _EMsgComposerAttachmentBarPrivate { GList *attachments; guint num_attachments; - - GtkWidget *context_menu; - GtkWidget *icon_context_menu; }; @@ -375,20 +370,20 @@ static void edit_selected (EMsgComposerAttachmentBar *bar) { GnomeIconList *icon_list; - EMsgComposerAttachment *attachment; - GList *selection; + GList *selection, *attach; int num; icon_list = GNOME_ICON_LIST (bar); selection = gnome_icon_list_get_selection (icon_list); - num = GPOINTER_TO_INT (selection->data); - attachment = g_list_nth (bar->priv->attachments, num)->data; - - e_msg_composer_attachment_edit (attachment, GTK_WIDGET (bar)); + if (selection) { + num = GPOINTER_TO_INT (selection->data); + attach = g_list_nth (bar->priv->attachments, num); + if (attach) + e_msg_composer_attachment_edit ((EMsgComposerAttachment *)attach->data, GTK_WIDGET (bar)); + } } - /* "Attach" dialog. */ static void @@ -417,109 +412,115 @@ add_from_user (EMsgComposerAttachmentBar *bar) /* Callbacks. */ static void -add_cb (GtkWidget *widget, gpointer data, GtkWidget *for_widget) +emcab_add(EPopup *ep, EPopupItem *item, void *data) { - g_return_if_fail (E_IS_MSG_COMPOSER_ATTACHMENT_BAR (data)); - - add_from_user (E_MSG_COMPOSER_ATTACHMENT_BAR (data)); + EMsgComposerAttachmentBar *bar = data; + + add_from_user(bar); } static void -properties_cb (GtkWidget *widget, gpointer data, GtkWidget *for_widget) +emcab_properties(EPopup *ep, EPopupItem *item, void *data) { - EMsgComposerAttachmentBar *bar; - - g_return_if_fail (E_IS_MSG_COMPOSER_ATTACHMENT_BAR (data)); + EMsgComposerAttachmentBar *bar = data; - bar = E_MSG_COMPOSER_ATTACHMENT_BAR (data); - edit_selected (data); + edit_selected(bar); } static void -remove_cb (GtkWidget *widget, gpointer data, GtkWidget *for_widget) +emcab_remove(EPopup *ep, EPopupItem *item, void *data) { - EMsgComposerAttachmentBar *bar; - - g_return_if_fail (E_IS_MSG_COMPOSER_ATTACHMENT_BAR (data)); - - bar = E_MSG_COMPOSER_ATTACHMENT_BAR (data); - remove_selected (bar); -} + EMsgComposerAttachmentBar *bar = data; + remove_selected(bar); +} /* Popup menu handling. */ - -static GnomeUIInfo icon_context_menu_info[] = { - GNOMEUIINFO_ITEM_STOCK (N_("_Remove"), - N_("Remove selected items from the attachment list"), - remove_cb, - GTK_STOCK_REMOVE), - GNOMEUIINFO_MENU_PROPERTIES_ITEM (properties_cb, NULL), - GNOMEUIINFO_END +static EPopupItem emcab_popups[] = { + { E_POPUP_ITEM, "10.attach", N_("_Remove"), emcab_remove, NULL, GTK_STOCK_REMOVE, EM_POPUP_ATTACHMENTS_MANY }, + { E_POPUP_ITEM, "20.attach", N_("_Properties"), emcab_properties, NULL, GTK_STOCK_PROPERTIES, EM_POPUP_ATTACHMENTS_ONE }, + { E_POPUP_BAR, "30.attach.00", NULL, NULL, NULL, NULL, EM_POPUP_ATTACHMENTS_MANY|EM_POPUP_ATTACHMENTS_ONE }, + { E_POPUP_ITEM, "30.attach.01", N_("_Add attachment..."), emcab_add, NULL, GTK_STOCK_ADD, 0 }, }; -#define ICON_CONTEXT_MENU_PROPERTIES (1) - -static GtkWidget * -get_icon_context_menu (EMsgComposerAttachmentBar *bar) +static void +emcab_popup_position(GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) { - EMsgComposerAttachmentBarPrivate *priv; + EMsgComposerAttachmentBar *bar = user_data; + GnomeIconList *icon_list = user_data; + GList *selection; + GnomeCanvasPixbuf *image; - priv = bar->priv; - if (priv->icon_context_menu == NULL) - priv->icon_context_menu = gnome_popup_menu_new (icon_context_menu_info); + gdk_window_get_origin (((GtkWidget*) bar)->window, x, y); - return priv->icon_context_menu; + selection = gnome_icon_list_get_selection (icon_list); + if (selection == NULL) + return; + + image = gnome_icon_list_get_icon_pixbuf_item (icon_list, (gint)selection->data); + if (image == NULL) + return; + + /* Put menu to the center of icon. */ + *x += (int)(image->item.x1 + image->item.x2) / 2; + *y += (int)(image->item.y1 + image->item.y2) / 2; } static void -popup_icon_context_menu (EMsgComposerAttachmentBar *bar, - gint num, - GdkEventButton *event) +emcab_popups_free(EPopup *ep, GSList *l, void *data) { - GtkWidget *menu; - GnomeIconList *icon_list; - GList *selection; + g_slist_free(l); +} - menu = get_icon_context_menu (bar); - icon_list = GNOME_ICON_LIST (bar); - selection = gnome_icon_list_get_selection (icon_list); +/* if id != -1, then use it as an index for target of the popup */ +static void +emcab_popup(EMsgComposerAttachmentBar *bar, GdkEventButton *event, int id) +{ + GList *p; + GSList *attachments = NULL, *menus = NULL; + int i; + EMPopup *emp; + EMPopupTargetAttachments *t; + GtkMenu *menu; + EMsgComposerAttachment *attachment; - gtk_widget_set_sensitive (icon_context_menu_info[ICON_CONTEXT_MENU_PROPERTIES].widget, g_list_length (selection) == 1); + /* We need to check if there are duplicated index in the return list of + gnome_icon_list_get_selection() because of gnome bugzilla bug #122356. + FIXME in the future. */ - gnome_popup_menu_do_popup (menu, NULL, NULL, event, bar, NULL); -} + if (id == -1 + || (attachment = g_list_nth_data(bar->priv->attachments, id)) == NULL) { + p = gnome_icon_list_get_selection((GnomeIconList *)bar); + for ( ; p != NULL; p = p->next) { + int num = GPOINTER_TO_INT(p->data); + EMsgComposerAttachment *attachment = g_list_nth_data(bar->priv->attachments, num); + + if (attachment && g_slist_find(attachments, attachment) == NULL) { + g_object_ref(attachment); + attachments = g_slist_prepend(attachments, attachment); + } + } + attachments = g_slist_reverse(attachments); + } else { + g_object_ref(attachment); + attachments = g_slist_prepend(attachments, attachment); + } -static GnomeUIInfo context_menu_info[] = { - GNOMEUIINFO_ITEM (N_("Add attachment..."), - N_("Attach a file to the message"), - add_cb, NULL), - GNOMEUIINFO_END -}; + for (i=0;i<sizeof(emcab_popups)/sizeof(emcab_popups[0]);i++) + menus = g_slist_prepend(menus, &emcab_popups[i]); -static GtkWidget * -get_context_menu (EMsgComposerAttachmentBar *bar) -{ - EMsgComposerAttachmentBarPrivate *priv; - - priv = bar->priv; - if (priv->context_menu == NULL) - priv->context_menu = gnome_popup_menu_new (context_menu_info); - - return priv->context_menu; -} + emp = em_popup_new("com.novell.evolution.mail.composer.attachmentBar"); + e_popup_add_items((EPopup *)emp, menus, emcab_popups_free, bar); + t = em_popup_target_new_attachments(emp, attachments); + t->target.widget = (GtkWidget *)bar; + menu = e_popup_create_menu_once((EPopup *)emp, (EPopupTarget *)t, t->target.mask, t->target.mask); -static void -popup_context_menu (EMsgComposerAttachmentBar *bar, - GdkEventButton *event) -{ - GtkWidget *menu; - - menu = get_context_menu (bar); - gnome_popup_menu_do_popup (menu, NULL, NULL, event, bar, NULL); + if (event == NULL) + gtk_menu_popup(menu, NULL, NULL, emcab_popup_position, bar, 0, gtk_get_current_event_time()); + else + gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); } - /* GtkObject methods. */ static void @@ -539,91 +540,42 @@ destroy (GtkObject *object) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } - /* GtkWidget methods. */ - -static void -popup_menu_placement_callback (GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) -{ - EMsgComposerAttachmentBar *bar; - GnomeIconList *icon_list; - GList *selection; - GnomeCanvasPixbuf *image; - - bar = E_MSG_COMPOSER_ATTACHMENT_BAR (user_data); - icon_list = GNOME_ICON_LIST (user_data); - - gdk_window_get_origin (((GtkWidget*) bar)->window, x, y); - - selection = gnome_icon_list_get_selection (icon_list); - if (selection == NULL) - return; - - image = gnome_icon_list_get_icon_pixbuf_item (icon_list, (gint)selection->data); - if (image == NULL) - return; - - /* Put menu to the center of icon. */ - *x += (int)(image->item.x1 + image->item.x2) / 2; - *y += (int)(image->item.y1 + image->item.y2) / 2; -} - static gboolean popup_menu_event (GtkWidget *widget) { - EMsgComposerAttachmentBar *bar = E_MSG_COMPOSER_ATTACHMENT_BAR (widget); - GnomeIconList *icon_list = GNOME_ICON_LIST (widget); - GList *selection = gnome_icon_list_get_selection (icon_list); - GtkWidget *menu; - - if (selection == NULL) - menu = get_context_menu (bar); - else - menu = get_icon_context_menu (bar); - - gnome_popup_menu_do_popup (menu, popup_menu_placement_callback, - widget, NULL, widget, NULL); - + emcab_popup((EMsgComposerAttachmentBar *)widget, NULL, -1); return TRUE; } static int -button_press_event (GtkWidget *widget, - GdkEventButton *event) +button_press_event (GtkWidget *widget, GdkEventButton *event) { - EMsgComposerAttachmentBar *bar; - GnomeIconList *icon_list; + EMsgComposerAttachmentBar *bar = (EMsgComposerAttachmentBar *)widget; + GnomeIconList *icon_list = GNOME_ICON_LIST(widget); int icon_number; - bar = E_MSG_COMPOSER_ATTACHMENT_BAR (widget); - icon_list = GNOME_ICON_LIST (widget); - if (event->button != 3) return GTK_WIDGET_CLASS (parent_class)->button_press_event (widget, event); icon_number = gnome_icon_list_get_icon_at (icon_list, event->x, event->y); - if (icon_number >= 0) { + gnome_icon_list_unselect_all(icon_list); gnome_icon_list_select_icon (icon_list, icon_number); - popup_icon_context_menu (bar, icon_number, event); - } else { - popup_context_menu (bar, event); } + + emcab_popup(bar, event, icon_number); return TRUE; } static gint -key_press_event (GtkWidget *widget, GdkEventKey *event) +key_press_event(GtkWidget *widget, GdkEventKey *event) { - EMsgComposerAttachmentBar *bar; - GnomeIconList *icon_list; - - bar = E_MSG_COMPOSER_ATTACHMENT_BAR (widget); - icon_list = GNOME_ICON_LIST (bar); - + EMsgComposerAttachmentBar *bar = E_MSG_COMPOSER_ATTACHMENT_BAR(widget); + if (event->keyval == GDK_Delete) { remove_selected (bar); return TRUE; @@ -675,9 +627,6 @@ init (EMsgComposerAttachmentBar *bar) priv = g_new (EMsgComposerAttachmentBarPrivate, 1); priv->attachments = NULL; - priv->context_menu = NULL; - priv->icon_context_menu = NULL; - priv->num_attachments = 0; bar->priv = priv; diff --git a/composer/e-msg-composer-attachment.c b/composer/e-msg-composer-attachment.c index d28562ee38..f4f173cee2 100644 --- a/composer/e-msg-composer-attachment.c +++ b/composer/e-msg-composer-attachment.c @@ -41,6 +41,7 @@ #include <gtk/gtktogglebutton.h> #include <gtk/gtkdialog.h> #include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnome/gnome-i18n.h> #include "e-util/e-mktemp.h" diff --git a/composer/e-msg-composer-hdrs.c b/composer/e-msg-composer-hdrs.c index 2bfc9ec986..d43dde191b 100644 --- a/composer/e-msg-composer-hdrs.c +++ b/composer/e-msg-composer-hdrs.c @@ -35,6 +35,7 @@ #include <gtk/gtkoptionmenu.h> #include <gtk/gtktooltips.h> #include <libgnomeui/gnome-uidefs.h> +#include <libgnome/gnome-i18n.h> #include "Composer.h" diff --git a/composer/e-msg-composer-select-file.c b/composer/e-msg-composer-select-file.c index 54a48c959c..687660f0de 100644 --- a/composer/e-msg-composer-select-file.c +++ b/composer/e-msg-composer-select-file.c @@ -42,6 +42,7 @@ #endif #include <libgnomeui/gnome-uidefs.h> +#include <libgnome/gnome-i18n.h> #include "e-msg-composer-select-file.h" #include <e-util/e-icon-factory.h> diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 0fb23557d1..2fd958fe01 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -70,6 +70,7 @@ #include <gconf/gconf-client.h> #include <libgnome/gnome-exec.h> +#include <libgnome/gnome-i18n.h> #include <libgnomeui/gnome-uidefs.h> #include <libgnomeui/gnome-window-icon.h> diff --git a/configure.in b/configure.in index 5fcc235292..213664e77e 100644 --- a/configure.in +++ b/configure.in @@ -674,6 +674,16 @@ AC_SUBST(KRB5_LDFLAGS) AC_SUBST(KRB4_CFLAGS) AC_SUBST(KRB4_LDFLAGS) +dnl Mono hooks + +AC_ARG_ENABLE(mono, [ --enable-mono=[yes,no] Add Mono embedded hooks.], + enable_mono="$enableval", enable_mono="no") + +if test "x${enable_mono}" = "xyes"; then + AC_DEFINE(ENABLE_MONO,1,[Define if Mono embedding should be enabled]) + mono_package="mono" +fi +AM_CONDITIONAL(ENABLE_MONO, test x$enable_mono = xyes) dnl ******************************************************************************** dnl security extension support (SSL and S/MIME) @@ -1133,11 +1143,11 @@ EVO_SET_COMPILE_FLAGS(CERT_UI, glib-2.0 gobject-2.0 gthread-2.0, AC_SUBST(CERT_UI_CFLAGS) AC_SUBST(CERT_UI_LIBS) -EVO_SET_COMPILE_FLAGS(E_NAME, libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= GAL_REQUIRED) +EVO_SET_COMPILE_FLAGS(E_NAME, libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= $GAL_REQUIRED) AC_SUBST(E_NAME_CFLAGS) AC_SUBST(E_NAME_LIBS) -EVO_SET_COMPILE_FLAGS(E_UTIL, gthread-2.0 gconf-2.0 libxml-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED libglade-2.0 gal-$GAL_PACKAGE >= GAL_REQUIRED libgnomeui-2.0 libgnome-2.0 libgnomecanvas-2.0 libedataserver-$EDS_PACKAGE >= $EDS_REQUIRED $mozilla_nspr, $THREADS_CFLAGS $MANUAL_NSPR_CFLAGS, $THREADS_LIBS $MANUAL_NSPR_LIBS) +EVO_SET_COMPILE_FLAGS(E_UTIL, gthread-2.0 gconf-2.0 libxml-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED libglade-2.0 gal-$GAL_PACKAGE >= $GAL_REQUIRED libgnomeui-2.0 libgnome-2.0 libgnomecanvas-2.0 libedataserver-$EDS_PACKAGE >= $EDS_REQUIRED $mozilla_nspr $mono_package, $THREADS_CFLAGS $MANUAL_NSPR_CFLAGS, $THREADS_LIBS $MANUAL_NSPR_LIBS) AC_SUBST(E_UTIL_CFLAGS) AC_SUBST(E_UTIL_LIBS) @@ -1159,19 +1169,19 @@ EVO_SET_COMPILE_FLAGS(IMPORTERS, gconf-2.0 libglade-2.0 libbonoboui-2.0 >= $BONO AC_SUBST(IMPORTERS_CFLAGS) AC_SUBST(IMPORTERS_LIBS) -EVO_SET_COMPILE_FLAGS(LIBFILTER, libgnome-2.0 libgnomeui-2.0 libglade-2.0 libxml-2.0 gconf-2.0 gal-$GAL_PACKAGE >= GAL_REQUIRED) +EVO_SET_COMPILE_FLAGS(LIBFILTER, libgnome-2.0 libgnomeui-2.0 libglade-2.0 libxml-2.0 gconf-2.0 gal-$GAL_PACKAGE >= $GAL_REQUIRED) AC_SUBST(LIBFILTER_CFLAGS) AC_SUBST(LIBFILTER_LIBS) dnl --- evolution (shell) flags -EVO_SET_COMPILE_FLAGS(SHELL, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= GAL_REQUIRED libglade-2.0 gnome-vfs-2.0 libgtkhtml-3.1 >= $GTKHTML_REQUIRED) +EVO_SET_COMPILE_FLAGS(SHELL, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= $GAL_REQUIRED libglade-2.0 gnome-vfs-2.0 libgtkhtml-3.1 >= $GTKHTML_REQUIRED) AC_SUBST(SHELL_CFLAGS) AC_SUBST(SHELL_LIBS) dnl --- evolution-addressbook flags -EVOLUTION_ADDRESSBOOK_DEPS="gconf-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED libglade-2.0 gal-$GAL_PACKAGE >= GAL_REQUIRED libgnomeui-2.0 libgnome-2.0 libgnomecanvas-2.0 gnome-vfs-2.0 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libebook-$EDS_PACKAGE >= $EDS_REQUIRED" +EVOLUTION_ADDRESSBOOK_DEPS="gconf-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED libglade-2.0 gal-$GAL_PACKAGE >= $GAL_REQUIRED libgnomeui-2.0 libgnome-2.0 libgnomecanvas-2.0 gnome-vfs-2.0 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libebook-$EDS_PACKAGE >= $EDS_REQUIRED" EVO_SET_COMPILE_FLAGS(EVOLUTION_ADDRESSBOOK, $EVOLUTION_ADDRESSBOOK_DEPS) AC_SUBST(EVOLUTION_ADDRESSBOOK_CFLAGS) @@ -1188,7 +1198,7 @@ EVO_SET_COMPILE_FLAGS(LIBSOUP, libsoup-2.2 >= 2.2.0) AC_SUBST(LIBSOUP_CFLAGS) AC_SUBST(LIBSOUP_LIBS) -EVO_SET_COMPILE_FLAGS(EVOLUTION_CALENDAR, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 gal-$GAL_PACKAGE >= GAL_REQUIRED libglade-2.0 gnome-vfs-2.0 libgnomeprint-2.2 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libebook-$EDS_PACKAGE >= $EDS_REQUIRED libecal-$EDS_PACKAGE >= $EDS_REQUIRED) +EVO_SET_COMPILE_FLAGS(EVOLUTION_CALENDAR, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 gal-$GAL_PACKAGE >= $GAL_REQUIRED libglade-2.0 gnome-vfs-2.0 libgnomeprint-2.2 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libebook-$EDS_PACKAGE >= $EDS_REQUIRED libecal-$EDS_PACKAGE >= $EDS_REQUIRED) AC_SUBST(EVOLUTION_CALENDAR_CFLAGS) AC_SUBST(EVOLUTION_CALENDAR_LIBS) @@ -1200,7 +1210,7 @@ fi dnl --- evolution-mail flags -EVO_SET_COMPILE_FLAGS(EVOLUTION_MAIL, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= GAL_REQUIRED libglade-2.0 gnome-vfs-module-2.0 libgnomeprint-2.2 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libxml-2.0 bonobo-activation-2.0 gthread-2.0 gconf-2.0 $mozilla_nss libebook-$EDS_PACKAGE >= $EDS_REQUIRED) +EVO_SET_COMPILE_FLAGS(EVOLUTION_MAIL, libgnome-2.0 libgnomeui-2.0 libbonoboui-2.0 >= $BONOBOUI_REQUIRED gal-$GAL_PACKAGE >= $GAL_REQUIRED libglade-2.0 gnome-vfs-module-2.0 libgnomeprint-2.2 libgnomeprintui-2.2 libgtkhtml-3.1 >= $GTKHTML_REQUIRED libxml-2.0 bonobo-activation-2.0 gthread-2.0 gconf-2.0 $mozilla_nss libebook-$EDS_PACKAGE >= $EDS_REQUIRED) AC_SUBST(EVOLUTION_MAIL_CFLAGS) AC_SUBST(EVOLUTION_MAIL_LIBS) @@ -1250,6 +1260,9 @@ AC_SUBST(camel_providerdir) componentdir="$privlibdir/components" AC_SUBST(componentdir) +plugindir="$privlibdir/plugins" +AC_SUBST(plugindir) + idldir="$datadir/idl/evolution-$BASE_VERSION" AC_SUBST(idldir) diff --git a/doc/devel/ChangeLog b/doc/devel/ChangeLog index 0f6ba969e2..89afe74c72 100644 --- a/doc/devel/ChangeLog +++ b/doc/devel/ChangeLog @@ -1,3 +1,27 @@ +2004-09-09 Not Zed <NotZed@Ximian.com> + + * evolution-plugin-manual.xml: updates. some not very readable, sigh. + + * images: Added e-popup-merge-[12].pic. + +2004-09-04 Not Zed <NotZed@Ximian.com> + + * images/Makefile: add a temporary makefile to build images from + source pic files. + + * evolution-plugin-manual.xml: updates/rearrangements. + +2004-09-01 Not Zed <NotZed@Ximian.com> + + * images/*: added some image files. + + * evolution-plugin-manual.xml: fix for some api changes. Updates. + +2004-08-25 Not Zed <NotZed@Ximian.com> + + * evolution-plugin-manual.xml: Added the working plugin manual + in-case i crash my bike going to work one day. + 2003-11-20 JP Rosevear <jpr@ximian.com> * Remove dead doc files. diff --git a/doc/devel/evolution-plugin-manual.xml b/doc/devel/evolution-plugin-manual.xml new file mode 100644 index 0000000000..9e3331d74a --- /dev/null +++ b/doc/devel/evolution-plugin-manual.xml @@ -0,0 +1,2915 @@ +<?xml version='1.0'?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" + "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ + +<!ENTITY Evolution "<application>Evolution</application>"> +<!ENTITY GNOME "<application>GNOME</application>"> +<!ENTITY eclipse "<application>Eclipse</application>"> +<!ENTITY Camel "<application>Camel</application>"> +<!ENTITY EPlugin "<application>EPlugin</application>"> +<!ENTITY e-popup-reference SYSTEM "e-popup.xml"> +<!ENTITY e-menu-reference SYSTEM "e-menu.xml"> +<!ENTITY e-config-reference SYSTEM "e-config.xml"> +<!ENTITY e-event-reference SYSTEM "e-event.xml"> +<!ENTITY e-plugin-reference SYSTEM "e-plugin.xml"> + +<!ENTITY em-popup-reference SYSTEM "em-popup.xml"> +<!ENTITY em-format-reference SYSTEM "em-format.xml"> + +]> +<?xml-stylesheet href="sdocbook.css" type="text/css"?> + +<book lang="en"> + <!-- DocBook file was created by LyX 1.3 + See http://www.lyx.org/ for more information --> + <bookinfo> + <title> + &Evolution; Plugin Development Manual + </title> + + <authorgroup> + <corpauthor> + Novell, Inc. + </corpauthor> + <author> + <firstname>Michael</firstname><surname>Zucchi</surname> + </author> + </authorgroup> + + <copyright> + <year>2004</year> + <holder>Novell, Inc.</holder> + </copyright> + + </bookinfo> + + <preface id="preface"> + <title>Preface</title> + + <para> + This document is work-in-progress. Its structure and design is still as + fluid as the underlying strucutre and design of some parts of EPlugin. + There's no guarantee it will be updated at regular intervals, + particularly this version. + </para> + <para> + The API documentation is currently generated using the Linux kernel-doc + script. The stylesheets used to generate the HTML you're seeing seems to + have bugs which duplicates some sections. It is also ugly and difficult + to navigate. + </para> + + <sect1> + <title>Conventions</title> + <para> + The following conventions are used in the manual ... (insert details + here). + </para> + <sect2> + <title>XML Annotation</title> + <para> + XML definitions are annotated with BNF-style markers to indicate + alternative (|), multiples (* or +), and optional (?) items. If no + annotation is present then the item must be present once. + </para> + <variablelist> + <varlistentry> + <!-- is symbol the right one here? --> + <term><symbol>|</symbol></term> + <listitem> + <simpara>Indicates an alternative option. Only one of the items + separated by <symbol>|</symbol> is to be chosen. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><symbol>*</symbol></term> + <listitem> + <simpara>Following an item, <symbol>*</symbol> indicates the item + may occur any number of times, including no times (0 or more + multiple). + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><symbol>+</symbol></term> + <listitem> + <simpara>Following an item, <symbol>+</symbol> indicates the item + must occur at least once, but may occur more than ones (1 or + more multiple). + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><symbol>?</symbol></term> + <listitem> + <simpara>Following an item, <symbol>?</symbol> indicates the item + may occur at most once, if present (0 or 1 times). + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> + + </preface> + + <part id="informational"> + <title> + EPlugin + </title> + <chapter> + <title> + Introduction + </title> + <para> + This book aims to be a comprehensive technical manual for the + development of plugins for &Evolution;, a personal information manager + for &GNOME;. + </para> + <para> + Up-to, and including, &Evolution; version 2.0, &Evolution; contained + limited extensibility interfaces. There were only two ways to extend + &Evolution;; by implementing a new top-level component, or by + implementing a &Camel; provider. When implementing a top-level component, + there was still little integration, and in effect it was merely a more + complex way of writing a separate &GNOME; application. &Camel; providers + were only designed to be e-mail storage backends, so were of limited + use for general extensibility. Despite this, both mechanisms were used + for example for the Exchange Connector, although the system made the + integration clumsy and difficult. + </para> + <para> + This lack of extensibility has severaly stifled external developer + contributions by forcing any extensions to be considered as core + features. &Evolution; being a commercial product, it has tight usability + and quality requirements that limits the ability to experiment with + the core feature set in this way. As a result, very few lines of code + or new features have been implemented by external contributors. + </para> + <para> + One of the major goals for the 2.2 release was to implement an + extensibility system, given the working name of EPlugin, which must + provide a frame-work for both providing extensibility hooks, and for + extending the functionality of &Evolution;. + </para> + <sect1> + <title> + Plugin System + </title> + <para> + Any plugin system will generally have a number of goals: + </para> + <itemizedlist> + <listitem> + <simpara> + Provide a language independent invocation mechanism + </simpara> + </listitem> + <listitem> + <simpara> + Allow extension of parts of the user interface and processing elements + </simpara> + </listitem> + <listitem> + <simpara> + Require minimal extra or foreign code to implement in the core application + </simpara> + </listitem> + <listitem> + <simpara> + Require minimal interface code to implement the extensions + </simpara> + </listitem> + <listitem> + <simpara> + Not to impact performance or increase resource usage unduly + </simpara> + </listitem> + <listitem> + <simpara> + Versioning + </simpara> + </listitem> + <listitem> + <simpara> + Be able to be extended itself fairly easily. + </simpara> + </listitem> + </itemizedlist> + <para> + EPlugin manages to fulfill these goals in most cases. EPlugin isn't a + single object or interface in itself, although there is an object + titled EPlugin, it is a synergistic + <footnote><simpara>I've always wanted to use + <emphasis>synergistic</emphasis> in a sentence since + I read it on the back of the Commodore 64 Users + Guide.</simpara></footnote> + collection of integrated and + continually evolving objects which work together to achieve these + goals (and that will definitly be the end of the MarketSpeak). It + consists of a loader to invoke extension callbacks, hooks to resolve + these callbacks, targets to identify context, and managers which are + used by the core code to provide functionality and merging points for + the extensions. + </para> + <para> + EPlugin's design was inspired and influenced by the &eclipse; + project. It aims at a lower target however, so it was able more easily + implemented in a practical time-frame. + </para> + <para> + &EPlugin; was chosen as an approach to the problem of adding + scriptability to &Evolution;. Instead of just linking to Perl, or + Python, or even Mono by itself an approach was taken which focuses on + the application end of the system. So instead of making every part + of the application export its functionality and have to deal with + whatever script engine is present, EPlugin addresses the hooking part + of the equation in a language-independent manner. It also attemps to + do it in a way which doesn't impact on the application development + either. + </para> + <para> + The EPlugin world is awash with its own language. The next few + sections will introduce the basic plugin nomenclature and high-level + view of this world. + </para> + + </sect1> + + <sect1> + <title> + Loaders + </title> + <para> + The core of EPlugin is a light-weight object loader and callback + invocation system. Because of the varied calling conventions of + different languages, and to reduce the overhead of the plugin system + itself, all callbacks only receive and return a single argument. By + using structures to pass complex arguments, native C plugins require + no extra overhead, and marshalling details are moved into the plugin + implementation itself where required. It also simplifies memory + management issues significantly. For example, the C plugin handler + merely loads a shared library using GModule, and resolves a symbol by + name; and is so all of 50 lines of code, total. The loaders are the + only modules which need to interace with non-native code or + conventions. + </para> + <para> + The other task of the plugin core is to load XML definitions of the + plugins. Extension hooks are registered with the plugin core before + the plugins are scanned, and are automatically instantiated to load + each definition appropriately as they are encountered. + </para> + <para> + At each layer, a level of indirection is used so that new loaders and + new hooks can be added transparently, and extend the plugin + definition freely with any information they require. + </para> + </sect1> + + <sect1> + <title> + Hooks + </title> + <para> + The hooks + <footnote> + <para>A hook is something you can hang your stuff on.</para> + </footnote> + which are registered with the loader provide meta-data for + the management implementation layer for extending it at + run-time. Their primary + functions are to load the detail of the XML plugin definition, map it + to the implementation, and marshal the implementation callbacks to + the common plugin interface. How they do this depends on the + implementation itself, and ranges from registering factory methods to + simply adding the items directly. + </para> + <para> + In most cases the physical object need not be loaded until the + callback is invoked, since the plugin definitions provide enough + contextual information to build the interface or determine when they + need to be invoked. + </para> + </sect1> + + <sect1> + <title> + Managers + </title> + <para> + Managers + <footnote> + <para> + Unlike real managers, these are the ones that do the heavy lifting. + </para> + </footnote> + provide tools for the core code to extend itself at specific + points, and in many cases are the objects used directly in the code + to implement core features. In other cases they simply provide the + hooks with an entry point into &Evolution;. For example, for the main + menu hook, the manager is a thin layer to BonoboUI. On the other + hand, EPopup is a complete implementation of a popup menu management + system which was already used in &Evolution; 2.0. Some managers are + one-off objects used as constructors for other objects, others are + view-dependent, and some are static objects, such as the Event + routers. + </para> + </sect1> + + <sect1> + <title> + Items + </title> + <para> + Each manager uses a number of items to describe the object they + control or create. The items are added to each manager instance from + the plugins or from core code. The items from all of these sources + are then merged together when required and processed accordingly. For + example, menu items are merged into a tree of GtkMenus. Events on the + other hand are simply ordered and then invoked in the order of their + priority. Items are part of the manager implementation, and in + &EPlugin; they are all extensible objects too, which the hooks use to + perform mapping to the plugin. Items may be extended by code hooking + into the implementation, either the plugin hooks, or the core code. + </para> + </sect1> + + <sect1> + <title> + Targets + </title> + <para> + Targets + <footnote><para>Think of a target as the target of + interest.</para></footnote> + are view or component specific context objects. They contain + enough information to be used as stand-alone contexts to implement + callbacks for both core functions and plugin hooks. For example for + the mail view, a select target contains a folder and a list of + selected messages. An attachment (part) target contains the + &Camel; representation of the part and the mime-type for + that part. Targets are part of the manager implementation and are + extended by subclassing the manager. + </para> + </sect1> + </chapter> + + <chapter id="plugin-loaders"> + <title> + Plugin Loaders + </title> + <para> + Plugin loaders implement a hool to a new language, or loading system in the + plugin system. The actual binding of new languages to the plugin system or + other parst of &Evolution;s api's are beyond the scope of this + document, some languages make this easier than others. + </para> + <sect1 id="plugin-loaders-base"> + <title> + Base Plugin + </title> + <para> + The <link linkend="API-struct--EPlugin">EPlugin base class</link> + is an abstract class which provides the basic services for plugin + implementations. The main services are: + <itemizedlist> + <listitem><simpara>Resolve plugin type and instantiate an EPlugin + object to represent and manage it.</simpara></listitem> + <listitem><simpara>Load the base structure of the XML plugin + definition files.</simpara></listitem> + <listitem><simpara>Resolve plugin hook types and instantiate a + EPluginHook to represent and manage it.</simpara></listitem> + <listitem><simpara>Provide a simple, language-independent api for + invoking plugin callbacks</simpara></listitem> + <listitem><simpara>Provide I18N context for plugins.</simpara></listitem> + <listitem><simpara>Some simple static helper methods to simplify each + implementing class.</simpara></listitem> + </itemizedlist> + See the <xref linkend="REF-EPlugin"/> for + these details. + </para> + + <sect2 id="plugin-loaders-definition"> + <title>Definition of a Plugin</title> + <para> + The base plugin XML definition. Subclasses of EPlugin extend this + basic structure with additional parameters or elements as they + require. + </para> + <para> + Note that there may be any number of <sgmltag>e-plugin</sgmltag> + elements in a given plugin file, this may be used to simplify + distribution of plugin packages. + </para> + <programlisting> + <![CDATA[ +<?xml version="1.0"> +<e-plugin-list> + <e-plugin + type="loader type" + domain="translation domain" ? + name="plugin name" + ...> + <description>long description</description> ? + <hook + class="hook class" + ...> + ... + </hook> + + </e-plugin> + +</e-plugin-list>]]></programlisting> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The type name of the plugin loader. Currently <link + linkend="plugin-loaders-lib">shlib</link> and <link + linkend="plugin-loaders-mono">mono</link> are the only + supported values. If no known handler is registered for this + type, the plugin definition is silently ignored. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>domain</parameter></term> + <listitem> + <simpara> + The translation domain for this plugin, as passed to the + <function>dcgettext</function> call of the gettext package. + If not supplied then the default application domain is used + (i.e. "evolution"). This is used to translate + translatable strings for display. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>name</parameter></term> + <listitem> + <simpara> + A short name for the plugin. "Bob's Wonder + Extender" might be suitable. This value will be + translated. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>description</parameter></term> + <listitem> + <simpara> + A longer description of the plugin's purpose. This value will be + translated. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>hook</parameter></term> + <listitem> + <simpara> + This is a list of all of the hooks that this plugin wishes to + hook into. See the <link linkend="plugin-hooks">Plugin + Hooks</link> section for the details of the basic hook + types defined. + </simpara> + <simpara> + The hook <parameter>class</parameter> is resolved using the + registered hook types, and if none can be found, or a version + mismatch occurs, then the hook is silently ignored. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> + + <sect1 id="plugin-loaders-lib"> + <title> + Shared Library Loader + </title> + <para> + The shared library loader <link + linkend="API-struct--EPluginLib">EPluginLib</link> implements a + concrete EPlugin type which loads GNU shared libraries via the + GModule api. It simply resolves symbols directly from the loaded + shared object and invokes them with the same arguments as the + <link linkend="API-e-plugin-invoke">e_plugin_invoke</link> method. + </para> + + <sect2 id="plugin-loaders-lib-definition"> + <title>Definition</title> + <para>The shared library loader only requires one extra parameter in + the base plugin definition. + </para> + <programlisting> + <![CDATA[ +<e-plugin + ... + type="shlib" + location="/full/path/name.so" + ... + <hook class="..."> + ... +</e-plugin>]]></programlisting> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The type name of the shared library plugin is + <constant>shlib</constant>. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>location</parameter></term> + <listitem> + <simpara> + The location parameter contains + the full path-name of a shared object to load. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="plugin-loaders-lib-invocation"> + <title>Invocation</title> + <simplesect> + <title>Function specification + </title> + <para>Where a function spec is required in a plugin + hook definition, it should simply be the full name of an + exported symbol in the shared object. + </para> + </simplesect> + <simplesect> + <title>Callback signature</title> + <funcsynopsis><funcprototype> + <funcdef>void * <function>function</function></funcdef> + <paramdef>EPlugin * <parameter>ep</parameter></paramdef> + <paramdef>void * <parameter>data</parameter></paramdef> + </funcprototype></funcsynopsis> + <variablelist> + <varlistentry> + <term><function>function</function></term> + <listitem> + <simpara> + The callback function. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>ep</parameter></term> + <listitem> + <simpara> + The container EPlugin representing this plugin. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>data</parameter></term> + <listitem> + <simpara> + Hook context data. It is part of the hook's api to specify + the type of this pointer. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><returnvalue>return value</returnvalue></term> + <listitem> + <simpara> + Return data. It is part of the hook's api to specify the + type of this pointer. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </simplesect> + </sect2> + </sect1> + + <sect1 id="plugin-loaders-mono"> + <title> + Mono Assembly Loader + </title> + <para> + The mono assembly loader <link + linkend="API-struct--EPluginMono">EPluginMono</link> implements a + concrete EPlugin type which loads C# assemblies using Mono. Apart + from loading the assembly, it can optionally instantiate a class to + implement the callback or invoke static methods directly. + </para> + + <sect2 id="plugin-loaders-mono-definition"> + <title>Definition</title> + <para>The mono assembly loader needs the name of the assembly and + optionally the name of the class for handling the callbacks. + </para> + <programlisting> + <![CDATA[ +<e-plugin + ... + type="mono" + location="/full/path/name.dll" + handler="PluginClass" ? + ... + <hook class="..."> + ... +</e-plugin>]]></programlisting> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The type name of a mono assembly plugin is + <constant>mono</constant>. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>location</parameter></term> + <listitem> + <simpara> + The location parameter contains + the full path-name of an assembly to load. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>handler</parameter></term> + <listitem> + <simpara> + If supplied, the handler contains the fully qualified name of + the class which handles all callbacks for this plugin. If a + handling class is used, then the function specifications + become relative to this class. + </simpara> + <simpara> + This class will be + instantiated once upon the first callback invocation, and + remain active for the life of the plugin (or application). + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2 id="plugin-loaders-mono-invocation"> + <title>Invocation</title> + <simplesect> + <title>Function specification + </title> + <para>If no <parameter>handler</parameter> class is specified, then + the function specification must match a static method in the + assembly. This is passed to <function>mono_method_desc_new</function> + and <function>mono_method_desc_search_in_image</function>, + typically <function>FunctionName(intptr)</function>. + </para> + <para> + If the handler is specified, then the function specification is + relative to the handler class. This is passed to + <function>mono_method_desc_new</function> and + <function>mono_method_desc_search_in_class</function>, typically + <function>:MethodName(intptr)</function>. + </para> + </simplesect> + <simplesect> + <title>Callback signature</title> + <funcsynopsis><funcprototype> + <funcdef>IntPtr <function>function</function></funcdef> + <paramdef>IntPtr <parameter>data</parameter></paramdef> + </funcprototype></funcsynopsis> + <variablelist> + <varlistentry> + <term><function>function</function></term> + <listitem> + <simpara> + The callback method. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>data</parameter></term> + <listitem> + <simpara> + The hook context data. This is a pointer to unmanaged data, and it is up-to the plugin to interpret this + data right now, although some helper binding classes are + planned. FIXME: hook-up when they and doco are done. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><returnvalue>return value</returnvalue></term> + <listitem> + <simpara> + The callback return data. It is up to the hook's api to + define the type of this pointer. It may be a simple boxed + value type, or a memory pointer allocated in unmanaged memory (e.g. a + GObject handle or a CamelObject cobject value). + </simpara> + </listitem> + </varlistentry> + </variablelist> + </simplesect> + </sect2> + </sect1> + + </chapter> + + <chapter id="plugin-hooks"> + <title> + Plugin Hooks + </title> + <para> + This chapter will introduce the available plugin hook types. A given + plugin can hook into any of these hooks any number of times. Some refer + to specific instances of objects and others are implicitly defined. + </para> + <para> + By design, there is considerable similarity and orthogonality amongst + all of the various hook types and management objects. + </para> + <sect1 id="plugin-hooks-popup"> + <title> + Popup Menus + </title> + <para> + The popup menu hook lets you hook into any of the context menus in + &Evolution;, by name and context. Complex, dynamic, and multi-level + menus are created on the fly by merging the items for a given menu as + it is being shown. Each component provides its own context targets to + self-describe the situation under which the menu is invoked. Plugins + and core code alike are then invoked at the user's direction. The + popup manager and all context data lives as long as the menu and + until a choice is made, simplifying memory management. + </para> + <para> + The menu is merged from multiple plugins and core application code by + using a simple lexiographical sort of an absolute path to the menu + item. This merged list is then scanned and expanded into a tree of + menus. Individual items can be hidden or inactive based on the target + and a simple mask which is defined by the component itself. A rich + collection of menu item types are possible, from simple, to + checkboxes or images. The popup code is simple, and easy to use, and + simplifies the use of popup menu's in the core application anyway, + that they are pluggable is a free-bonus. + </para> + <sect2> + <title>Defining a popup hook</title> + <para> + Not sure if this fits here as such. Probably temporary placeholder. + </para> + <programlisting> + <![CDATA[ +<hook class="com.ximian.evolution.mail.popup:1.0"> + <menu id="menuid" target="targettype"> + <item + type="item | toggle | radio | image | submenu | bar" + active ? + path="foo/bar" + label="menu text" + icon="icon name" ? + visible="target mask" ? + enable="target mask" ? + activate="function spec"/> * + </menu> * +</hook>]]></programlisting> + <!-- this is all too bloody verbose, is there a better way? --> + <para> + <emphasis>Need to define menu tag</emphasis> + </para> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The menu item type. The type maps directly to the + corresponding EPopupItem types. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>active</parameter></term> + <listitem> + <simpara> + If present, then radio or toggle menu items are active when + first shown. After the first instantiation, they will + remember their active state. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>path</parameter></term> + <listitem> + <simpara> + A '/' separated path used to position the item within menu + and in the right submenu. Each menu and plugin should + define how its menu's are layed out so other plugins can + determine what value to use here. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>label</parameter></term> + <listitem> + <simpara> + The text to be displayed on the menu item. This will be + translated based on the plugin translation domain. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>icon</parameter></term> + <listitem> + <simpara> + The name of a gnome-icon-theme standard icon, or the full + path-name of an icon image to use as menu item icon. This + will be blank if not supplied. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>visible</parameter></term> + <listitem> + <simpara> + A comma separated list of mask enumeration values used to + define when this item is shown. What values are valid + depend on the menu hook class of the menu being hooked + onto. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>enable</parameter></term> + <listitem> + <simpara> + A comma separated list of mask enumeration values used to + define when this item is enabled. What values are valid + depend on the menu hook class of the menu being hooked + onto. This is currently unimplemented. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>activate</parameter></term> + <listitem> + <simpara> + A plugin-type specific function specification. This + function will be resolved and called when the menu item is + activated. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + <sect2> + <title>Merging Plugin Items</title> + <para> + A very simple algorithm is used to form the menu by merging the + plugin's menu items with the system menu items for a given menu. + What follows is a simple example of how this is done. It will be + demonstrated using a simplified menu from the message-list, as used + in the &Evolution; Mail component, and a simple plugin which adds a + single menu item and menu separator into the middle of the menu, + when appropriate. + </para> + <para> + When the application wishes to show a specific popup menu, it + creates a new EPopup object with a unique menu id to manage it. It + adds all of the items it wishes to add to the menu (see "Builtin + Items" in the following diagrams). The application then asks for + the menu to be created. The menu building + process adds all of the menu items from all plugins that target + this specific menu into a flat list, discarding those which don't + match the current Target qualifications. The result is then + sorted using a simple ASCII sort, and then a menu built from the + remaining items. This is probably best described by some diagrams. + </para> + <para> + The following two diagrams show how a popup menu is automatically + customised depending on the context. On the left of each diagram + are all of the menu items which apply to the example menu. The + menu label, with the qualifiers listed underneath, with the menu + item path along-side. On the right-hand side of each diagram is + the result of: + </para> + <itemizedlist> + <listitem><simpara>Selecting items based on the target + qualifiers</simpara></listitem> + <listitem><simpara>Sorting the remaining items based on their + path.</simpara></listitem> + <listitem><simpara>Building this sorted list into a + menu.</simpara></listitem> + </itemizedlist> + + <para> + The actual list of target qualifiers are defined by the application + itself. Generally a specific menu will have only one possible + target, and a list of matching target qualifiers. The example + shows how a plugin can insert a menu item anwhere it wishes in the + menu system. Submenus are also supported, and they work in exactly + the same manner, with / characters used to separate submenu paths. + A submenu must sort into the position immediately before the + definition of its items. + </para> + + <figure id="e-popup-merge-1"> + <title>Merging a menu with many items selected.</title> + <mediaobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-1.pic" format="PIC" /> + </imageobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-1.eps" format="EPS"/> + </imageobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-1.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>Showing how a display list of menu items is selected + and then sorted for display.</phrase> + </textobject> + </mediaobject> + </figure> + + <para> + The first diagram shows when the target qualifiers are + <parameter>many</parameter>, and + <parameter>mark_unread</parameter>. The menu items which operate + on only one selected message are not shown. Similarly for those + able to be marked as unread (i.e. they are currently read). + </para> + + <figure id="e-popup-merge-2"> + <title>Merging a menu with one item selected.</title> + <mediaobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-2.pic" format="PIC"/> + </imageobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-2.eps" format="EPS"/> + </imageobject> + <imageobject> + <imagedata fileref="images/e-popup-merge-2.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>Showing how a display list of menu items is selected + and then sorted for display with different qualifiers.</phrase> + </textobject> + </mediaobject> + </figure> + + <para> + This diagram shows when the target qualifiers are + <parameter>one</parameter>, and + <parameter>mark_read</parameter>. The menu items which operate + on only many selected messages are not shown. Similarly for those + able to be marked as read. + </para> + </sect2> + </sect1> + + <sect1 id="plugin-hooks-menu"> + <title> + Main menus + </title> + <para> + The main menu hook lets you hook into various main menus in + &Evolution;, based on the current active view (component). The system + works by piggy-backing on the existing use of the BonoboUI menu + system used by all of the &Evolution; components. Bonobo handles the + menu merging and user input, and the hook resolves the verb being + invoked and redirects it to the plugin. Each view defines a single + target which describes the appropriate context. For the Mail view, + this is the current folder and currently selected message(s). + </para> + <para> + Each view keeps track of its own manager object. When it is + (de)activated, it also (de)activates the management object which + dynamically adds and removes the menu items from the + BonoboUIContainer via a supplied BonoboUI XML definition file + <perhaps it should embed the bonobouixml>. If the target + changes, the view lets the manager know, and it updates the + visibility and sensitivity of objects appropriately, allowing + reasonably dynamic user-interfaces to be managed automatically. The + plugin itself isn't loaded until the menu item in question is invoked + </para> + <para> + Simple menu items and toggle menu items are supported currently. + Also, because actual menu display is driven by BonoboUI, then toolbar + items can also be added using this mechanism. + </para> + <sect2> + <title>Defining a menu hook</title> + <para> + Not sure if this fits here as such. Probably temporary placeholder. + </para> + <programlisting> + <![CDATA[ +<hook class="com.ximian.evolution.mail.bonoboMenu:1.0"> + <menu id="menuid" target="targettype" + <ui file="/path/to/bonobo-ui-menu-definition.xml"> + + <item + type="item | toggle | radio" + active ? + path="/commands/FooBar" + verb="FooBar" + visible="target mask" ? + enable="target mask" ? + activate="function spec"/> * + </menu> * +</hook>]]></programlisting> + <para> + <emphasis>Need to define menu tag</emphasis> + </para> + <variablelist> + <varlistentry> + <term><parameter>ui</parameter></term> + <listitem> + <simpara> + The <parameter>ui</parameter> element contains a filename of the + BonoboUI XML menu definition to load when the view is activated. Any number of + <parameter>ui</parameter> elements may be defined, and they + are all loaded. + </simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The menu item type. The type maps directly to the + corresponding EMenuItem types. + <parameter>radio</parameter> is currently not implemented. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>active</parameter></term> + <listitem> + <simpara> + If present, then radio or toggle menu items are active when + first shown. After the first instantiation, they will + remember their active state. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>path</parameter></term> + <listitem> + <simpara> + The BonoboUI element path corresponding to this menu item. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>verb</parameter></term> + <listitem> + <simpara> + The BonoboUI verb corresponding to the item to be listened to. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>visible</parameter></term> + <listitem> + <simpara> + A comma separated list of mask enumeration values used to + define when this item is shown. What values are valid + depend on the menu hook class of the menu being hooked + onto. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>enable</parameter></term> + <listitem> + <simpara> + A comma separated list of mask enumeration values used to + define when this item is sensitive. What values are valid + depend on the menu hook class of the menu being hooked + onto. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>activate</parameter></term> + <listitem> + <simpara> + A plugin-type specific function specification. This + function will be resolved and called when the menu item is + activated. The funciton's parameters will depend on the type + of menu item being invoked. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + <sect2> + <title>Merging Plugin Items</title> + <para> + Merging is performed by BonoboUI, and the source of the menu data + is defined by the <parameter>ui</parameter> file. + </para> + </sect2> + </sect1> + + <sect1 id="plugin-hooks-config"> + <title> + Configuration Pages and Wizards + </title> + <para> + Configuration pages are somewhat more complex than any of the other + types of hookable object. This is reflected in the complexity of the + items and callbacks involved. + </para> + <para> + Essentially, the EConfig object is used in combination to both + instrument existing windows and building new content. Each + configuration window comprises of several basic elements with some + minor variations allowed. It consists of a number of pages in a + specific order, each containing a number of titled sections in a specific + order, each containing a number of items. The variations are that + the top-level widget may be a GtkNotebook or a GnomeDruid; and each + section may instrument a GtkBox, or a GtkTable. The definition of + the available hooks will define what form they take. + </para> + <para> + The EConfig manager uses the description of all the items supplied to + it to build the complete window. It can also drive various aspects + of the UI, such as navigating through a druid or handling + instant-apply vs. modify-and-save dialogues. + </para> + <figure id="e-config-flow"> + <title>Event and Data Flow in EMConfig</title> + <mediaobject> + <imageobject> + <imagedata fileref="images/e-config-flow.pic" format="PIC"/> + </imageobject> + <imageobject> + <imagedata fileref="images/e-config-flow.eps" format="EPS"/> + </imageobject> + <imageobject> + <imagedata fileref="images/e-config-flow.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>The flow of information and control signals in the + configuration management object.</phrase> + </textobject> + </mediaobject> + </figure> + + <sect2> + <title>Defining a configuration page hook</title> + <para> + Not sure if this fits here as such. Probably temporary placeholder. + </para> + <programlisting> + <![CDATA[ +<hook class="com.ximian.evolution.mail.bonoboMenu:1.0"> + <group + id="window id" + target="targettype" + commit="function spec"? + abort="function spec"?> + <item + type="book | druid | page | page_start | page_finish | section | section_table | item" + path="/absolute/path" + label="name" | factory="function spec" + /> * + </menu> * +</hook>]]></programlisting> + <para> + <emphasis>Need to define group tag</emphasis> + </para> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The menu item type. The type maps directly to the + corresponding EConfigItem types. Only one of + <parameter>book</parameter> and <parameter>druid</parameter> + may be supplied for the entire configuration page, and this + will usually already be defined by the application. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>path</parameter></term> + <listitem> + <simpara> + The path to the configuration item in question. This is a + simple string that when sorted using an ASCII sort will place + the items in the right order. That is, sections before items + before pages before the root object. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>label</parameter></term> + <listitem> + <simpara> + The textual label of this item. This may only be supplied + for the section and page types. For sections it will be the + section frame text. For pages this will be the druid page + title or the notebook tab text. If a + <parameter>factory</parameter>is supplied then this value is + not used. This will be translated based on the plugin + translation domain. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>factory</parameter></term> + <listitem> + <simpara> + If supplied, the factory method used to create the GtkWidget + elements for this configuration item. Factories may be + supplied for any of the item types. If no + <parameter>label</parameter> is set then the + <parameter>factory</parameter> must be set. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + <sect2> + <title>Generating Configuration Pages</title> + <para> + Configuration items essentially spam 3 dimensions, but are + merged in a similar fashion to the way Popup items are merged. The + main difference is that there are no target qualifiers used to + select which items are shown, it is up to the item factory to + either create or not create the item as it sees fit. The EConfig + manager takes care of the rest, including removing un-used sections + or pages. + </para> + <para> + All items for a given configuration screen are converted into a + list and sorted based on the <parameter>path</parameter>. The + configuration builder then goes through each item, creating + container widgets or calling factories as required. If a given + page or section is empty, then it is removed automatically. This + process isn't only a one-off process. For certain complex + configuration screens, items or even pages and sections need to be + dynamic based on a previous setting. EConfig supports this mode of + operation too, in which case it re-builds the configuration screen + the same way, and automatically destroys the old widgets + <footnote><simpara>In most cases, in some cases additional manual + processing is required in the factory + callback.</simpara></footnote> and even re-orders pages and + sections where appropriate to make the user-interface consistent. + </para> + <para> + The following few examples some of the flexibility of the EConfig + system. + </para> + <figure id="e-config-build-1"> + <title>The application defined, unaltered configuration page.</title> + <mediaobject> + <!-- do we need to build our own eps for the image? --> + <imageobject> + <imagedata fileref="images/e-config-build-1.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>Shows the original HTML Mail settings page.</phrase> + </textobject> + </mediaobject> + </figure> + <para> + First we have the original configuration window. This is defined + by the application, the application uses EConfig to build this + window, and in the process EConfig instruments the sections that + the application defines. This allows plugins to add new + pages/sections/items anywhere on the page - to a granularity as + defined by the application. For example the application may at + minimum merely define the top-level notebook or druid object and a + number of pages. When the pages are created the application could + add as much content as it wants, which would still allow plugins to + extend the user interface, but only by adding options to the end of + each page. At the other end of the scale the application could + enumerate every single item (i.e. row) in every section on every + page, allowing plugins to put new items anywhere in the display. + </para> + <figure id="e-config-build-2"> + <title>A plugin adding a new section to an existing page.</title> + <mediaobject> + <!-- do we need to build our own eps for the image? --> + <imageobject> + <imagedata fileref="images/e-config-build-2.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>Shows the HTML Mail settings page with a new section + and item added by a plugin.</phrase> + </textobject> + </mediaobject> + </figure> + <para> + In this case the plugin has merely added a new section on the + bottom of the HTML Mail settings page. When the factory is called + the plugin has a parent GtkTable (in this case, it could be a VBox) + and borderless frame already defined, and it just has to + instantiate its own control widgets, add them to the table, and + return one of the widgets. The returned widget is used later if + the window needs to be reconfigured, although this particular configuration + page is static so it isn't needed. + </para> + <figure id="e-config-build-3"> + <title>A plugin inserting a new page for its settings.</title> + <mediaobject> + <!-- do we need to build our own eps for the image? --> + <imageobject> + <imagedata fileref="images/e-config-build-3.png" format="PNG"/> + </imageobject> + <textobject> + <phrase>Shows the plugin adding a new page for its setting as + an alternative.</phrase> + </textobject> + </mediaobject> + </figure> + <para> + And finally we have exactly the same plugin, which has exactly the + same code. But a small change to the plugin definition allows the + plugin to add an arbitrary new page (in an arbitrary position) into + the whole window. If this was a druid, then new druid pages can + also be inserted at arbitrary locations, and page navigation (in a + strictly linear manner) is automatically controlled by EConfig as + per <xref linkend="e-config-flow"/>. + </para> + <para> + In practice, EConfig provides more than it takes the application to + use - generally little or no extra application code is required to + use it. It also + <footnote><simpara>Or it will - the code needs some + tweaking.</simpara></footnote> enforces and simplifies HIG + compliance. And as a side-benefit to the application it + transparently provides extension hooks for + external code to provide a seamlessly integrated user experience. + </para> + </sect2> + </sect1> + + <sect1 id="plugin-hooks-event"> + <title> + Events + </title> + <para> + No extensibility framework would be complete without an event + system. Events are used to reflect changes in internal state of the + application, and track actions by the user. They can contain any + information and additionally can be filtered based on the information + itself. Special targets are used, as in the other plugin hooks, to + hold this information. + </para> + <para> + Event managers are defined to contain the different event types that + a given component can export. Only one event manager object is + instantiated for each component, and each plugin listening to events + from that component are registered on that event manager directly. + </para> + <para> + Events handlers have priorities, and can swallow events, allowing + some level of complexity of event routing. This feature might not + prove useful and may be removed in the future if it isn't. + </para> + <sect2> + <title>Defining an event hook</title> + <para> + Not sure if this fits here as such. Probably temporary placeholder. + </para> + + <programlisting> + <![CDATA[ +<hook class="com.ximian.evolution.mail.events:1.0"> + <event + type="pass | sink" + priority="signed integer" + id="event name" + enable="target mask" ? + handle="function spec"/> * + </event> * +</hook>]]></programlisting> + <variablelist> + <varlistentry> + <term><parameter>type</parameter></term> + <listitem> + <simpara> + The event listener type. The type maps directly to the corresponding + corresponding EEventItem types. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>priority</parameter></term> + <listitem> + <simpara> + A signed integer specifying the priority of this event + listener. 0 (zero) should be used normally, although positive + and negative integers in the range -128 to 127 may aslo be + used. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>id</parameter></term> + <listitem> + <simpara> + The name of the event to listen to. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>enable</parameter></term> + <listitem> + <simpara> + A comma separated list of mask enumeration values used to + qualify when this event listener is invoked. What values are valid + depend on the event hook class. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>handle</parameter></term> + <listitem> + <simpara> + A plugin-type specific function specification. This + function will be resolved and called when an event is routed to + this listener. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + </sect1> + + <sect1 id="plugin-hooks-format"> + <title> + Mail Formatter + </title> + <para> + The mail formatter plugin will invoke plugin code to format any part + of an email based on mime-type. There are several formatters used + internally by the mailer for different contexts, and each can be + hooked into separately, providing extensible mail formatting for + everything from the primary mail display, to printing, to reply + quoting and more. If you are implementing a handler for a given + mime-type, each formatter appropriate for the data-type should be + hooked into, so that it displays properly in all contexts. + </para> + <para> + Since the management object in this case is the same formatting + object as used by the core mail display engine, a plugin may override + or reimplement complete new functionality seamlessly. + </para> + <para> + This plugin hook isn't strictly part of the core functionality as it + is provided only by the mail component. It however demonstrates that the + plugin system is extensible itself. + </para> + <sect2> + <title>Defining a formatter hook</title> + <para> + Not sure if this fits here as such. Probably temporary placeholder. + </para> + + <programlisting> + <![CDATA[ +<hook class="com.novell.evolution.mail.format:1.0"> + <group id="formatter type"> + <item + flags="handler flags" + mime_type="major/minor" + format="function spec"/> + + </group> + +</hook>]]></programlisting> + <variablelist> + <varlistentry> + <term><parameter>id</parameter></term> + <listitem> + <simpara> + The actual formatter this applies to. e.g. EMFormat for the + base formatter class, EMFormatHTML for HTML output to a + GtkHTML object, etc. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>flags</parameter></term> + <listitem> + <simpara> + Flags to define whether this is an attachment or inline + content. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>id</parameter></term> + <listitem> + <simpara> + The name of the event to listen to. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>mime_type</parameter></term> + <listitem> + <simpara> + The type of object this handler formats. + </simpara> + </listitem> + </varlistentry> + <varlistentry> + <term><parameter>format</parameter></term> + <listitem> + <simpara> + A plugin-type specific function specification. This + function will be invoked to format objects of the specified + <parameter>mime_type</parameter>. + </simpara> + </listitem> + </varlistentry> + </variablelist> + </sect2> + + <sect2> + <title>The formatting process</title> + <para> + The formatting process is driven by the <link + linkend="API-struct--EMFormat">EMFormat</link> object, although + there are different subclasses of this object used for different + purposes. These behave quite differently so each must be explained + separately. There is the basic formatter type which converts a + CamelMimeMessage into a stream of data, and there is a HTML + <link linkend="API-struct--EMFormatHTML">formatter type</link> + which uses a GtkHTML object to parse the content + and may request further information required to complete the + formatting. + </para> + <para> + A basic formatter goes through the following steps: + </para> + <orderedlist> + <listitem> + <simpara>Outputs pre-amble information. e.g. Flag-For-Followup + status.</simpara> + </listitem> + <listitem> + <simpara>Invokes <methodname>format_message</methodname> to begin the message + formatting. <methodname>format_message</methodname> displays the message header, then + looks up the content object.</simpara> + </listitem> + <listitem> + <simpara>Using the mime-type of the content object (whether + supplied or calculated), a handler is looked up from a + per-class table to process the type.</simpara> + </listitem> + <listitem> + <simpara>If no handler exists, then the data is formatted as an + attachment.</simpara> + </listitem> + <listitem> + <simpara>If a handler exists, then it is invoked to display that + type. Depending on whether the data is to be displayed + 'inline' or not, the data may also get an attachment expander + and button.</simpara> + </listitem> + <listitem> + <simpara>The handler transforms the part's data, if need be, and + writes the appropriate format output to a stream.</simpara> + </listitem> + </orderedlist> + <para> + For conglomerate types, the formatting process is continued + recursively, until all parts have been displayed, as appropriate. + </para> + <para> + A HTML formatter goes through the same basic steps, but has + additional features and requirements. It uses multiple + threads. At least one other thread is used for all of the + Camel message content operations since some of them may block on + remote I/O. This also simplifies cancellation processing. Also, + because it has access to a full HTML rendering object, references + to embedded content (images, buttons, etc.) are also processed. + </para> + <para> + Most format handlers don't need to know about all the fiddly + details however. If they are just outputting HTML content with no + out of band references, they work identical to the basic format + handlers with the exception they cannot call any Gtk GUI code + because of threading issues. This can still be done by using an + IFRAME. If they want to embed an icon or other image, + they simply need to insert the HTML IMG tag reference in their + format handler, and setup a callback to handle it when GtkHTML + requests it. EMFormat has some helper classes to make this + only a few lines of code, including generation of the IMG SRC URL. + IFRAMEs work identically to IMG tags, and similar process is + involved with embedding custom widgets using the OBJECT tag. + EMFormatHTML takes care of calling the right callbacks for the + right embedded reference from the right thread. + </para> + <para> + Since format handlers are chained off a given type, then a plugin + can also inherit formatting behaviour as well as override it. This + gives much greater flexibility since the plugin need only implement + its behaviour in specific situations. e.g. an OpenPGP message + handler could fall-back to the normal text-formatter if it doesn't + detect the ASCII armour in a text/plain part. Or another handler + may disable itself based on configuration or state. + </para> + <para> + All format handlers for all types must also be fully re-entrant + code (more or less write-once global and static variables) if + they call any other formatting functions. + </para> + </sect2> + </sect1> + </chapter> + </part> + + <part> + <title> + &Evolution; Hook Points. + </title> + <partintro> + <para> + This section enumerates all of the published hook points and target + types available in each component in &Evolution;. + </para> + + <simplesect> + <title>Table Format</title> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody> + <row> + <entry>Name</entry> + <entry>The hook point id.</entry> + </row> + <row> + <entry>Target</entry> + <entry>The target which this hook uses for its context data. + Targets are described in a following section. + </entry> + </row> + <row> + <entry>Items</entry> + <entry>If appropriate and defined, specifies identifying path + names of items which make up the hook. e.g. popup menu + items, and configuration pages. These item specifications + allow the plugin writer to position their items + appropriately. + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </simplesect> + + </partintro> + <chapter id="mail-hooks"> + <title> + Mail Hooks + </title> + <para> + <emphasis> + Need to find out the right docbook to mark-up most of this + text. + </emphasis> + </para> + + <sect1 id="mail-hooks-popup"> + <title>Popup menus</title> + + <para> + The mail popup menu class is + <interfacename>com.ximian.evolution.mail.popup:1.0</interfacename>. + </para> + <para> + The plugin callback data will be the target matching the plugin + menu itself, and the callback returns no value. + </para> + + <sect2> + <title>Folder Tree Context Menu</title> + <para> + This is the context menu shown on the folder tree. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.ximian.mail.storageset.popup.select</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-popup-EMPopupTargetFolder">EMPopupTargetFolder</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Message List Context Menu</title> + <para> + This is the context menu shown on the message list. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.ximian.mail.folderview.popup.select</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-popup-EMPopupTargetSelect">EMPopupTargetSelect</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Inline URI Context Menu</title> + <para> + This is the context menu shown when clicking on inline URIs, + including addresses or normal HTML links that are displayed inside + the message view. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.ximian.mail.folderview.popup.uri</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry><link + linkend="mail-hooks-popup-EMPopupTargetURI">EMPopupTargetURI</link></entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Inline Content and Attachment Context Menu</title> + <para> + This context menu is shown when right-clicking on inline images, or + when clicking on the attachment expander button. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.ximian.mail.formathtmldisplay.popup.part</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry><link + linkend="mail-hooks-popup-EMPopupTargetPart">EMPopupTargetPart</link></entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Composer Attachment Bar Context Menu</title> + <para> + This context menu is displayed when the user brings up a context + menu on the attachment bar displayed in the composer. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.composer.attachmentBar</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry><link + linkend="mail-hooks-popup-EMPopupTargetAttachments">EMPopupTargetAttachments</link></entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>one</constant> = <constant>EM_POPUP_ATTACHMENTS_ONE</constant></member> + <member><constant>many</constant> = <constant>EM_POPUP_ATTACHMENTS_MANY</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Internal popup menus</title> + <para> + The following popup menus are defined, but they are used with no + target, and so provide no useful context if they were to be hooked + onto. + </para> + <para> + <interfacename>com.ximian.mail.messagelist.popup.drop</interfacename> + is used for the ASK drop type on the message list. + </para> + <para> + <interfacename>com.ximian.mail.storageset.popup.drop</interfacename> + is used for the ASK drop type on the folder tree. + </para> + </sect2> + + <sect2> + <title>Mail Popup Targets</title> + + <para> + <emphasis> + Not sure if this needs to explain the qualifier meanings, or + leave it to the in-line comment stuff in the enumeration + definition. Maybe it just needs a direct link to the + enumeration. + </emphasis> + </para> + + <sect3 id="mail-hooks-popup-EMPopupTargetFolder"> + <title>Folder Target</title> + + <para> + This target is used to define actions on a folder context. + Normally associated with the folder tree. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>folder</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMPopupTargetFolder">EMPopupTargetFolder</link> + </entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>folder</constant> = <constant>EM_POPUP_FOLDER_FOLDER</constant></member> + <member><constant>store</constant> = <constant>EM_POPUP_FOLDER_STORE</constant></member> + <member><constant>inferiors</constant> = <constant>EM_POPUP_FOLDER_INFERIORS</constant></member> + <member><constant>delete</constant> = <constant>EM_POPUP_FOLDER_DELETE</constant></member> + <member><constant>select</constant> = <constant>EM_POPUP_FOLDER_SELECT</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-popup-EMPopupTargetSelect"> + <title>Selection Target</title> + + <para>This target is used to define context for actions associated + with a selection of mail messages from a specific folder.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>select</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMPopupTargetSelect">EMPopupTargetSelect</link> + </entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>one</constant> = <constant>EM_POPUP_SELECT_ONE</constant></member> + <member><constant>many</constant> = <constant>EM_POPUP_SELECT_MANY</constant></member> + <member><constant>mark_read</constant> = <constant>EM_POPUP_SELECT_MARK_READ</constant></member> + <member><constant>mark_unread</constant> = <constant>EM_POPUP_SELECT_MARK_UNREAD</constant></member> + <member><constant>delete</constant> = <constant>EM_POPUP_SELECT_DELETE</constant></member> + <member><constant>undelete</constant> = <constant>EM_POPUP_SELECT_UNDELETE</constant></member> + <member><constant>mailing_list</constant> = <constant>EM_POPUP_SELECT_MAILING_LIST</constant></member> + <member><constant>resend</constant> = <constant>EM_POPUP_SELECT_EDIT</constant></member> + <member><constant>mark_important</constant> = <constant>EM_POPUP_SELECT_MARK_IMPORTANT</constant></member> + <member><constant>mark_unimportant</constant> = <constant>EM_POPUP_SELECT_MARK_UNIMPORTANT</constant></member> + <member><constant>flag_followup</constant> = <constant>EM_POPUP_SELECT_FLAG_FOLLOWUP</constant></member> + <member><constant>flag_completed</constant> = <constant>EM_POPUP_SELECT_FLAG_COMPLETED</constant></member> + <member><constant>flag_clear</constant> = <constant>EM_POPUP_SELECT_FLAG_CLEAR</constant></member> + <member><constant>add_sender</constant> = <constant>EM_POPUP_SELECT_ADD_SENDER</constant></member> + <member><constant>mark_junk</constant> = <constant>EM_POPUP_SELECT_MARK_JUNK</constant></member> + <member><constant>mark_nojunk</constant> = <constant>EM_POPUP_SELECT_MARK_NOJUNK</constant></member> + <member><constant>folder</constant> = <constant>EM_POPUP_SELECT_FOLDER</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-popup-EMPopupTargetURI"> + <title>URI Target</title> + + <para>This target defines context for operations on a URI, normally + displayed inline somewhere in the message view.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>uri</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry><link + linkend="API-struct--EMPopupTargetURI">EMPopupTargetURI</link></entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>http</constant> = <constant>EM_POPUP_URI_HTTP</constant></member> + <member><constant>mailto</constant> = <constant>EM_POPUP_URI_MAILTO</constant></member> + <member><constant>notmailto</constant> = <constant>EM_POPUP_URI_NOT_MAILTO</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-popup-EMPopupTargetPart"> + <title>Message Part Target</title> + + <para>This target defines context for operations on messages, or + individual message parts. The same target is used for inline + images or other content which can be encapsulated in a MIME part + (i.e. anything).</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>part</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry><link + linkend="API-struct--EMPopupTargetPart">EMPopupTargetPart</link></entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>message</constant> = <constant>EM_POPUP_PART_MESSAGE</constant></member> + <member><constant>image</constant> = <constant>EM_POPUP_PART_IMAGE</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-popup-EMPopupTargetAttachments"> + <title>Attachments Target</title> + + <para>This target is used to define context for operations on the + mail composer attachment bar.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>attachments</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry><link + linkend="API-struct--EMPopupTargetAttachments">EMPopupTargetAttachments</link></entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>one</constant> = <constant>EM_POPUP_ATTACHMENTS_ONE</constant></member> + <member><constant>many</constant> = <constant>EM_POPUP_ATTACHMENTS_MANY</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + </sect2> + </sect1> + + <sect1 id="mail-hooks-menu"> + <title>Main menus</title> + + <para> + The mail popup menu class is + <interfacename>com.ximian.evolution.mail.bonobomenu:1.0</interfacename>. + </para> + <para> + The plugin callback data will be the target matching the plugin + menu itself, and the callback returns no value. + </para> + + <sect2> + <title>Main Mail Menu</title> + + <para> + This is the main mail view embedded in the &Evolution; Shell. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.browser</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-menu-EMMenuTargetSelect">EMMenuTargetSelect</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Standalone Message View Menu</title> + + <para> + This is the popup mail-message view. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.messagebrowser</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-menu-EMMenuTargetSelect">EMMenuTargetSelect</link>, + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Composer Menu</title> + <para> + This is the mail message composer. This is currently not implemented. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.composer</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry>Undefined</entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry>Undefined</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Mail Menu Targets</title> + + <sect3 id="mail-hooks-menu-EMMenuTargetSelect"> + <title>Message Selection Target</title> + + <para>This target is used to define context for operations on a + selection of messages in the view's message list.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>select</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMMenuTargeSelect">EMMenuTargetSelect</link> + </entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry> + <simplelist> + <member><constant>one</constant> = <constant>EM_MENU_SELECT_ONE</constant></member> + <member><constant>many</constant> = <constant>EM_MENU_SELECT_MANY</constant></member> + <member><constant>mark_read</constant> = <constant>EM_MENU_SELECT_MARK_READ</constant></member> + <member><constant>mark_unread</constant> = <constant>EM_MENU_SELECT_MARK_UNREAD</constant></member> + <member><constant>delete</constant> = <constant>EM_MENU_SELECT_DELETE</constant></member> + <member><constant>undelete</constant> = <constant>EM_MENU_SELECT_UNDELETE</constant></member> + <member><constant>mailing_list</constant> = <constant>EM_MENU_SELECT_MAILING_LIST</constant></member> + <member><constant>resend</constant> = <constant>EM_MENU_SELECT_EDIT</constant></member> + <member><constant>mark_important</constant> = <constant>EM_MENU_SELECT_MARK_IMPORTANT</constant></member> + <member><constant>mark_unimportant</constant> = <constant>EM_MENU_SELECT_MARK_UNIMPORTANT</constant></member> + <member><constant>flag_followup</constant> = <constant>EM_MENU_SELECT_FLAG_FOLLOWUP</constant></member> + <member><constant>flag_completed</constant> = <constant>EM_MENU_SELECT_FLAG_COMPLETED</constant></member> + <member><constant>flag_clear</constant> = <constant>EM_MENU_SELECT_FLAG_CLEAR</constant></member> + <member><constant>add_sender</constant> = <constant>EM_MENU_SELECT_ADD_SENDER</constant></member> + <member><constant>mark_junk</constant> = <constant>EM_MENU_SELECT_MARK_JUNK</constant></member> + <member><constant>mark_nojunk</constant> = <constant>EM_MENU_SELECT_MARK_NOJUNK</constant></member> + <member><constant>folder</constant> = <constant>EM_MENU_SELECT_FOLDER</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + </sect2> + </sect1> + + <sect1 id="mail-hooks-config"> + <title>Config Windows and Druids</title> + + <para> + The mail config class is + <interfacename>com.novell.evolution.mail.config:1.0</interfacename>. + </para> + + <sect2> + <title>Account Editor Druid</title> + <para> + This is the GnomeDruid which is shown when you create a new mail account. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.config.accountDruid</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-config-EMConfigTargetAccount">EMConfigTargetAccount</link> + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Account Editor Window</title> + <para> + This is the editor notebook which is shown when you edit an + existing mail account. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.config.accountEditor</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="API-struct--EMConfigTargetAccount">EMConfigTargetAccount</link>, plugin target is "<constant>account</constant>". + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Composer Preferences</title> + <para> + This is the notebook of configuration items for the composer which + are accessed via the main settings window under the Composer tab. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.composerPrefs</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-config-EMConfigTargetPrefs">EMConfigTargetPrefs</link> + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Mail Preferences</title> + <para> + This is the notebook of configuration items for the mailer which + are accessed via the main settings window under the Mail Settings tab. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.prefs</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-config-EMConfigTargetPrefs">EMConfigTargetPrefs</link> + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Folder Properties</title> + <para> + This is the notebook of configuration items for a given mail folder + which is accessed via the Folder Properties item in the main menu item or + folder-tree context menu. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><interfacename>com.novell.evolution.mail.folderConfig</interfacename></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-config-EMConfigTargetFolder">EMConfigTargetFolder</link> + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Mail Config Targets</title> + + <sect3 id="mail-hooks-config-EMConfigTargetAccount"> + <title>Account Target</title> + + <para>The account target is used for configuring accounts, and so + has a pointer to the EAccount being configured. This is a copy + of the actual account object, and is copied to the original once + the data is ready to commit.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>account</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMConfigTargetAccount">EMConfigTargetAccount</link> + </entry> + </row> + <row> + <entry>Items</entry> + <entry>Define some of the items available and where they fit + in the gui</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-config-EMConfigTargetPrefs"> + <title>Preferences Target</title> + + <para>The preferences target is used for global preferences. As + such it just contains a pointer to the global configuration store + - a GConfClient.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>prefs</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMConfigTargetPrefs">EMConfigTargetPrefs</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + <sect3 id="mail-hooks-config-EMConfigTargetFolder"> + <title>Folder Target</title> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>folder</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMConfigTargetFolder">EMConfigTargetFolder</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + + </sect2> + </sect1> + + <sect1 id="mail-hooks-event"> + <title>Events</title> + + <para> + The mail event class is + <interfacename>com.ximian.evolution.mail.events:1.0</interfacename>. + </para> + + <sect2> + <title>Folder Changed Event</title> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>folder.changed</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-event-EMEventTargetFolder">EMEventTargetFolder</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>Mail Event Targets</title> + + <sect3 id="mail-hooks-event-EMEventTargetFolder"> + <title>Folder Target</title> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>folder</constant></entry> + </row> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMEventTargetFolder">EMEventTargetFolder</link> + </entry> + </row> + <row> + <entry>Qualifiers</entry> + <entry>List qualifiers</entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect3> + </sect2> + </sect1> + + <sect1 id="mail-hooks-format"> + <title>Formatters</title> + + <para> + The mail formatter hook class is + <interfacename>com.novell.evolution.mail.format:1.0</interfacename>. + </para> + + <sect2> + <title>Base Formatter</title> + + <para> + The EMFormat class is the base class for all formatting types. + It should only be used to define compound and complex types which + do not rely on outputting any textual information, or rely on any + screen or print output differences. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>EMFormat</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-format-EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2> + <title>HTML Formatter</title> + + <para> + The EMFormatHTML class is the base class for most formatting types + which generate HTML output. It renders output to a GtkHTML + object. It uses a fairly complex multi-thread approach to the + formatting to ensure the user-interface is not blocked for + processing. GtkHTML is used in a limited way by this class for + HTML parsing and resolution of embedded objects. Embedded objects + and Widgets may not be used from formatters which hook onto this + entry point. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>EMFormatHTML</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-format-EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para> + <emphasis>This section needs a huge amount of + explanation, and/or more detail needs to be added to another + section about the formatter class</emphasis> + </para> + </sect2> + + <sect2> + <title>HTML Display Formatter</title> + + <para> + The EMFormatHTMLDisplay class is a subclass of EMFormatHTML, and is + used as a mail display widget. As such, it has access to all of + the facilities of GtkHTML, such as embedded widgets. Like the + EMFormatHTML class, this uses a complex multi-thread architecture. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>EMFormatHTMLDisplay</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-format-EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para> + <emphasis>This section needs a huge amount of + explanation, and/or more detail needs to be added to another + section about the formatter class</emphasis> + </para> + </sect2> + + <sect2> + <title>HTML Print Formatter</title> + + <para> + The EMFormatHTMLPrint class is a subclass of EMFormatHTML, and is + used as a mail printing widget. It cannot access embedded + widgets. For most purposes you would normally only connect to the + EMFormatHTML hook, and generate generic HTML output which could be + printed or shown on-screen if it isn't overriden by the display + formatter. + </para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>EMFormatHTMLPrint</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-format-EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + <para> + <emphasis>This section needs a huge amount of + explanation, and/or more detail needs to be added to another + section about the formatter class</emphasis> + </para> + </sect2> + + <sect2> + <title>Mail Quote Formatter</title> + <para> + The EMFormatQuote class is a subclass of EMFormat, and is + used as generator for quoted mail content and for + inline-forwarding. This formatter converts message objects into + a pure HTML stream, which is not parsed directly, but normally fed + to the message composer. + </para> + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Name</entry> + <entry><constant>EMFormatQuote</constant></entry> + </row> + <row> + <entry>Target</entry> + <entry> + <link + linkend="mail-hooks-EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + </sect2> + + <sect2 id="mail-hooks-format-EMFormatHookTarget"> + <title>Mail Formatter Targets</title> + + <para>There is only one target for all mail formatters, and it is + implied automatically for all formatter hooks.</para> + + <informaltable> + <tgroup cols="2"> + <colspec colnum="1" colname="field" colwidth="1*"/> + <colspec colnum="2" colname="value" colwidth="4*"/> + <tbody valign="top"> + <row> + <entry>Structure</entry> + <entry> + <link + linkend="API-struct--EMFormatHookTarget">EMFormatHookTarget</link> + </entry> + </row> + <row> + <entry>Flags</entry> + <entry> + <simplelist> + <member><constant>inline</constant> = <constant>EM_FORMAT_HANDLER_INLINE</constant></member> + <member><constant>inline_disposition</constant> = + <constant>EM_FORMAT_HANDLER_INLINE_DISPOSITION</constant></member> + </simplelist> + </entry> + </row> + </tbody> + </tgroup> + </informaltable> + + </sect2> + </sect1> + </chapter> + + <chapter id="contacts-hooks"> + <title> + Contacts Hooks + </title> + <para> + None defined. + </para> + </chapter> + + <chapter id="calendar-hooks"> + <title> + Calendar and Tasks Hooks + </title> + <para> + None defined. + </para> + </chapter> + + <chapter id="shell-hooks"> + <title> + Shell Hooks + </title> + <para> + None defined. + </para> + </chapter> + + </part> + + <part id="reference"> + <title> + Reference + </title> + <partintro> + <para> + This section of the book is a detailed API reference of the + objects and methods that implement the core plugin system and hooks. + </para> + <para> + It contains the detailed information required for all uses of the + plugin system. That is, implementors + of new hook types, application developers providing hook points, and + plugin developers. + </para> + </partintro> + <chapter id="REF-EPlugin"> + <title> + EPlugin + </title> + <para> + The EPlugin object manages the loading and invocation of physical + plugin definitions and plugin binaries. The base EPlugin class is an + abstract class which loads plugin definitons, resolving hooks, and + provides an api for invoking callbacks. + </para> + <para> + The EPluginLib object is a concrete derived class of EPlugin which + handles loading shared libraries using the GModule interface. + </para> + &e-plugin-reference; + </chapter> + + <chapter> + <title> + EPopup + </title> + <para> + The EPopup object manages a single popup menu. It is used to + application code as a convenience function for building dynamic popup + menus based on a specific context. + </para> + <para> + The EPopupHook object is loaded by + the &EPlugin; system, and is used to provide dynamic extension to the + application context menus. + </para> + &e-popup-reference; + + <!-- this looks like bum here, not sure where else to put it though --> + &em-popup-reference; + </chapter> + + <chapter> + <title> + EMenu + </title> + <para> + The EMenu object manages the menus for a given view or component. It + is used by application code to allow the plugin system an entry point + to current application view. It may also be used by the application as + a convenience function to dynamically alter the menu system based on + user context. + </para> + <para> + The EMenuHook object is loaded by the &EPlugin; system, and is used to + provide dynamic extension to the application menus. + </para> + &e-menu-reference; + </chapter> + + <chapter> + <title> + EConfig + </title> + <para> + The EConfig object manages the building of dynamic configuration pages + to configure specific application objects. The same basic object can + be used to fully drive a wizard-like druid object, or to drive a + note-book of configuration options. It is used by application code to + provide the core controller in a model-view-controller implementation + of a UI window. + </para> + <para> + The EConfigHook object is loaded by the &EPlugin; system, and is used hook + in additional configuration items into configuration windows or druids + dynamically. + </para> + &e-config-reference; + </chapter> + + <chapter> + <title> + EEvent + </title> + <para> + The EEvent object manages broadcast of events for a given component or + application. It is used by application code to provide the plugin + system with an entry point for user and system state events. + </para> + <para> + The EEventHook object is loaded by the &EPlugin; system, and is used hook + event listeners into dynamically loaded event handlers. + </para> + &e-event-reference; + </chapter> + + <chapter> + <title> + EMFormat + </title> + <para> + The EMFormat object drives the formatting of MIME message content for + display, print, and replying. EMFormatHTML is an implementation of + EMFormat which writes its output to a GtkHTML instance. + </para> + <para> + The EMFormatHook object is loaded by the &EPlugin; system, and is used hook + event listeners into dynamically loaded event handlers. + </para> + &em-format-reference; + </chapter> + </part> + +</book> 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__ */ diff --git a/mail/ChangeLog b/mail/ChangeLog index 2954cbdaaf..97a64fff60 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -411,6 +411,216 @@ * Makefile.am (SUBDIRS): Revert previous error change +2004-09-10 Not Zed <NotZed@Ximian.com> + + * em-*: various doco updates. + + * em-format-quote.c (emfq_builtin_init): instead of removing the + type, just add an override noop. Changed api's. + +2004-09-09 Not Zed <NotZed@Ximian.com> + + * em-format.c (em_format_class_remove_handler): Change this to use + the original structure only, not by type. + + * em-format-hook.c (emfh_enable): implement hook enablation. + +2004-09-07 Not Zed <NotZed@Ximian.com> + + * em-account-editor.c (emae_setup_providers): set the provider + shown selected on the store as well. + + * mail-config.glade: moved the druid 'help text' directly into the + glade file. + + * em-account-editor.c (emae_check_complete): add pre-load for + management page options. + + * em-utils.c (em_utils_configure_account): remove use of + mail-config-druid. + + * em-account-editor.c (emae_remove_childen): removed. + (*): removed service->url, always get it from/set it to the + e-account so it doesn't have side-effects with plugins. + + * mail-dialogs.glade: fix up the names of the various license + dialog fields. + +2004-09-06 Not Zed <NotZed@Ximian.com> + + * em-account-editor.c (emae_receive_options_extra_item): store the + config widgets in the item entry itself. + (emae_auto_detect): wasted a whole lot of time on this crap. + + * em-account-prefs.c (account_able_clicked): removed some redundant logic. + + * mail-send-recv.c (mail_autoreceive_setup): renamed to + mail_autoreceive_init and rewrote all the callbacks. now it + listens to events on the accountlist directly and doesn't need + invoking manually. It also runs relative to the account always, + rather than copying the uri. Removed all callers except the mail + component one. + + * em-account-editor.c (em_account_editor_provider_selector_new): + removed becaus eof below. + (em_account_editor_construct): copy the account to a working + object if supplied, else create a new one. + + * mail-config.glade: make the provider selectors just use + em_account_editor_dropdown new for the dropdown menu. + + * em-account-editor.c (prepare_signatures): removed, merged into + the identity page code. + (sig_fill_menu): removed. + (emae_setup_signatures): new function to setup signatures on a + gtkcombobox. + (clear_menu): removed. + (signature_changed): removed. + (emae_signature_changed): new function for changed. + (signature_removed): removed. + (emae_signature_removed): new function for removed. + (emae_signature_get_iter): helper for finding the right row for + this signature. + (signature_added): removed. + (emae_signature_added): new function for added. + (sig_activate): removed. + (emae_signaturetype_changed): new function to update the account + info immediately. + (sig_add_new_signature): renamed to emae_signature_new. + (select_account_signature): no longer needed, this happens in the + setup. + + * mail-config.glade: changed the signature thing to a custom + widget (gtkcombobox). + + * em-account-editor.c (em_account_editor_construct): updates for + api change, table sections require table items. + (emae_setup_service): handle host:port hostname syntax. + (emae_hostname_changed): and here too. + (emae_commit): fill this out, and implement it. + +2004-09-04 Not Zed <NotZed@Ximian.com> + + * *.c: include gnome i18.h if needed since camel-object.h was + fixed. + + * em-format.c (em_format_class_add_handler): if a handler is + already set for type, link it in. + + * em-format.h: added a link pointer to EMFormatHanlder, so that + overrides can also fallback. remove applications member - not + used anymore. + +2004-09-01 Not Zed <NotZed@Ximian.com> + + * em-popup.h: added some docs. + + * em-popup.c (emp_standard_menu_factory): + * em-folder-tree.c (tree_drag_data_received): popup api changes. + +2004-08-31 Not Zed <NotZed@Ximian.com> + + * em-format-html-display.c (efhd_attachment_button_show): use a + wrapper for the button event since the popup event has changed + signature. + +2004-08-30 Not Zed <NotZed@Ximian.com> + + * em-popup.c (emp_standard_menu_factory): changed args. + (struct _open_in_item): removed, use user data instead. + (*): fix callbacks for new apis. + (emp_popup_open): Removed some dead popup callbacks never invoked. + (em_popup_target_new_attachments): new target type for attachment + bar in composer. + + * em-folder-tree.c (emft_popup*): convert to new e-popup apis. + + * em-folder-view.c: emfv_popup_*: convert to new e-poup callback + signatures. + (EMFV_MAP_CALLBACK): fix for e-popup callback change. + (EMFV_POPUP_AUTO_TYPE): same. + (emfv_popup): dont bother setting item callback data. + (emfv_popup_labels_free): api changes. + (emfv_popup_items_free): new free method. + (EMFVPopupItem): No longer needed. + (emfv_uri_popup_free): api changes. + (emfv_format_popup_event): new api's == simpler code. + + * em-account-editor.c (emae_receive_options_extra_item): add a + hack for the label item, which is only used by connector to + override the hostname and username labels. + +2004-08-24 Not Zed <NotZed@Ximian.com> + + * em-account-editor.c (em_account_editor_construct): create the + window rather than the widget here. + + * mail-config.glade: remove the extra_page from the druid, it is + autogenerated now. renamed the "Receiving options" notebook page + to "Receiving Email" since it isn't hte options page. + + * em-account-editor.c (emae_receive_options_page): no longer + needed, autogenerated. + (emae_receive_options_item): setup the auto-receive time values. + (emae_receive_page): don't setup the auto check time values here. + + * mail-config.glade: Remove the Receiving Mail tab, it is now + completely auto-generated. + +2004-08-17 Not Zed <NotZed@Ximian.com> + + * em-event.[ch]: mail event dispatcher. + + * mail-component.c (mc_startup): hook into events. + + * mail-folder-cache.c (update_1folder): always set the uri field. + (real_flush_updates): emit a folder.changed:new event if we have new + mail. + +2004-08-13 Not Zed <NotZed@Ximian.com> + + * mail-config.glade: replaced ssl selector with custom widget + (gtkcombobox). Giv the preferences, composer toplevel unique + names. + +2004-07-29 Not Zed <NotZed@Ximian.com> + + * mail-config.glade: replaced source and transport selector with + custom widget (gtkcombobox). + + * em-account-editor.[ch]: copy over mail-account-gui.[ch] and + rename stuff. Make a gobject. Move most internals, internal. + +2004-07-28 Not Zed <NotZed@Ximian.com> + + * em-composer-prefs.c (em_composer_prefs_construct): get the + toplevel tab as the root. + (em_composer_prefs_construct): setup plugin metadata for composer + prefs. + + * em-mailer-prefs.c (em_mailer_prefs_construct): setup plugin + metadata for mail preferences. + + * em-config.c (em_config_target_new_prefs): new target for 'prefs' + mode, everything global via gconf. + + * em-mailer-prefs.c (em_mailer_prefs_construct): get the gui from + the preferences_toplevel not the preferences_tab window which we + don't need. + + * mail-config.glade: rename the preferences 'toplevel' to + preferences_toplevel so we can get it unparented. And the + composer toplevel to composer_toplevel. + + * em-folder-properties.c (em_folder_properties_show): remove test + code. + + * em-format.h (struct _EMFormatPURI): add a free function + callback. + + * em-format.c (emf_clear_puri_node): if the free function is set, + call it. + 2004-07-01 Rodney Dawes <dobey@novell.com> * Makefile.am (BUILT_SOURCES): Remove $(error_i18n) diff --git a/mail/Makefile.am b/mail/Makefile.am index 888529cd1e..834ad77d4c 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -58,76 +58,86 @@ libevolution_mail_la_SOURCES = \ $(MARSHAL_GENERATED) \ e-searching-tokenizer.c \ e-searching-tokenizer.h \ + em-account-editor.c \ + em-account-editor.h \ em-account-prefs.c \ em-account-prefs.h \ em-composer-prefs.c \ em-composer-prefs.h \ - em-mailer-prefs.c \ - em-mailer-prefs.h \ - em-inline-filter.c \ - em-inline-filter.h \ + em-composer-utils.c \ + em-composer-utils.h \ + em-config.c \ + em-config.h \ + em-event.c \ + em-event.h \ em-filter-context.c \ em-filter-context.h \ em-filter-editor.c \ em-filter-editor.h \ - em-filter-rule.c \ - em-filter-rule.h \ em-filter-folder-element.c \ em-filter-folder-element.h \ - em-filter-source-element.h \ + em-filter-rule.c \ + em-filter-rule.h \ em-filter-source-element.c \ + em-filter-source-element.h \ + em-folder-browser.c \ + em-folder-browser.h \ em-folder-properties.c \ em-folder-properties.h \ - em-folder-selection.c \ - em-folder-selection.h \ em-folder-selection-button.c \ em-folder-selection-button.h \ + em-folder-selection.c \ + em-folder-selection.h \ em-folder-selector.c \ em-folder-selector.h \ - em-folder-tree.c \ - em-folder-tree.h \ em-folder-tree-model.c \ em-folder-tree-model.h \ + em-folder-tree.c \ + em-folder-tree.h \ em-folder-view.c \ em-folder-view.h \ - em-folder-browser.c \ - em-folder-browser.h \ - em-format.c \ - em-format.h \ - em-format-html.c \ - em-format-html.h \ + em-format-hook.c \ + em-format-hook.h \ em-format-html-display.c \ em-format-html-display.h \ em-format-html-print.c \ em-format-html-print.h \ - em-stripsig-filter.c \ - em-stripsig-filter.h \ + em-format-html.c \ + em-format-html.h \ em-format-quote.c \ em-format-quote.h \ + em-format.c \ + em-format.h \ + em-html-stream.c \ + em-html-stream.h \ + em-icon-stream.c \ + em-icon-stream.h \ + em-inline-filter.c \ + em-inline-filter.h \ + em-junk-filter.c \ + em-junk-filter.h \ + em-junk-plugin.c \ + em-junk-plugin.h \ + em-mailer-prefs.c \ + em-mailer-prefs.h \ + em-menu.c \ + em-menu.h \ em-message-browser.c \ em-message-browser.h \ em-migrate.c \ em-migrate.h \ - em-composer-utils.c \ - em-composer-utils.h \ em-popup.c \ em-popup.h \ - em-utils.c \ - em-utils.h \ em-search-context.c \ em-search-context.h \ + em-stripsig-filter.c \ + em-stripsig-filter.h \ em-subscribe-editor.c \ em-subscribe-editor.h \ em-sync-stream.c \ em-sync-stream.h \ - em-icon-stream.c \ - em-icon-stream.h \ - em-junk-filter.c \ - em-junk-filter.h \ - em-junk-plugin.c \ - em-junk-plugin.h \ - em-html-stream.c \ - em-html-stream.h \ + em-utils.c \ + em-utils.h \ em-vfolder-context.c \ em-vfolder-context.h \ em-vfolder-editor.c \ @@ -143,14 +153,14 @@ libevolution_mail_la_SOURCES = \ mail-component-factory.c \ mail-component.c \ mail-component.h \ - mail-config.c \ - mail-config.h \ mail-config-druid.c \ mail-config-druid.h \ - mail-crypto.c \ - mail-crypto.h \ mail-config-factory.c \ mail-config-factory.h \ + mail-config.c \ + mail-config.h \ + mail-crypto.c \ + mail-crypto.h \ mail-folder-cache.c \ mail-folder-cache.h \ mail-mt.c \ diff --git a/mail/em-account-editor.c b/mail/em-account-editor.c new file mode 100644 index 0000000000..080b59d5cc --- /dev/null +++ b/mail/em-account-editor.c @@ -0,0 +1,2439 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Dan Winship <danw@ximian.com> + * Jeffrey Stedfast <fejj@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright 2001 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +/* + work before merge can occur: + + verify behaviour. + work out what to do with the startup druid. + + also need to work out: + how to remove unecessary items from a service url once + configured (removing settings from other types). + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +#include <string.h> +#include <stdarg.h> + +#include <gconf/gconf-client.h> + +#include <glade/glade.h> + +#include <gtk/gtkentry.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtktextbuffer.h> +#include <gtk/gtktextview.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcelllayout.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtktable.h> + +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-druid.h> +#include <libgnomeui/gnome-druid-page-standard.h> + +#include <e-util/e-account-list.h> +#include <e-util/e-signature-list.h> + +#include <widgets/misc/e-error.h> + +#include "em-config.h" +#include "em-folder-selection-button.h" +#include "em-account-editor.h" +#include "mail-session.h" +#include "mail-send-recv.h" +#include "mail-signature-editor.h" +#include "mail-component.h" +#include "em-utils.h" +#include "em-composer-prefs.h" +#include "mail-config.h" +#include "mail-ops.h" +#include "mail-mt.h" + +#if defined (HAVE_NSS) +#include "smime/gui/e-cert-selector.h" +#endif + +#define d(x) + +/* econfig item for the extra config hings */ +struct _receive_options_item { + EMConfigItem item; + GSList *widgets; + + /* Only CAMEL_PROVIDER_CONF_ENTRYs GtkEntrys are stored here. + The auto-detect camel provider code will probably be removed */ + GHashTable *extra_table; +}; + +typedef struct _EMAccountEditorService { + EMAccountEditor *emae; /* parent pointer, for callbacks */ + + struct _GtkWidget *frame; + struct _GtkWidget *container; + + struct _GtkLabel *description; + struct _GtkEntry *hostname; + struct _GtkEntry *username; + struct _GtkEntry *path; + struct _GtkWidget *ssl_frame; + struct _GtkComboBox *use_ssl; + struct _GtkWidget *ssl_hbox; + struct _GtkWidget *no_ssl; + + struct _GtkWidget *auth_frame; + struct _GtkComboBox *authtype; + struct _GtkWidget *authitem; + struct _GtkToggleButton *remember; + struct _GtkButton *check_supported; + struct _GtkToggleButton *needs_auth; + + GList *authtypes; /* if "Check supported" */ + CamelProvider *provider; + CamelProviderType type; + + int auth_changed_id; +} EMAccountEditorService; + +typedef struct _EMAccountEditorPrivate { + struct _GladeXML *xml; + struct _GladeXML *druidxml; + struct _EMConfig *config; + GList *providers; + + /* signatures */ + struct _GtkComboBox *signatures_dropdown; + guint sig_added_id; + guint sig_removed_id; + guint sig_changed_id; + const char *sig_uid; + + /* incoming mail */ + EMAccountEditorService source; + + /* extra incoming config */ + CamelProvider *extra_provider; + GSList *extra_items; /* this is freed by the econfig automatically */ + + /* outgoing mail */ + EMAccountEditorService transport; + + /* account management */ + struct _GtkToggleButton *default_account; + + /* special folders */ + struct _GtkButton *drafts_folder_button; + struct _GtkButton *sent_folder_button; + struct _GtkButton *restore_folders_button; + + /* Security */ + struct _GtkEntry *pgp_key; + struct _GtkToggleButton *pgp_encrypt_to_self; + struct _GtkToggleButton *pgp_always_sign; + struct _GtkToggleButton *pgp_no_imip_sign; + struct _GtkToggleButton *pgp_always_trust; + + struct _GtkToggleButton *smime_sign_default; + struct _GtkEntry *smime_sign_key; + struct _GtkButton *smime_sign_key_select; + struct _GtkButton *smime_sign_key_clear; + struct _GtkButton *smime_sign_select; + struct _GtkToggleButton *smime_encrypt_default; + struct _GtkToggleButton *smime_encrypt_to_self; + struct _GtkEntry *smime_encrypt_key; + struct _GtkButton *smime_encrypt_key_select; + struct _GtkButton *smime_encrypt_key_clear; + + /* for druid page preparation */ + unsigned int identity_set:1; + unsigned int receive_set:1; + unsigned int management_set:1; +} EMAccountEditorPrivate; + +static GtkWidget *emae_setup_authtype(EMAccountEditor *emae, EMAccountEditorService *service); + +static GtkVBoxClass *emae_parent; + +static void +emae_init(GObject *o) +{ + EMAccountEditor *emae = (EMAccountEditor *)o; + + emae->priv = g_malloc0(sizeof(*emae->priv)); + + emae->priv->source.emae = emae; + emae->priv->transport.emae = emae; +} + +static void +emae_finalise(GObject *o) +{ + EMAccountEditor *emae = (EMAccountEditor *)o; + EMAccountEditorPrivate *p = emae->priv; + + if (p->sig_added_id) { + ESignatureList *signatures = mail_config_get_signatures(); + + g_signal_handler_disconnect(signatures, p->sig_added_id); + g_signal_handler_disconnect(signatures, p->sig_removed_id); + g_signal_handler_disconnect(signatures, p->sig_changed_id); + } + + if (p->xml) + g_object_unref(p->xml); + if (p->druidxml) + g_object_unref(p->druidxml); + + g_list_free(p->source.authtypes); + g_list_free(p->transport.authtypes); + + g_list_free(p->providers); + g_free(p); + + g_object_unref(emae->account); + if (emae->original) + g_object_unref(emae->original); + + ((GObjectClass *)emae_parent)->finalize(o); +} + +static void +emae_class_init(GObjectClass *klass) +{ + klass->finalize = emae_finalise; +} + +GType +em_account_editor_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMAccountEditorClass), + NULL, NULL, + (GClassInitFunc)emae_class_init, + NULL, NULL, + sizeof(EMAccountEditor), 0, + (GInstanceInitFunc)emae_init + }; + emae_parent = g_type_class_ref(G_TYPE_OBJECT); + type = g_type_register_static(G_TYPE_OBJECT, "EMAccountEditor", &info, 0); + } + + return type; +} + +/** + * em_account_editor_new: + * @account: + * @type: + * + * Create a new account editor. If @account is NULL then this is to + * create a new account, else @account is copied to a working + * structure and is for editing an existing account. + * + * Return value: + **/ +EMAccountEditor *em_account_editor_new(EAccount *account, em_account_editor_t type) +{ + EMAccountEditor *emae = g_object_new(em_account_editor_get_type(), 0); + + em_account_editor_construct(emae, account, type); + + return emae; +} + +/* ********************************************************************** */ + +static struct { + char *label; + char *value; +} ssl_options[] = { + { N_("Always"), "always" }, + { N_("Whenever Possible"), "when-possible" }, + { N_("Never"), "never" } +}; + +#define num_ssl_options (sizeof (ssl_options) / sizeof (ssl_options[0])) + +static gboolean +is_email (const char *address) +{ + /* This is supposed to check if the address's domain could be + an FQDN but alas, it's not worth the pain and suffering. */ + const char *at; + + at = strchr (address, '@'); + /* make sure we have an '@' and that it's not the first or last char */ + if (!at || at == address || *(at + 1) == '\0') + return FALSE; + + return TRUE; +} + +static CamelURL * +emae_account_url(EMAccountEditor *emae, int urlid) +{ + CamelURL *url = NULL; + const char *uri; + + uri = e_account_get_string(emae->account, urlid); + + if (uri && uri[0]) + url = camel_url_new(uri, NULL); + + if (url == NULL) { + url = camel_url_new("dummy:", NULL); + camel_url_set_protocol(url, NULL); + } + + return url; +} + +/* ********************************************************************** */ +static void +emae_license_state(GtkToggleButton *button, GtkDialog *dialog) +{ + gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_ACCEPT, + gtk_toggle_button_get_active(button)); +} + +static gboolean +emae_load_text(GtkTextView *view, const char *filename) +{ + FILE *fd; + char filebuf[1024]; + GtkTextIter iter; + GtkTextBuffer *buffer; + int count; + + g_return_val_if_fail (filename != NULL , FALSE); + + fd = fopen (filename, "r"); + if (fd) { + buffer = gtk_text_buffer_new (NULL); + gtk_text_buffer_get_start_iter (buffer, &iter); + while (!feof (fd) && !ferror (fd)) { + count = fread (filebuf, 1, sizeof (filebuf), fd); + gtk_text_buffer_insert (buffer, &iter, filebuf, count); + } + + gtk_text_view_set_buffer(GTK_TEXT_VIEW (view), GTK_TEXT_BUFFER(buffer)); + fclose (fd); + } + + return fd != NULL; +} + +static gboolean +emae_display_license(EMAccountEditor *emae, CamelProvider *prov) +{ + GladeXML *xml; + GtkWidget *w, *dialog; + char *tmp; + GtkResponseType response = GTK_RESPONSE_NONE; + + xml = glade_xml_new (EVOLUTION_GLADEDIR "/mail-dialogs.glade", "license_dialog", NULL); + dialog = glade_xml_get_widget(xml, "license_dialog"); + gtk_dialog_set_response_sensitive((GtkDialog *)dialog, GTK_RESPONSE_ACCEPT, FALSE); + tmp = g_strdup_printf(_("%s License Agreement"), prov->license); + gtk_window_set_title((GtkWindow *)dialog, tmp); + g_free(tmp); + + g_signal_connect(glade_xml_get_widget(xml, "license_checkbutton"), + "toggled", G_CALLBACK(emae_license_state), dialog); + + tmp = g_strdup_printf(_("\nPlease read carefully the license agreement\n" + "for %s displayed below\n" + "and tick the check box for accepting it\n"), prov->license); + gtk_label_set_text((GtkLabel *)glade_xml_get_widget(xml, "license_top_label"), tmp); + g_free(tmp); + + w = glade_xml_get_widget(xml, "license_textview"); + if (emae_load_text((GtkTextView *)w, prov->license_file)) { + gtk_text_view_set_editable((GtkTextView *)w, FALSE); + response = gtk_dialog_run((GtkDialog *)dialog); + } else { + e_error_run((GtkWindow *)gtk_widget_get_toplevel(emae->editor), + "mail:no-load-license", prov->license_file, NULL); + } + + gtk_widget_destroy(dialog); + g_object_unref(xml); + + return (response == GTK_RESPONSE_ACCEPT); +} + +static gboolean +emae_check_license(EMAccountEditor *emae, CamelProvider *prov) +{ + gboolean accepted = TRUE; + + if (prov->flags & CAMEL_PROVIDER_HAS_LICENSE) { + GConfClient *gconf = mail_config_get_gconf_client(); + GSList *providers_list, *l; + + providers_list = gconf_client_get_list (gconf, "/apps/evolution/mail/licenses", GCONF_VALUE_STRING, NULL); + + for (l = providers_list, accepted = FALSE; l && !accepted; l = g_slist_next(l)) + accepted = (strcmp((char *)l->data, prov->protocol) == 0); + + if (!accepted + && (accepted = emae_display_license(emae, prov)) == TRUE) { + providers_list = g_slist_append(providers_list, g_strdup(prov->protocol)); + gconf_client_set_list(gconf, + "/apps/evolution/mail/licenses", + GCONF_VALUE_STRING, + providers_list, NULL); + } + + g_slist_foreach(providers_list, (GFunc)g_free, NULL); + g_slist_free(providers_list); + } + + return accepted; +} + +static void +default_folders_clicked (GtkButton *button, gpointer user_data) +{ + EMAccountEditor *emae = user_data; + const char *uri; + + uri = mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_DRAFTS); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)emae->priv->drafts_folder_button, uri); + + uri = mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_SENT); + em_folder_selection_button_set_selection((EMFolderSelectionButton *)emae->priv->sent_folder_button, uri); +} + +/* custom widget factories */ +GtkWidget *em_account_editor_folder_selector_button_new (char *widget_name, char *string1, char *string2, int int1, int int2); + +GtkWidget * +em_account_editor_folder_selector_button_new (char *widget_name, char *string1, char *string2, int int1, int int2) +{ + return (GtkWidget *)em_folder_selection_button_new(_("Select Folder"), NULL); +} + +GtkWidget *em_account_editor_dropdown_new(char *widget_name, char *string1, char *string2, int int1, int int2); + +GtkWidget * +em_account_editor_dropdown_new(char *widget_name, char *string1, char *string2, int int1, int int2) +{ + return (GtkWidget *)gtk_combo_box_new(); +} + +GtkWidget *em_account_editor_ssl_selector_new(char *widget_name, char *string1, char *string2, int int1, int int2); + +GtkWidget * +em_account_editor_ssl_selector_new(char *widget_name, char *string1, char *string2, int int1, int int2) +{ + GtkComboBox *dropdown = (GtkComboBox *)gtk_combo_box_new(); + GtkCellRenderer *cell = gtk_cell_renderer_text_new(); + GtkListStore *store; + int i; + GtkTreeIter iter; + + gtk_widget_show((GtkWidget *)dropdown); + + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER); + + for (i=0;i<num_ssl_options;i++) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _(ssl_options[i].label), 1, ssl_options[i].value, -1); + } + + gtk_cell_layout_pack_start((GtkCellLayout *)dropdown, cell, TRUE); + gtk_cell_layout_set_attributes((GtkCellLayout *)dropdown, cell, "text", 0, NULL); + + gtk_combo_box_set_model(dropdown, (GtkTreeModel *)store); + + return (GtkWidget *)dropdown; +} + +/* The camel provider auto-detect interface should be deprecated. + But it still needs to be replaced with something of similar functionality. + Just using the normal econfig plugin mechanism should be adequate. */ +static void +emae_auto_detect_free (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + g_free (value); +} + +static void +emae_auto_detect(EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + EMAccountEditorService *service = &gui->source; + GHashTable *auto_detected; + GSList *l; + CamelProviderConfEntry *entries; + char *value; + int i; + CamelURL *url; + + if (service->provider == NULL + || (entries = service->provider->extra_conf) == NULL) + return; + + printf("Running auto-detect\n"); + + url = emae_account_url(emae, E_ACCOUNT_SOURCE_URL); + camel_provider_auto_detect(service->provider, url, &auto_detected, NULL); + camel_url_free(url); + if (auto_detected == NULL) { + printf(" no values detected\n"); + return; + } + + for (i = 0; entries[i].type != CAMEL_PROVIDER_CONF_END; i++) { + struct _receive_options_item *item; + GtkWidget *w; + + if (entries[i].name == NULL + || (value = g_hash_table_lookup (auto_detected, entries[i].name)) == NULL) + continue; + + /* only 2 providers use this, and they only do it for 3 entries only */ + g_assert(entries[i].type == CAMEL_PROVIDER_CONF_ENTRY); + + w = NULL; + for (l = emae->priv->extra_items;l;l=g_slist_next(l)) { + item = l->data; + if (item->extra_table && (w = g_hash_table_lookup(item->extra_table, entries[i].name))) + break; + } + + gtk_entry_set_text((GtkEntry *)w, value?value:""); + } + + g_hash_table_foreach(auto_detected, emae_auto_detect_free, NULL); + g_hash_table_destroy(auto_detected); +} + +static gint +provider_compare (const CamelProvider *p1, const CamelProvider *p2) +{ + /* sort providers based on "location" (ie. local or remote) */ + if (p1->flags & CAMEL_PROVIDER_IS_REMOTE) { + if (p2->flags & CAMEL_PROVIDER_IS_REMOTE) + return 0; + return -1; + } else { + if (p2->flags & CAMEL_PROVIDER_IS_REMOTE) + return 1; + return 0; + } +} + +static void +emae_signature_added(ESignatureList *signatures, ESignature *sig, EMAccountEditor *emae) +{ + GtkTreeModel *model; + GtkTreeIter iter; + + model = gtk_combo_box_get_model(emae->priv->signatures_dropdown); + + gtk_list_store_append((GtkListStore *)model, &iter); + gtk_list_store_set((GtkListStore *)model, &iter, 0, sig->autogen?_("Autogenerated"):sig->name, 1, sig->uid, -1); + + gtk_combo_box_set_active(emae->priv->signatures_dropdown, gtk_tree_model_iter_n_children(model, NULL)-1); +} + +static int +emae_signature_get_iter(EMAccountEditor *emae, ESignature *sig, GtkTreeModel **modelp, GtkTreeIter *iter) +{ + GtkTreeModel *model; + int found = 0; + + model = gtk_combo_box_get_model(emae->priv->signatures_dropdown); + *modelp = model; + if (!gtk_tree_model_get_iter_first(model, iter)) + return FALSE; + + do { + char *uid; + + gtk_tree_model_get(model, iter, 1, &uid, -1); + if (uid && !strcmp(uid, sig->uid)) + found = TRUE; + g_free(uid); + } while (!found && gtk_tree_model_iter_next(model, iter)); + + return found; +} + +static void +emae_signature_removed(ESignatureList *signatures, ESignature *sig, EMAccountEditor *emae) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + if (emae_signature_get_iter(emae, sig, &model, &iter)) + gtk_list_store_remove((GtkListStore *)model, &iter); +} + +static void +emae_signature_changed(ESignatureList *signatures, ESignature *sig, EMAccountEditor *emae) +{ + GtkTreeIter iter; + GtkTreeModel *model; + + if (emae_signature_get_iter(emae, sig, &model, &iter)) + gtk_list_store_set((GtkListStore *)model, &iter, 0, sig->autogen?_("Autogenerated"):sig->name, -1); +} + +static void +emae_signaturetype_changed(GtkComboBox *dropdown, EMAccountEditor *emae) +{ + int id = gtk_combo_box_get_active(dropdown); + GtkTreeModel *model; + GtkTreeIter iter; + char *uid = NULL; + + if (id != -1) { + model = gtk_combo_box_get_model(dropdown); + if (gtk_tree_model_iter_nth_child(model, &iter, NULL, id)) + gtk_tree_model_get(model, &iter, 1, &uid, -1); + } + + printf("signaturetype changed: %d uid=%s\n", id, uid?uid:""); + + e_account_set_string(emae->account, E_ACCOUNT_ID_SIGNATURE, uid); + g_free(uid); +} + +static void +emae_signature_new(GtkWidget *w, EMAccountEditor *emae) +{ + /* TODO: why is this in composer prefs? apart from it being somewhere to put it? */ + em_composer_prefs_new_signature((GtkWindow *)gtk_widget_get_toplevel(w), + gconf_client_get_bool(mail_config_get_gconf_client(), + "/apps/evolution/mail/composer/send_html", NULL)); +} + +static GtkWidget * +emae_setup_signatures(EMAccountEditor *emae) +{ + EMAccountEditorPrivate *p = emae->priv; + GtkComboBox *dropdown = (GtkComboBox *)glade_xml_get_widget(p->xml, "signature_dropdown"); + GtkCellRenderer *cell = gtk_cell_renderer_text_new(); + GtkListStore *store; + int i, active=0; + GtkTreeIter iter; + ESignatureList *signatures; + EIterator *it; + const char *current = e_account_get_string(emae->account, E_ACCOUNT_ID_SIGNATURE); + GtkWidget *button; + + emae->priv->signatures_dropdown = dropdown; + gtk_widget_show((GtkWidget *)dropdown); + + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _("None"), 1, NULL, -1); + + signatures = mail_config_get_signatures (); + + p->sig_added_id = g_signal_connect(signatures, "signature-added", G_CALLBACK(emae_signature_added), emae); + p->sig_removed_id = g_signal_connect(signatures, "signature-removed", G_CALLBACK(emae_signature_removed), emae); + p->sig_changed_id = g_signal_connect(signatures, "signature-changed", G_CALLBACK(emae_signature_changed), emae); + + /* we need to count the 'none' entry before using the index */ + i = 1; + it = e_list_get_iterator ((EList *) signatures); + while (e_iterator_is_valid (it)) { + ESignature *sig = (ESignature *)e_iterator_get(it); + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, sig->autogen?_("Autogenerated"):sig->name, 1, sig->uid, -1); + + printf("check sig '%s' is ours '%s' = %s\n", sig->uid, current, (current && !strcmp(current, sig->uid))?"yep":"no"); + if (current && !strcmp(current, sig->uid)) + active = i; + + e_iterator_next(it); + i++; + } + g_object_unref (it); + + gtk_cell_layout_pack_start((GtkCellLayout *)dropdown, cell, TRUE); + gtk_cell_layout_set_attributes((GtkCellLayout *)dropdown, cell, "text", 0, NULL); + + gtk_combo_box_set_model(dropdown, (GtkTreeModel *)store); + gtk_combo_box_set_active(dropdown, active); + + g_signal_connect(dropdown, "changed", G_CALLBACK(emae_signaturetype_changed), emae); + gtk_widget_set_sensitive((GtkWidget *)dropdown, e_account_writable(emae->account, E_ACCOUNT_ID_SIGNATURE)); + + button = glade_xml_get_widget(p->xml, "sigAddNew"); + g_signal_connect(button, "clicked", G_CALLBACK(emae_signature_new), emae); + gtk_widget_set_sensitive(button, + gconf_client_key_is_writable(mail_config_get_gconf_client(), + "/apps/evolution/mail/signatures", NULL)); + + return (GtkWidget *)dropdown; +} + +static void +emae_account_entry_changed(GtkEntry *entry, EMAccountEditor *emae) +{ + int item = GPOINTER_TO_INT(g_object_get_data((GObject *)entry, "account-item")); + + e_account_set_string(emae->account, item, gtk_entry_get_text(entry)); +} + +static GtkEntry * +emae_account_entry(EMAccountEditor *emae, const char *name, int item) +{ + GtkEntry *entry; + const char *text; + + entry = (GtkEntry *)glade_xml_get_widget(emae->priv->xml, name); + text = e_account_get_string(emae->account, item); + if (text) + gtk_entry_set_text(entry, text); + g_object_set_data((GObject *)entry, "account-item", GINT_TO_POINTER(item)); + g_signal_connect(entry, "changed", G_CALLBACK(emae_account_entry_changed), emae); + gtk_widget_set_sensitive((GtkWidget *)entry, e_account_writable(emae->account, item)); + + return entry; +} + +static void +emae_account_toggle_changed(GtkToggleButton *toggle, EMAccountEditor *emae) +{ + int item = GPOINTER_TO_INT(g_object_get_data((GObject *)toggle, "account-item")); + + e_account_set_bool(emae->account, item, gtk_toggle_button_get_active(toggle)); +} + +static void +emae_account_toggle_widget(EMAccountEditor *emae, GtkToggleButton *toggle, int item) +{ + gtk_toggle_button_set_active(toggle, e_account_get_bool(emae->account, item)); + g_object_set_data((GObject *)toggle, "account-item", GINT_TO_POINTER(item)); + g_signal_connect(toggle, "toggled", G_CALLBACK(emae_account_toggle_changed), emae); + gtk_widget_set_sensitive((GtkWidget *)toggle, e_account_writable(emae->account, item)); +} + +static GtkToggleButton * +emae_account_toggle(EMAccountEditor *emae, const char *name, int item) +{ + GtkToggleButton *toggle; + + toggle = (GtkToggleButton *)glade_xml_get_widget(emae->priv->xml, name); + emae_account_toggle_widget(emae, toggle, item); + + return toggle; +} + +static void +emae_account_spinint_changed(GtkSpinButton *spin, EMAccountEditor *emae) +{ + int item = GPOINTER_TO_INT(g_object_get_data((GObject *)spin, "account-item")); + + e_account_set_int(emae->account, item, gtk_spin_button_get_value(spin)); +} + +static void +emae_account_spinint_widget(EMAccountEditor *emae, GtkSpinButton *spin, int item) +{ + gtk_spin_button_set_value(spin, e_account_get_int(emae->account, item)); + g_object_set_data((GObject *)spin, "account-item", GINT_TO_POINTER(item)); + g_signal_connect(spin, "value_changed", G_CALLBACK(emae_account_spinint_changed), emae); + gtk_widget_set_sensitive((GtkWidget *)spin, e_account_writable(emae->account, item)); +} + +#if 0 +static GtkSpinButton * +emae_account_spinint(EMAccountEditor *emae, const char *name, int item) +{ + GtkSpinButton *spin; + + spin = (GtkSpinButton *)glade_xml_get_widget(emae->priv->xml, name); + emae_account_spinint_widget(emae, spin, item); + + return spin; +} +#endif + +static void +emae_account_folder_changed(EMFolderSelectionButton *folder, EMAccountEditor *emae) +{ + int item = GPOINTER_TO_INT(g_object_get_data((GObject *)folder, "account-item")); + + e_account_set_string(emae->account, item, em_folder_selection_button_get_selection(folder)); +} + +static EMFolderSelectionButton * +emae_account_folder(EMAccountEditor *emae, const char *name, int item, int deffolder) +{ + EMFolderSelectionButton *folder; + const char *uri; + + folder = (EMFolderSelectionButton *)glade_xml_get_widget(emae->priv->xml, name); + uri = e_account_get_string(emae->account, item); + if (uri) { + char *tmp = em_uri_to_camel(uri); + + em_folder_selection_button_set_selection(folder, tmp); + g_free(tmp); + } else { + em_folder_selection_button_set_selection(folder, mail_component_get_folder_uri(NULL, deffolder)); + } + + g_object_set_data((GObject *)folder, "account-item", GINT_TO_POINTER(item)); + g_object_set_data((GObject *)folder, "folder-default", GINT_TO_POINTER(deffolder)); + g_signal_connect(folder, "selected", G_CALLBACK(emae_account_folder_changed), emae); + gtk_widget_show((GtkWidget *)folder); + + gtk_widget_set_sensitive((GtkWidget *)folder, e_account_writable(emae->account, item)); + + return folder; +} + +#if defined (HAVE_NSS) +static void +smime_changed(EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + int act; + const char *tmp; + + tmp = gtk_entry_get_text(gui->smime_sign_key); + act = tmp && tmp[0]; + gtk_widget_set_sensitive((GtkWidget *)gui->smime_sign_key_clear, act); + gtk_widget_set_sensitive((GtkWidget *)gui->smime_sign_default, act); + if (!act) + gtk_toggle_button_set_active(gui->smime_sign_default, FALSE); + + tmp = gtk_entry_get_text(gui->smime_encrypt_key); + act = tmp && tmp[0]; + gtk_widget_set_sensitive((GtkWidget *)gui->smime_encrypt_key_clear, act); + gtk_widget_set_sensitive((GtkWidget *)gui->smime_encrypt_default, act); + gtk_widget_set_sensitive((GtkWidget *)gui->smime_encrypt_to_self, act); + if (!act) { + gtk_toggle_button_set_active(gui->smime_encrypt_default, FALSE); + gtk_toggle_button_set_active(gui->smime_encrypt_to_self, FALSE); + } +} + +static void +smime_sign_key_selected(GtkWidget *dialog, const char *key, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + + if (key != NULL) { + gtk_entry_set_text(gui->smime_sign_key, key); + smime_changed(emae); + } + + gtk_widget_destroy(dialog); +} + +static void +smime_sign_key_select(GtkWidget *button, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + GtkWidget *w; + + w = e_cert_selector_new(E_CERT_SELECTOR_SIGNER, gtk_entry_get_text(gui->smime_sign_key)); + gtk_window_set_modal((GtkWindow *)w, TRUE); + gtk_window_set_transient_for((GtkWindow *)w, (GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emae)); + g_signal_connect(w, "selected", G_CALLBACK(smime_sign_key_selected), emae); + gtk_widget_show(w); +} + +static void +smime_sign_key_clear(GtkWidget *w, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + + gtk_entry_set_text(gui->smime_sign_key, ""); + smime_changed(emae); +} + +static void +smime_encrypt_key_selected(GtkWidget *dialog, const char *key, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + + if (key != NULL) { + gtk_entry_set_text(gui->smime_encrypt_key, key); + smime_changed(emae); + } + + gtk_widget_destroy(dialog); +} + +static void +smime_encrypt_key_select(GtkWidget *button, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + GtkWidget *w; + + w = e_cert_selector_new(E_CERT_SELECTOR_SIGNER, gtk_entry_get_text(gui->smime_encrypt_key)); + gtk_window_set_modal((GtkWindow *)w, TRUE); + gtk_window_set_transient_for((GtkWindow *)w, (GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emae)); + g_signal_connect(w, "selected", G_CALLBACK(smime_encrypt_key_selected), emae); + gtk_widget_show(w); +} + +static void +smime_encrypt_key_clear(GtkWidget *w, EMAccountEditor *emae) +{ + EMAccountEditorPrivate *gui = emae->priv; + + gtk_entry_set_text(gui->smime_encrypt_key, ""); + smime_changed(emae); +} +#endif + +static void +emae_url_set_hostport(CamelURL *url, const char *txt) +{ + const char *port; + char *host; + + if (txt && (port = strchr(txt, ':'))) { + camel_url_set_port(url, atoi(port+1)); + host = g_alloca(port-txt+1); + memcpy(host, txt, port-txt); + host[port-txt] = 0; + } else { + host = (char *)txt; + } + camel_url_set_host(url, host); +} + +/* This is used to map a funciton which will set on the url a string value. + We need our own function for host:port decoding, as above */ +struct _provider_host_info { + guint32 flag; + void (*setval)(CamelURL *, const char *); + const char *widgets[3]; +}; + +static struct _provider_host_info emae_source_host_info[] = { + { CAMEL_URL_PART_HOST, emae_url_set_hostport, { "source_host", "source_host_label" } }, + { CAMEL_URL_PART_USER, camel_url_set_user, { "source_user", "source_user_label", } }, + { CAMEL_URL_PART_PATH, camel_url_set_path, { "source_path", "source_path_label", "source_path_entry" } }, + { CAMEL_URL_PART_AUTH, NULL, { NULL, "source_auth_frame" } }, + { 0 }, +}; + +static struct _provider_host_info emae_transport_host_info[] = { + { CAMEL_URL_PART_HOST, emae_url_set_hostport, { "transport_host", "transport_host_label" } }, + { CAMEL_URL_PART_USER, camel_url_set_user, { "transport_user", "transport_user_label", } }, + { CAMEL_URL_PART_AUTH, NULL, { NULL, "transport_auth_frame" } }, + { 0 }, +}; + +/* This is used to map each of the two services in a typical account to the widgets that represent each service. + i.e. the receiving (source) service, and the sending (transport) service. + It is used throughout the following code to drive each page */ +static struct _service_info { + int account_uri_key; + int save_passwd_key; + + char *frame; + char *type_dropdown; + + char *container; + char *description; + char *hostname; + char *username; + char *path; + + char *security_frame; + char *ssl_hbox; + char *use_ssl; + char *ssl_disabled; + + char *needs_auth; + char *auth_frame; + + char *authtype; + char *authtype_check; + + char *remember_password; + + struct _provider_host_info *host_info; +} emae_service_info[CAMEL_NUM_PROVIDER_TYPES] = { + { E_ACCOUNT_SOURCE_URL, E_ACCOUNT_SOURCE_SAVE_PASSWD, + "source_frame", "source_type_dropdown", + "source_vbox", "source_description", "source_host", "source_user", "source_path", + "source_security_frame", "source_ssl_hbox", "source_use_ssl", "source_ssl_disabled", + NULL, "source_auth_frame", + "source_auth_dropdown", "source_check_supported", + "source_remember_password", + emae_source_host_info, + }, + { E_ACCOUNT_TRANSPORT_URL, E_ACCOUNT_TRANSPORT_SAVE_PASSWD, + "transport_frame", "transport_type_dropdown", + "transport_vbox", "transport_description", "transport_host", "transport_user", NULL, + "transport_security_frame", "transport_ssl_hbox", "transport_use_ssl", "transport_ssl_disabled", + "transport_needs_auth", "transport_auth_frame", + "transport_auth_dropdown", "transport_check_supported", + "transport_remember_password", + emae_transport_host_info, + }, +}; + +static void +emae_uri_changed(EMAccountEditorService *service, CamelURL *url) +{ + char *uri; + + uri = camel_url_to_string(url, 0); + printf("uri changed: '%s'\n", uri); + e_account_set_string(service->emae->account, emae_service_info[service->type].account_uri_key, uri); + g_free(uri); +} + +static void +emae_service_url_changed(EMAccountEditorService *service, void (*setval)(CamelURL *, const char *), GtkEntry *entry) +{ + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + setval(url, gtk_entry_get_text(entry)); + emae_uri_changed(service, url); + camel_url_free(url); +} + +static void +emae_hostname_changed(GtkEntry *entry, EMAccountEditorService *service) +{ + emae_service_url_changed(service, emae_url_set_hostport, entry); +} + +static void +emae_username_changed(GtkEntry *entry, EMAccountEditorService *service) +{ + emae_service_url_changed(service, camel_url_set_user, entry); +} + +static void +emae_path_changed(GtkEntry *entry, EMAccountEditorService *service) +{ + emae_service_url_changed(service, camel_url_set_path, entry); +} + +static void +emae_needs_auth(GtkToggleButton *toggle, EMAccountEditorService *service) +{ + GtkWidget *w; + int need = gtk_toggle_button_get_active(toggle); + + w = glade_xml_get_widget(service->emae->priv->xml, emae_service_info[service->type].auth_frame); + gtk_widget_set_sensitive(w, need); + /* if need ; service_changed? */ +} + +static int +emae_ssl_update(EMAccountEditorService *service, CamelURL *url) +{ + int id = gtk_combo_box_get_active(service->use_ssl); + GtkTreeModel *model; + GtkTreeIter iter; + char *ssl; + + if (id == -1) + return 0; + + model = gtk_combo_box_get_model(service->use_ssl); + if (gtk_tree_model_iter_nth_child(model, &iter, NULL, id)) { + gtk_tree_model_get(model, &iter, 1, &ssl, -1); + if (!strcmp(ssl, "none")) + ssl = NULL; + camel_url_set_param(url, "use_ssl", ssl); + return 1; + } + + return 0; +} + +static void +emae_ssl_changed(GtkComboBox *dropdown, EMAccountEditorService *service) +{ + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + if (emae_ssl_update(service, url)) + emae_uri_changed(service, url); + camel_url_free(url); +} + +static void +emae_service_provider_changed(EMAccountEditorService *service) +{ + int i, j; + void (*show)(GtkWidget *); + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + if (service->provider) { + int enable; + GtkWidget *dwidget = NULL; + + camel_url_set_protocol(url, service->provider->protocol); + gtk_label_set_text(service->description, service->provider->description); + if (!emae_check_license(service->emae, service->provider)) + gtk_widget_hide(service->frame); + else + gtk_widget_show(service->frame); + + enable = e_account_writable_option(service->emae->account, service->provider->protocol, "auth"); + gtk_widget_set_sensitive((GtkWidget *)service->authtype, enable); + gtk_widget_set_sensitive((GtkWidget *)service->check_supported, enable); + + enable = e_account_writable_option(service->emae->account, service->provider->protocol, "use_ssl"); + gtk_widget_set_sensitive((GtkWidget *)service->use_ssl, enable); + + enable = e_account_writable(service->emae->account, emae_service_info[service->type].save_passwd_key); + gtk_widget_set_sensitive((GtkWidget *)service->remember, enable); + + for (i=0;emae_service_info[service->type].host_info[i].flag;i++) { + const char *name; + GtkWidget *w; + struct _provider_host_info *info = &emae_service_info[service->type].host_info[i]; + + enable = CAMEL_PROVIDER_ALLOWS(service->provider, info->flag); + show = enable?gtk_widget_show:gtk_widget_hide; + + for (j=0; j < sizeof(info->widgets)/sizeof(info->widgets[0]); j++) { + name = info->widgets[j]; + if (name) { + w = glade_xml_get_widget(service->emae->priv->xml, name); + show(w); + if (j == 0) { + if (dwidget == NULL && enable) + dwidget = w; + + if (info->setval) + info->setval(url, enable?gtk_entry_get_text((GtkEntry *)w):NULL); + } + } + } + } + + if (dwidget) + gtk_widget_grab_focus(dwidget); + + if (CAMEL_PROVIDER_ALLOWS(service->provider, CAMEL_URL_PART_AUTH)) { + camel_url_set_authmech(url, NULL); + emae_setup_authtype(service->emae, service); + if (service->needs_auth && !CAMEL_PROVIDER_NEEDS(service->provider, CAMEL_URL_PART_AUTH)) + gtk_widget_show((GtkWidget *)service->needs_auth); + } else { + if (service->needs_auth) + gtk_widget_hide((GtkWidget *)service->needs_auth); + } +#ifdef HAVE_SSL + gtk_widget_hide(service->no_ssl); + if (service->provider->flags & CAMEL_PROVIDER_SUPPORTS_SSL) { + emae_ssl_update(service, url); + show = gtk_widget_show; + } else { + camel_url_set_param(url, "use_ssl", NULL); + show = gtk_widget_hide; + } + show(service->ssl_frame); + show(service->ssl_hbox); +#else + gtk_widget_hide(service->ssl_hbox); + gtk_widget_show(service->no_ssl); + camel_url_set_param(url, "use_ssl", NULL); +#endif + } else { + camel_url_set_protocol(url, NULL); + gtk_label_set_text(service->description, ""); + gtk_widget_hide(service->frame); + gtk_widget_hide(service->auth_frame); + gtk_widget_hide(service->ssl_frame); + } + + /* FIXME: linked services? */ + /* FIXME: permissions setup */ + + emae_uri_changed(service, url); + camel_url_free(url); +} + +static void +emae_provider_changed(GtkComboBox *dropdown, EMAccountEditorService *service) +{ + int id = gtk_combo_box_get_active(dropdown); + GtkTreeModel *model; + GtkTreeIter iter; + + if (id == -1) + return; + + model = gtk_combo_box_get_model(dropdown); + if (!gtk_tree_model_iter_nth_child(model, &iter, NULL, id)) + return; + + gtk_tree_model_get(model, &iter, 1, &service->provider, -1); + + g_list_free(service->authtypes); + service->authtypes = NULL; + + emae_service_provider_changed(service); + + e_config_target_changed((EConfig *)service->emae->priv->config, E_CONFIG_TARGET_CHANGED_REBUILD); +} + +static GtkWidget * +emae_setup_providers(EMAccountEditor *emae, EMAccountEditorService *service) +{ + EMAccountEditorPrivate *gui = emae->priv; + EAccount *account = emae->account; + GtkListStore *store; + GtkTreeIter iter; + GList *l; + GtkCellRenderer *cell = gtk_cell_renderer_text_new(); + GtkComboBox *dropdown; + int active = 0, i; + struct _service_info *info = &emae_service_info[service->type]; + const char *uri = e_account_get_string(account, info->account_uri_key); + char *current = NULL; + + dropdown = (GtkComboBox *)glade_xml_get_widget(gui->xml, info->type_dropdown); + gtk_widget_show((GtkWidget *)dropdown); + + if (uri) { + const char *colon = strchr(uri, ':'); + int len; + + if (colon) { + len = colon-uri; + current = g_alloca(len+1); + memcpy(current, uri, len); + current[len] = 0; + } + } + + store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER); + + i = 0; + + /* We just special case each type here, its just easier */ + if (service->type == CAMEL_PROVIDER_STORE) { + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, _("None"), 1, NULL, -1); + i++; + } + + for (l=gui->providers; l; l=l->next) { + CamelProvider *provider = l->data; + + if (!((strcmp(provider->domain, "mail") == 0 + || strcmp (provider->domain, "news") == 0) + && provider->object_types[service->type] + && (service->type != CAMEL_PROVIDER_STORE || (provider->flags & CAMEL_PROVIDER_IS_SOURCE) != 0))) + continue; + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, provider->name, 1, provider, -1); + + /* FIXME: GtkCombo doesn't support sensitiviy, we can hopefully kill this crap anyway */ +#if 0 + if (type == CAMEL_PROVIDER_TRANSPORT + && CAMEL_PROVIDER_IS_STORE_AND_TRANSPORT (provider)) + gtk_widget_set_sensitive (item, FALSE); +#endif + /* find the displayed and set default */ + if (i == 0 || (current && strcmp(provider->protocol, current) == 0)) { + service->provider = provider; + active = i; + + /* we need to set this value on the uri too */ + if (current == NULL) { + CamelURL *url = emae_account_url(emae, info->account_uri_key); + + camel_url_set_protocol(url, provider->protocol); + emae_uri_changed(service, url); + camel_url_free(url); + } + } + i++; + } + + gtk_combo_box_set_model(dropdown, (GtkTreeModel *)store); + gtk_cell_layout_pack_start((GtkCellLayout *)dropdown, cell, TRUE); + gtk_cell_layout_set_attributes((GtkCellLayout *)dropdown, cell, "text", 0, NULL); + + gtk_combo_box_set_active(dropdown, -1); /* needed for gtkcombo bug(?) */ + gtk_combo_box_set_active(dropdown, active); + g_signal_connect(dropdown, "changed", G_CALLBACK(emae_provider_changed), service); + + return (GtkWidget *)dropdown; +} + +static void +emae_authtype_changed(GtkComboBox *dropdown, EMAccountEditorService *service) +{ + int id = gtk_combo_box_get_active(dropdown); + GtkTreeModel *model; + GtkTreeIter iter; + CamelServiceAuthType *authtype; + CamelURL *url; + + if (id == -1) + return; + + url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + model = gtk_combo_box_get_model(dropdown); + if (gtk_tree_model_iter_nth_child(model, &iter, NULL, id)) { + gtk_tree_model_get(model, &iter, 1, &authtype, -1); + if (authtype) + camel_url_set_authmech(url, authtype->authproto); + else + camel_url_set_authmech(url, NULL); + emae_uri_changed(service, url); + } + camel_url_free(url); + + gtk_widget_set_sensitive((GtkWidget *)service->remember, authtype?authtype->need_password:FALSE); +} + +static void emae_check_authtype(GtkWidget *w, EMAccountEditorService *service); + +static GtkWidget * +emae_setup_authtype(EMAccountEditor *emae, EMAccountEditorService *service) +{ + EMAccountEditorPrivate *gui = emae->priv; + EAccount *account = emae->account; + GtkListStore *store; + GtkTreeIter iter; + GtkComboBox *dropdown; + GtkWidget *w; + int active = 0; + int i; + struct _service_info *info = &emae_service_info[service->type]; + const char *uri = e_account_get_string(account, info->account_uri_key); + GList *l, *ll; + CamelURL *url = NULL; + + dropdown = (GtkComboBox *)glade_xml_get_widget(gui->xml, info->authtype); + gtk_widget_show((GtkWidget *)dropdown); + + store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN); + + if (uri) + url = camel_url_new(uri, NULL); + + if (service->provider) { + for (i=0, l=service->provider->authtypes; l; l=l->next, i++) { + CamelServiceAuthType *authtype = l->data; + int avail; + + /* if we have some already shown */ + if (service->authtypes) { + for (ll = service->authtypes;ll;ll = g_list_next(ll)) + if (!strcmp(authtype->authproto, ((CamelServiceAuthType *)ll->data)->authproto)) + break; + avail = ll != NULL; + } else { + avail = TRUE; + } + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, authtype->name, 1, authtype, 2, !avail, -1); + + if (url && url->authmech && !strcmp(url->authmech, authtype->authproto)) + active = i; + } + } + + gtk_combo_box_set_model(dropdown, (GtkTreeModel *)store); + gtk_combo_box_set_active(dropdown, -1); + + if (service->auth_changed_id == 0) { + GtkCellRenderer *cell = gtk_cell_renderer_text_new(); + + gtk_cell_layout_pack_start((GtkCellLayout *)dropdown, cell, TRUE); + gtk_cell_layout_set_attributes((GtkCellLayout *)dropdown, cell, "text", 0, "strikethrough", 2, NULL); + + service->auth_changed_id = g_signal_connect(dropdown, "changed", G_CALLBACK(emae_authtype_changed), service); + w = glade_xml_get_widget(gui->xml, info->authtype_check); + g_signal_connect(w, "clicked", G_CALLBACK(emae_check_authtype), service); + } + + gtk_combo_box_set_active(dropdown, active); + + if (url) + camel_url_free(url); + + return (GtkWidget *)dropdown; +} + +static void emae_check_authtype(GtkWidget *w, EMAccountEditorService *service) +{ + EMAccountEditor *emae = service->emae; + const char *uri; + + if (service->authtypes) { + g_list_free(service->authtypes); + service->authtypes = NULL; + } + + uri = e_account_get_string(emae->account, emae_service_info[service->type].account_uri_key); + if (mail_config_check_service(uri, service->type, &service->authtypes, (GtkWindow *)gtk_widget_get_toplevel((GtkWidget *)emae->editor))) + emae_setup_authtype(emae, service); +} + +static void +emae_setup_service(EMAccountEditor *emae, EMAccountEditorService *service) +{ + EMAccountEditorPrivate *gui = emae->priv; + struct _service_info *info = &emae_service_info[service->type]; + CamelURL *url = emae_account_url(emae, info->account_uri_key); + const char *uri = e_account_get_string(emae->account, info->account_uri_key); + const char *tmp; + int i; + + service->provider = uri?camel_provider_get(uri, NULL):NULL; + + service->frame = glade_xml_get_widget(gui->xml, info->frame); + service->container = glade_xml_get_widget(gui->xml, info->container); + service->description = GTK_LABEL (glade_xml_get_widget (gui->xml, info->description)); + service->hostname = GTK_ENTRY (glade_xml_get_widget (gui->xml, info->hostname)); + service->username = GTK_ENTRY (glade_xml_get_widget (gui->xml, info->username)); + if (info->path) + service->path = GTK_ENTRY (glade_xml_get_widget (gui->xml, info->path)); + + service->ssl_frame = glade_xml_get_widget (gui->xml, info->security_frame); + gtk_widget_hide (service->ssl_frame); + service->ssl_hbox = glade_xml_get_widget (gui->xml, info->ssl_hbox); + service->use_ssl = (GtkComboBox *)glade_xml_get_widget (gui->xml, info->use_ssl); + service->no_ssl = glade_xml_get_widget (gui->xml, info->ssl_disabled); + + /* configure ui for current settings */ + if (url->host) { + if (url->port) { + char *host = g_strdup_printf("%s:%d", url->host, url->port); + + gtk_entry_set_text(service->hostname, host); + g_free(host); + } else + gtk_entry_set_text(service->hostname, url->host); + } + if (url->user) + gtk_entry_set_text(service->username, url->user); + if (service->path && url->path) + gtk_entry_set_text(service->path, url->path); + + tmp = camel_url_get_param(url, "use_ssl"); + if (tmp == NULL) + tmp = "never"; + + for (i=0;i<num_ssl_options;i++) { + if (!strcmp(ssl_options[i].value, tmp)) { + gtk_combo_box_set_active(service->use_ssl, i); + break; + } + } + camel_url_free(url); + + g_signal_connect (service->hostname, "changed", G_CALLBACK (emae_hostname_changed), service); + g_signal_connect (service->username, "changed", G_CALLBACK (emae_username_changed), service); + if (service->path) + g_signal_connect (service->path, "changed", G_CALLBACK (emae_path_changed), service); + + g_signal_connect(service->use_ssl, "changed", G_CALLBACK(emae_ssl_changed), service); + + service->auth_frame = glade_xml_get_widget(gui->xml, info->auth_frame); + service->remember = emae_account_toggle(emae, info->remember_password, info->save_passwd_key); + service->check_supported = (GtkButton *)glade_xml_get_widget(gui->xml, info->authtype_check); + if (info->needs_auth) { + service->needs_auth = (GtkToggleButton *)glade_xml_get_widget (gui->xml, info->needs_auth); + g_signal_connect(service->needs_auth, "toggled", G_CALLBACK(emae_needs_auth), service); + emae_needs_auth(service->needs_auth, service); + } + + emae_setup_providers(emae, service); + service->authtype = (GtkComboBox *)emae_setup_authtype(emae, service); + + if (!e_account_writable (emae->account, info->account_uri_key)) + gtk_widget_set_sensitive(service->container, FALSE); + else + gtk_widget_set_sensitive(service->container, TRUE); + + emae_service_provider_changed(service); +} + +static struct { + char *name; + int item; +} emae_identity_entries[] = { + { "management_name", E_ACCOUNT_NAME }, + { "identity_full_name", E_ACCOUNT_ID_NAME }, + { "identity_address", E_ACCOUNT_ID_ADDRESS }, + { "identity_reply_to", E_ACCOUNT_ID_REPLY_TO }, + { "identity_organization", E_ACCOUNT_ID_ORGANIZATION }, +}; + +static GtkWidget * +emae_identity_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + EAccount *account = emae->account; + int i; + GtkWidget *w; + + if (old) + return old; + + /* Management & Identity fields*/ + for (i=0;i<sizeof(emae_identity_entries)/sizeof(emae_identity_entries[0]);i++) + emae_account_entry(emae, emae_identity_entries[i].name, emae_identity_entries[i].item); + + gui->default_account = GTK_TOGGLE_BUTTON (glade_xml_get_widget (gui->xml, "management_default")); + if (!mail_config_get_default_account () + || (account == mail_config_get_default_account ())) + gtk_toggle_button_set_active (gui->default_account, TRUE); + + if (emae->do_signature) { + emae_setup_signatures(emae); + } else { + /* TODO: this could/should probably be neater */ + gtk_widget_hide(glade_xml_get_widget(gui->xml, "sigLabel")); + gtk_widget_hide(glade_xml_get_widget(gui->xml, "sigOption")); + gtk_widget_hide(glade_xml_get_widget(gui->xml, "sigAddNew")); + } + + w = glade_xml_get_widget(gui->xml, item->label); + if (((EConfig *)gui->config)->type == E_CONFIG_DRUID) { + GtkWidget *page = glade_xml_get_widget(gui->druidxml, "identity_page"); + + /* need to set packing? */ + gtk_widget_reparent(w, ((GnomeDruidPageStandard *)page)->vbox); + + return page; + } + + return w; +} + +static GtkWidget * +emae_receive_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + GtkWidget *w; + + if (old) + return old; + + gui->source.type = CAMEL_PROVIDER_STORE; + emae_setup_service(emae, &gui->source); + + w = glade_xml_get_widget(gui->xml, item->label); + if (((EConfig *)gui->config)->type == E_CONFIG_DRUID) { + GtkWidget *page = glade_xml_get_widget(gui->druidxml, "source_page"); + + /* need to set packing? */ + gtk_widget_reparent(w, ((GnomeDruidPageStandard *)page)->vbox); + + return page; + } + + return w; +} + +static void +emae_option_toggle_changed(GtkToggleButton *toggle, EMAccountEditorService *service) +{ + const char *name = g_object_get_data((GObject *)toggle, "option-name"); + GSList *depl = g_object_get_data((GObject *)toggle, "dependent-list"); + int active = gtk_toggle_button_get_active(toggle); + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + for (;depl;depl = g_slist_next(depl)) + gtk_widget_set_sensitive((GtkWidget *)depl->data, active); + + camel_url_set_param(url, name, active?"":NULL); + emae_uri_changed(service, url); + camel_url_free(url); +} + +static GtkWidget * +emae_option_toggle(EMAccountEditorService *service, CamelURL *url, const char *text, const char *name, int def) +{ + GtkWidget *w; + + /* FIXME: how do we get the default value ever? */ + w = g_object_new(gtk_check_button_get_type(), + "label", text, + "active", camel_url_get_param(url, name) != NULL, + NULL); + g_object_set_data((GObject *)w, "option-name", (void *)name); + g_signal_connect(w, "toggled", G_CALLBACK(emae_option_toggle_changed), service); + gtk_widget_show(w); + + printf("adding option toggle '%s'\n", text); + + return w; +} + +static void +emae_option_entry_changed(GtkEntry *entry, EMAccountEditorService *service) +{ + const char *name = g_object_get_data((GObject *)entry, "option-name"); + const char *text = gtk_entry_get_text(entry); + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + camel_url_set_param(url, name, text && text[0]?text:NULL); + emae_uri_changed(service, url); + camel_url_free(url); +} + +static GtkWidget * +emae_option_entry(EMAccountEditorService *service, CamelURL *url, const char *name, const char *def) +{ + GtkWidget *w; + const char *val = camel_url_get_param(url, name); + + if (val == NULL) + val = def; + + w = g_object_new(gtk_entry_get_type(), + "text", val, + NULL); + g_object_set_data((GObject *)w, "option-name", (void *)name); + g_signal_connect(w, "changed", G_CALLBACK(emae_option_entry_changed), service); + gtk_widget_show(w); + + return w; +} + +static void +emae_option_checkspin_changed(GtkSpinButton *spin, EMAccountEditorService *service) +{ + const char *name = g_object_get_data((GObject *)spin, "option-name"); + char value[16]; + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + sprintf(value, "%d", gtk_spin_button_get_value_as_int(spin)); + camel_url_set_param(url, name, value); + emae_uri_changed(service, url); + camel_url_free(url); +} + +static void +emae_option_checkspin_check_changed(GtkToggleButton *toggle, EMAccountEditorService *service) +{ + const char *name = g_object_get_data((GObject *)toggle, "option-name"); + GtkSpinButton *spin = g_object_get_data((GObject *)toggle, "option-target"); + + if (gtk_toggle_button_get_active(toggle)) { + gtk_widget_set_sensitive((GtkWidget *)spin, TRUE); + emae_option_checkspin_changed(spin, service); + } else { + CamelURL *url = emae_account_url(service->emae, emae_service_info[service->type].account_uri_key); + + camel_url_set_param(url, name, NULL); + gtk_widget_set_sensitive((GtkWidget *)spin, FALSE); + emae_uri_changed(service, url); + camel_url_free(url); + } +} + +/* this is a fugly api */ +static GtkWidget * +emae_option_checkspin(EMAccountEditorService *service, CamelURL *url, const char *name, const char *fmt, const char *info) +{ + GtkWidget *hbox, *check, *spin, *label; + double min, def, max; + char *pre, *post; + const char *val; + char on; + int enable; + + pre = g_alloca(strlen(fmt)+1); + strcpy(pre, fmt); + post = strstr(pre, "%s"); + if (post) { + *post = 0; + post+=2; + } + + if (sscanf(info, "%c:%lf:%lf:%lf", &on, &min, &def, &max) != 4) { + min = 0.0; + def = 0.0; + max = 1.0; + } + + if ((enable = (val = camel_url_get_param(url, name)) != NULL) ) + def = strtod(val, NULL); + else + enable = (on == 'y'); + + hbox = gtk_hbox_new(FALSE, 0); + check = g_object_new(gtk_check_button_get_type(), "label", pre, "active", enable, NULL); + spin = gtk_spin_button_new((GtkAdjustment *)gtk_adjustment_new(def, min, max, 1, 1, 1), 1, 0); + if (post) + label = gtk_label_new(post); + gtk_box_pack_start((GtkBox *)hbox, check, FALSE, TRUE, 0); + gtk_box_pack_start((GtkBox *)hbox, spin, FALSE, TRUE, 0); + if (label) + gtk_box_pack_start((GtkBox *)hbox, label, FALSE, TRUE, 4); + + g_object_set_data((GObject *)spin, "option-name", (void *)name); + g_object_set_data((GObject *)check, "option-name", (void *)name); + g_object_set_data((GObject *)check, "option-target", (void *)spin); + + g_signal_connect(spin, "value_changed", G_CALLBACK(emae_option_checkspin_changed), service); + g_signal_connect(check, "toggled", G_CALLBACK(emae_option_checkspin_check_changed), service); + + gtk_widget_show_all(hbox); + + return hbox; +} + +static GtkWidget * +emae_receive_options_item(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + GtkWidget *w, *box; + int row; + + if (emae->priv->source.provider == NULL + || emae->priv->source.provider->extra_conf == NULL) + return NULL; + + if (old) + return old; + + /* We have to add the automatic mail check item with the rest of the receive options */ + row = ((GtkTable *)parent)->nrows; + + box = gtk_hbox_new(FALSE, 4); + w = gtk_check_button_new_with_label(_("Automatically check for _new mail every")); + emae_account_toggle_widget(emae, (GtkToggleButton *)w, E_ACCOUNT_SOURCE_AUTO_CHECK); + gtk_box_pack_start((GtkBox *)box, w, FALSE, FALSE, 0); + + w = gtk_spin_button_new_with_range(1.0, 1440.0, 1.0); + emae_account_spinint_widget(emae, (GtkSpinButton *)w, E_ACCOUNT_SOURCE_AUTO_CHECK_TIME); + gtk_box_pack_start((GtkBox *)box, w, FALSE, TRUE, 0); + + w = gtk_label_new(_("minutes")); + gtk_box_pack_start((GtkBox *)box, w, FALSE, FALSE, 0); + + gtk_widget_show_all(box); + + gtk_table_attach((GtkTable *)parent, box, 0, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0); + + return box; +} + +static GtkWidget * +emae_receive_options_extra_item(EConfig *ec, EConfigItem *eitem, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + struct _receive_options_item *item = (struct _receive_options_item *)eitem; + GtkWidget *w, *l; + CamelProviderConfEntry *entries; + GtkWidget *depw; + GSList *depl = NULL, *widgets = NULL, *n; + EMAccountEditorService *service = &emae->priv->source; + int row, i; + GHashTable *extra; + CamelURL *url; + + /* Clean up any widgets we had setup before */ + if (item->widgets) { + g_slist_foreach(item->widgets, (GFunc)gtk_widget_destroy, NULL); + g_hash_table_destroy(item->extra_table); + item->widgets = NULL; + item->extra_table = NULL; + } + + if (emae->priv->source.provider == NULL + || emae->priv->source.provider->extra_conf == NULL) + return NULL; + + entries = emae->priv->source.provider->extra_conf; + for (i=0;entries && entries[i].type != CAMEL_PROVIDER_CONF_END;i++) + if (entries[i].type == CAMEL_PROVIDER_CONF_SECTION_START + && entries[i].name + && strcmp(entries[i].name, eitem->user_data) == 0) + goto section; + + return NULL; +section: + printf("Building extra section '%s'\n", eitem->path); + + url = emae_account_url(emae, emae_service_info[service->type].account_uri_key); + item->extra_table = g_hash_table_new(g_str_hash, g_str_equal); + extra = g_hash_table_new(g_str_hash, g_str_equal); + row = ((GtkTable *)parent)->nrows; + + for (;entries[i].type != CAMEL_PROVIDER_CONF_END && entries[i].type != CAMEL_PROVIDER_CONF_SECTION_END;i++) { + if (entries[i].depname) { + depw = g_hash_table_lookup(extra, entries[i].depname); + if (depw) + depl = g_object_steal_data((GObject *)depw, "dependent-list"); + } else + depw = NULL; + + switch (entries[i].type) { + case CAMEL_PROVIDER_CONF_SECTION_START: + case CAMEL_PROVIDER_CONF_SECTION_END: + break; + case CAMEL_PROVIDER_CONF_LABEL: + /* FIXME: This is a hack for exchange connector, labels should be removed from confentry */ + if (!strcmp(entries[i].name, "hostname")) + l = glade_xml_get_widget(emae->priv->xml, "source_host_label"); + else if (!strcmp(entries[i].name, "username")) + l = glade_xml_get_widget(emae->priv->xml,"source_user_label"); + else + l = NULL; + + if (l) { + gtk_label_set_text_with_mnemonic((GtkLabel *)l, entries[i].text); + if (depw) + depl = g_slist_prepend(depl, l); + } + break; + case CAMEL_PROVIDER_CONF_CHECKBOX: + w = emae_option_toggle(service, url, entries[i].text, entries[i].name, atoi(entries[i].value)); + gtk_table_attach((GtkTable *)parent, w, 0, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0); + g_hash_table_insert(extra, entries[i].name, w); + if (depw) + depl = g_slist_prepend(depl, w); + widgets = g_slist_prepend(widgets, w); + row++; + break; + case CAMEL_PROVIDER_CONF_ENTRY: + l = g_object_new(gtk_label_get_type(), "label", entries[i].text, "xalign", 0.0, NULL); + gtk_widget_show(l); + w = emae_option_entry(service, url, entries[i].name, entries[i].value); + gtk_table_attach((GtkTable *)parent, l, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); + gtk_table_attach((GtkTable *)parent, w, 1, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0); + if (depw) { + depl = g_slist_prepend(depl, w); + depl = g_slist_prepend(depl, l); + } + widgets = g_slist_prepend(widgets, w); + widgets = g_slist_prepend(widgets, l); + row++; + /* FIXME: this is another hack for exchange/groupwise connector */ + g_hash_table_insert(item->extra_table, entries[i].name, w); + break; + case CAMEL_PROVIDER_CONF_CHECKSPIN: + w = emae_option_checkspin(service, url, entries[i].name, entries[i].text, entries[i].value); + gtk_table_attach((GtkTable *)parent, w, 0, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0); + if (depw) + depl = g_slist_prepend(depl, w); + widgets = g_slist_prepend(widgets, w); + row++; + break; + default: + break; + } + + if (depw && depl) { + int act = gtk_toggle_button_get_active((GtkToggleButton *)depw); + + g_object_set_data_full((GObject *)depw, "dependent-list", depl, (GDestroyNotify)g_slist_free); + for (n=depl;n;n=g_slist_next(n)) + gtk_widget_set_sensitive((GtkWidget *)n->data, act); + } + } + + camel_url_free(url); + + /* Since EConfig destroys the factory widget when it changes, we + * need to destroy our own ones as well, and add a dummy item + * so it knows this section isn't empty */ + + w = gtk_label_new(""); + gtk_widget_hide(w); + gtk_table_attach((GtkTable *)parent, w, 0, 2, row, row+1, 0, 0, 0, 0); + item->widgets = widgets; + + return w; +} + +static GtkWidget * +emae_send_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + GtkWidget *w; + + if (old) + return old; + + /* Transport */ + gui->transport.type = CAMEL_PROVIDER_TRANSPORT; + emae_setup_service(emae, &gui->transport); + + w = glade_xml_get_widget(gui->xml, item->label); + if (((EConfig *)gui->config)->type == E_CONFIG_DRUID) { + GtkWidget *page = glade_xml_get_widget(gui->druidxml, "transport_page"); + + /* need to set packing? */ + gtk_widget_reparent(w, ((GnomeDruidPageStandard *)page)->vbox); + + return page; + } + + return w; +} + +static GtkWidget * +emae_defaults_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + + if (old) + return old; + + /* Special folders */ + gui->drafts_folder_button = (GtkButton *)emae_account_folder(emae, "drafts_button", E_ACCOUNT_DRAFTS_FOLDER_URI, MAIL_COMPONENT_FOLDER_DRAFTS); + gui->sent_folder_button = (GtkButton *)emae_account_folder(emae, "sent_button", E_ACCOUNT_SENT_FOLDER_URI, MAIL_COMPONENT_FOLDER_SENT); + + /* Special Folders "Reset Defaults" button */ + gui->restore_folders_button = (GtkButton *)glade_xml_get_widget (gui->xml, "default_folders_button"); + g_signal_connect (gui->restore_folders_button, "clicked", G_CALLBACK (default_folders_clicked), emae); + + /* Always Cc/Bcc */ + emae_account_toggle(emae, "always_cc", E_ACCOUNT_CC_ALWAYS); + emae_account_entry(emae, "cc_addrs", E_ACCOUNT_CC_ADDRS); + emae_account_toggle(emae, "always_bcc", E_ACCOUNT_BCC_ALWAYS); + emae_account_entry(emae, "bcc_addrs", E_ACCOUNT_BCC_ADDRS); + + gtk_widget_set_sensitive((GtkWidget *)gui->drafts_folder_button, e_account_writable(emae->account, E_ACCOUNT_DRAFTS_FOLDER_URI)); + gtk_widget_set_sensitive((GtkWidget *)gui->sent_folder_button, e_account_writable(emae->account, E_ACCOUNT_SENT_FOLDER_URI)); + gtk_widget_set_sensitive((GtkWidget *)gui->restore_folders_button, + e_account_writable(emae->account, E_ACCOUNT_SENT_FOLDER_URI) + || e_account_writable(emae->account, E_ACCOUNT_DRAFTS_FOLDER_URI)); + + return glade_xml_get_widget(gui->xml, item->label); +} + +static GtkWidget * +emae_security_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + + if (old) + return old; + + /* Security */ + emae_account_entry(emae, "pgp_key", E_ACCOUNT_PGP_KEY); + emae_account_toggle(emae, "pgp_encrypt_to_self", E_ACCOUNT_PGP_ENCRYPT_TO_SELF); + emae_account_toggle(emae, "pgp_always_sign", E_ACCOUNT_PGP_ALWAYS_SIGN); + emae_account_toggle(emae, "pgp_no_imip_sign", E_ACCOUNT_PGP_NO_IMIP_SIGN); + emae_account_toggle(emae, "pgp_always_trust", E_ACCOUNT_PGP_ALWAYS_TRUST); + +#if defined (HAVE_NSS) + /* TODO: this should handle its entry separately? */ + gui->smime_sign_key = emae_account_entry(emae, "smime_sign_key", E_ACCOUNT_SMIME_SIGN_KEY); + gui->smime_sign_key_select = (GtkButton *)glade_xml_get_widget (gui->xml, "smime_sign_key_select"); + gui->smime_sign_key_clear = (GtkButton *)glade_xml_get_widget (gui->xml, "smime_sign_key_clear"); + g_signal_connect(gui->smime_sign_key_select, "clicked", G_CALLBACK(smime_sign_key_select), emae); + g_signal_connect(gui->smime_sign_key_clear, "clicked", G_CALLBACK(smime_sign_key_clear), emae); + + gui->smime_sign_default = emae_account_toggle(emae, "smime_sign_default", E_ACCOUNT_SMIME_SIGN_DEFAULT); + + gui->smime_encrypt_key = emae_account_entry(emae, "smime_encrypt_key", E_ACCOUNT_SMIME_ENCRYPT_KEY); + gui->smime_encrypt_key_select = (GtkButton *)glade_xml_get_widget (gui->xml, "smime_encrypt_key_select"); + gui->smime_encrypt_key_clear = (GtkButton *)glade_xml_get_widget (gui->xml, "smime_encrypt_key_clear"); + g_signal_connect(gui->smime_encrypt_key_select, "clicked", G_CALLBACK(smime_encrypt_key_select), emae); + g_signal_connect(gui->smime_encrypt_key_clear, "clicked", G_CALLBACK(smime_encrypt_key_clear), emae); + + gui->smime_encrypt_default = emae_account_toggle(emae, "smime_encrypt_default", E_ACCOUNT_SMIME_ENCRYPT_DEFAULT); + gui->smime_encrypt_to_self = emae_account_toggle(emae, "smime_encrypt_to_self", E_ACCOUNT_SMIME_ENCRYPT_TO_SELF); + smime_changed(emae); +#else + { + /* Since we don't have NSS, hide the S/MIME config options */ + GtkWidget *frame; + + frame = glade_xml_get_widget (gui->xml, "smime_vbox"); + gtk_widget_destroy (frame); + } +#endif /* HAVE_NSS */ + + return glade_xml_get_widget(gui->xml, item->label); +} + +static GtkWidget * +emae_widget_glade(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + + if (old) + return old; + + printf("getting widget '%s' = %p\n", item->label, glade_xml_get_widget(emae->priv->xml, item->label)); + + return glade_xml_get_widget(emae->priv->xml, item->label); +} + +/* plugin meta-data for "com.novell.evolution.mail.config.accountEditor" */ +static EMConfigItem emae_editor_items[] = { + { E_CONFIG_BOOK, "", "account_editor_notebook", emae_widget_glade }, + { E_CONFIG_PAGE, "00.identity", "vboxIdentityBorder", emae_identity_page }, + { E_CONFIG_SECTION, "00.identity/00.name", "account_vbox", emae_widget_glade }, + /* table not vbox: { E_CONFIG_SECTION, "00.identity/10.required", "identity_required_table", emae_widget_glade }, */ + /* table not vbox: { E_CONFIG_SECTION, "00.identity/20.info", "identity_optional_table", emae_widget_glade }, */ + + { E_CONFIG_PAGE, "10.receive", "vboxSourceBorder", emae_receive_page }, + /* table not vbox: { E_CONFIG_SECTION, "10.receive/00.type", "source_type_table", emcp_widget_glade }, */ + /* table not vbox: { E_CONFIG_SECTION, "10.receive/10.config", "table13", emcp_widget_glade }, */ + { E_CONFIG_SECTION, "10.receive/20.security", "vbox181", emae_widget_glade }, + { E_CONFIG_SECTION, "10.receive/30.auth", "vbox179", emae_widget_glade }, + + /* Most sections for this is auto-generated fromt the camel config */ + { E_CONFIG_PAGE, "20.receive_options", N_("Receiving Options"), }, + { E_CONFIG_SECTION_TABLE, "20.receive_options/10.mailcheck", N_("Checking for New Mail"), }, + { E_CONFIG_ITEM_TABLE, "20.receive_options/10.mailcheck/00.autocheck", NULL, emae_receive_options_item, }, + + { E_CONFIG_PAGE, "30.send", "vboxTransportBorder", emae_send_page }, + /* table not vbox: { E_CONFIG_SECTION, "30.send/00.type", "transport_type_table", emcp_widget_glade }, */ + { E_CONFIG_SECTION, "30.send/10.config", "vbox12", emae_widget_glade }, + { E_CONFIG_SECTION, "30.send/20.security", "vbox183", emae_widget_glade }, + { E_CONFIG_SECTION, "30.send/30.auth", "vbox61", emae_widget_glade }, + + { E_CONFIG_PAGE, "40.defaults", "vboxFoldersBorder", emae_defaults_page }, + { E_CONFIG_SECTION, "40.defaults/00.folders", "vbox184", emae_widget_glade }, + /* table not vbox: { E_CONFIG_SECTION, "40.defaults/10.composing", "table8", emae_widget_glade }, */ + + { E_CONFIG_PAGE, "50.security", "vboxSecurityBorder", emae_security_page }, + /* 1x1 table(!) not vbox: { E_CONFIG_SECTION, "50.security/00.gpg", "table19", emae_widget_glade }, */ + /* table not vbox: { E_CONFIG_SECTION, "50.security/10.smime", "smime_table", emae_widget_glade }, */ + { 0 }, +}; + +static GtkWidget * +emae_management_page(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + EMAccountEditorPrivate *gui = emae->priv; + GtkWidget *w; + + if (old) + return old; + + w = glade_xml_get_widget(gui->xml, item->label); + if (((EConfig *)gui->config)->type == E_CONFIG_DRUID) { + GtkWidget *page = glade_xml_get_widget(gui->druidxml, "management_page"); + + /* need to set packing? */ + gtk_widget_reparent(w, ((GnomeDruidPageStandard *)page)->vbox); + + return page; + } + + return w; +} + +static GtkWidget * +emae_widget_druid_glade(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMAccountEditor *emae = data; + GtkWidget *w; + + if (old) + return old; + + printf("getting widget '%s' = %p\n", item->label, glade_xml_get_widget(emae->priv->druidxml, item->label)); + + w = glade_xml_get_widget(emae->priv->druidxml, item->label); + /* i think the glade file has issues, we need to show all on at least the end page */ + gtk_widget_show_all(w); + + return w; +} + +/* plugin meta-data for "com.novell.evolution.mail.config.accountDruid" */ +static EMConfigItem emae_druid_items[] = { + { E_CONFIG_DRUID, "", "druid", emae_widget_druid_glade }, + { E_CONFIG_PAGE_START, "0.start", "start_page", emae_widget_druid_glade }, + + { E_CONFIG_PAGE, "00.identity", "vboxIdentityBorder", emae_identity_page }, + { E_CONFIG_SECTION, "00.identity/00.name", "account_vbox", emae_widget_glade }, + /* table not vbox: { E_CONFIG_SECTION, "00.identity/10.required", "identity_required_table", emae_widget_glade }, */ + /* table not vbox: { E_CONFIG_SECTION, "00.identity/20.info", "identity_optional_table", emae_widget_glade }, */ + + { E_CONFIG_PAGE, "10.receive", "vboxSourceBorder", emae_receive_page }, + /* table not vbox: { E_CONFIG_SECTION, "10.receive/00.type", "source_type_table", emcp_widget_glade }, */ + /* table not vbox: { E_CONFIG_SECTION, "10.receive/10.config", "table13", emcp_widget_glade }, */ + { E_CONFIG_SECTION, "10.receive/20.security", "vbox181", emae_widget_glade }, + { E_CONFIG_SECTION, "10.receive/30.auth", "vbox179", emae_widget_glade }, + + /* Most sections for this is auto-generated fromt the camel config */ + { E_CONFIG_PAGE, "20.receive_options", N_("Receiving Options"), }, + { E_CONFIG_SECTION_TABLE, "20.receive_options/10.mailcheck", N_("Checking for New Mail"), }, + { E_CONFIG_ITEM_TABLE, "20.receive_options/10.mailcheck/00.autocheck", NULL, emae_receive_options_item, }, + + { E_CONFIG_PAGE, "30.send", "vboxTransportBorder", emae_send_page }, + /* table not vbox: { E_CONFIG_SECTION, "30.send/00.type", "transport_type_table", emcp_widget_glade }, */ + { E_CONFIG_SECTION, "30.send/10.config", "vbox12", emae_widget_glade }, + { E_CONFIG_SECTION, "30.send/20.security", "vbox183", emae_widget_glade }, + { E_CONFIG_SECTION, "30.send/30.auth", "vbox61", emae_widget_glade }, + + { E_CONFIG_PAGE, "40.management", "management_frame", emae_management_page }, + + { E_CONFIG_PAGE_FINISH, "999.end", "finish_page", emae_widget_druid_glade }, + { 0 }, +}; + +static void +emae_free(EConfig *ec, GSList *items, void *data) +{ + g_slist_free(items); +} + +static void +emae_free_auto(EConfig *ec, GSList *items, void *data) +{ + GSList *l, *n; + + for (l=items;l;) { + struct _receive_options_item *item = l->data; + + n = g_slist_next(l); + g_free(item->item.path); + if (item->extra_table) + g_hash_table_destroy(item->extra_table); + g_slist_free(item->widgets); + g_free(item); + g_slist_free_1(l); + l = n; + } +} + +static gboolean +emae_service_complete(EMAccountEditor *emae, EMAccountEditorService *service) +{ + CamelURL *url; + int ok = TRUE; + const char *uri; + + if (service->provider == NULL) + return TRUE; + + uri = e_account_get_string(emae->account, emae_service_info[service->type].account_uri_key); + if (uri == NULL || (url = camel_url_new(uri, NULL)) == NULL) + return FALSE; + + if (CAMEL_PROVIDER_NEEDS(service->provider, CAMEL_URL_PART_HOST) + && (url->host == NULL || url->host[0] == 0)) + ok = FALSE; + + /* We only need the user if the service needs auth as well, i think */ + if (ok + && (service->needs_auth == NULL + || CAMEL_PROVIDER_NEEDS(service->provider, CAMEL_URL_PART_AUTH) + || gtk_toggle_button_get_active(service->needs_auth)) + && CAMEL_PROVIDER_NEEDS(service->provider, CAMEL_URL_PART_USER) + && (url->user == NULL || url->user[0] == 0)) + ok = FALSE; + + if (ok + && CAMEL_PROVIDER_NEEDS(service->provider, CAMEL_URL_PART_PATH) + && (url->path == NULL || url->path[0] == 0)) + ok = FALSE; + + camel_url_free(url); + + return ok; +} + +static gboolean +emae_check_complete(EConfig *ec, const char *pageid, void *data) +{ + EMAccountEditor *emae = data; + int ok = TRUE; + const char *tmp; + EAccount *ea; + + if (pageid == NULL || !strcmp(pageid, "00.identity")) { + /* TODO: check the account name is set, and unique in the account list */ + ok = (tmp = e_account_get_string(emae->account, E_ACCOUNT_ID_NAME)) + && tmp[0] + && (tmp = e_account_get_string(emae->account, E_ACCOUNT_ID_ADDRESS)) + && is_email(tmp) + && ((tmp = e_account_get_string(emae->account, E_ACCOUNT_ID_REPLY_TO)) == NULL + || tmp[0] == 0 + || is_email(tmp)); + if (!ok) + printf("identity incomplete\n"); + } + + if (ok && (pageid == NULL || !strcmp(pageid, "10.receive"))) { + ok = emae_service_complete(emae, &emae->priv->source); + if (!ok) + printf("receive page incomplete\n"); + } + + if (ok && (pageid == NULL || !strcmp(pageid, "30.send"))) { + ok = emae_service_complete(emae, &emae->priv->transport); + if (!ok) + printf("send page incomplete\n"); + } + + if (ok && (pageid == NULL || !strcmp(pageid, "40.management"))) { + ok = (tmp = e_account_get_string(emae->account, E_ACCOUNT_NAME)) + && tmp[0] + && ((ea = mail_config_get_account_by_name(tmp)) == NULL + || ea == emae->original); + if (!ok) + printf("management page incomplete\n"); + } + + /* We use the page-check of various pages to 'prepare' or + pre-load their values, only in the druid */ + if (pageid + && ((EConfig *)emae->priv->config)->type == E_CONFIG_DRUID) { + if (!strcmp(pageid, "00.identity")) { + if (!emae->priv->identity_set) { + char *uname; + + emae->priv->identity_set = 1; + uname = g_locale_to_utf8(g_get_real_name(), -1, NULL, NULL, NULL); + if (uname) { + gtk_entry_set_text((GtkEntry *)glade_xml_get_widget(emae->priv->xml, "management_name"), uname); + g_free(uname); + } + } + } else if (!strcmp(pageid, "10.receive")) { + if (!emae->priv->receive_set) { + char *user, *at; + + emae->priv->receive_set = 1; + tmp = e_account_get_string(emae->account, E_ACCOUNT_ID_ADDRESS); + at = strchr(tmp, '@'); + user = g_alloca(at-tmp+1); + memcpy(user, tmp, at-tmp); + user[at-tmp] = 0; + gtk_entry_set_text(emae->priv->source.username, user); + gtk_entry_set_text(emae->priv->transport.username, user); + } + } else if (!strcmp(pageid, "20.receive_options")) { + if (emae->priv->source.provider + && emae->priv->extra_provider != emae->priv->source.provider) { + emae->priv->extra_provider = emae->priv->source.provider; + emae_auto_detect(emae); + } + } else if (!strcmp(pageid, "40.management")) { + if (!emae->priv->management_set) { + char *template; + unsigned int i = 0, len; + + emae->priv->management_set = 1; + tmp = e_account_get_string(emae->account, E_ACCOUNT_ID_ADDRESS); + len = strlen(tmp); + template = alloca(len + 14); + strcpy(template, tmp); + while (mail_config_get_account_by_name(template)) + sprintf(template + len, " (%d)", i++); + + gtk_entry_set_text((GtkEntry *)glade_xml_get_widget(emae->priv->xml, "management_name"), template); + } + } + } + + return ok; +} + +/* HACK: FIXME: the component should listen to the account object directly */ +static void +add_new_store (char *uri, CamelStore *store, void *user_data) +{ + MailComponent *component = mail_component_peek (); + EAccount *account = user_data; + + if (store == NULL) + return; + + mail_component_add_store (component, store, account->name); +} + +static void +emae_commit(EConfig *ec, GSList *items, void *data) +{ + EMAccountEditor *emae = data; + EAccountList *accounts = mail_config_get_accounts(); + EAccount *account; + + /* the mail-config*acconts* api needs a lot of work */ + + if (emae->original) { + printf("Committing account '%s'\n", e_account_get_string(emae->account, E_ACCOUNT_NAME)); + e_account_import(emae->original, emae->account); + account = emae->original; + e_account_list_change(accounts, account); + } else { + printf("Adding new account '%s'\n", e_account_get_string(emae->account, E_ACCOUNT_NAME)); + e_account_list_add(accounts, emae->account); + account = emae->account; + + /* HACK: this will add the account to the folder tree. + We should just be listening to the account list directly for changed events */ + if (account->enabled + && emae->priv->source.provider + && (emae->priv->source.provider->flags & CAMEL_PROVIDER_IS_STORAGE)) + mail_get_store(e_account_get_string(emae->account, E_ACCOUNT_SOURCE_URL), NULL, add_new_store, account); + } + + if (gtk_toggle_button_get_active(emae->priv->default_account)) + e_account_list_set_default(accounts, account); + + e_account_list_save(accounts); +} + +void +em_account_editor_construct(EMAccountEditor *emae, EAccount *account, em_account_editor_t type) +{ + EMAccountEditorPrivate *gui = emae->priv; + int i, index; + GSList *l; + GList *prov; + EMConfig *ec; + EMConfigTargetAccount *target; + GHashTable *have; + EConfigItem *items; + + emae->type = type; + emae->original = account; + if (emae->original) { + char *xml; + + g_object_ref(emae->original); + xml = e_account_to_xml(emae->original); + emae->account = e_account_new_from_xml(xml); + g_free(xml); + + emae->do_signature = TRUE; + } else { + /* TODO: have a get_default_account thing?? */ + emae->account = e_account_new(); + emae->account->enabled = TRUE; + e_account_set_string(emae->account, E_ACCOUNT_DRAFTS_FOLDER_URI, + mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_DRAFTS)); + e_account_set_string(emae->account, E_ACCOUNT_SENT_FOLDER_URI, + mail_component_get_folder_uri(NULL, MAIL_COMPONENT_FOLDER_SENT)); + } + + gui->xml = glade_xml_new(EVOLUTION_GLADEDIR "/mail-config.glade", "account_editor_notebook", NULL); + if (type == EMAE_DRUID) + gui->druidxml = glade_xml_new(EVOLUTION_GLADEDIR "/mail-config.glade", "druid", NULL); + + /* sort the providers, remote first */ + gui->providers = g_list_sort(camel_provider_list(TRUE), (GCompareFunc)provider_compare); + + if (type == EMAE_NOTEBOOK) { + ec = em_config_new(E_CONFIG_BOOK, "com.novell.evolution.mail.config.accountEditor"); + items = emae_editor_items; + } else { + ec = em_config_new(E_CONFIG_DRUID, "com.novell.evolution.mail.config.accountDruid"); + items = emae_druid_items; + } + + emae->config = gui->config = ec; + l = NULL; + for (i=0;items[i].path;i++) + l = g_slist_prepend(l, &items[i]); + e_config_add_items((EConfig *)ec, l, emae_commit, NULL, emae_free, emae); + + /* This is kinda yuck, we're dynamically mapping from the 'old style' extensibility api to the new one */ + l = NULL; + have = g_hash_table_new(g_str_hash, g_str_equal); + index = 20; + for (prov=gui->providers;prov;prov=g_list_next(prov)) { + CamelProviderConfEntry *entries = ((CamelProvider *)prov->data)->extra_conf; + + for (i=0;entries && entries[i].type != CAMEL_PROVIDER_CONF_END;i++) { + struct _receive_options_item *item; + char *name = entries[i].name; + int myindex = index; + + if (entries[i].type != CAMEL_PROVIDER_CONF_SECTION_START + || name == NULL + || g_hash_table_lookup(have, name)) + continue; + + /* override mailcheck since we also insert our own mailcheck item at this index */ + if (name && !strcmp(name, "mailcheck")) + myindex = 10; + + item = g_malloc0(sizeof(*item)); + item->item.type = E_CONFIG_SECTION_TABLE; + item->item.path = g_strdup_printf("20.receive_options/%02d.%s", myindex, name?name:"unnamed"); + item->item.label = entries[i].text; + + l = g_slist_prepend(l, item); + + item = g_malloc0(sizeof(*item)); + item->item.type = E_CONFIG_ITEM_TABLE; + item->item.path = g_strdup_printf("20.receive_options/%02d.%s/80.camelitem", myindex, name?name:"unnamed"); + item->item.factory = emae_receive_options_extra_item; + item->item.user_data = entries[i].name; + + l = g_slist_prepend(l, item); + + index += 10; + g_hash_table_insert(have, entries[i].name, have); + } + } + g_hash_table_destroy(have); + e_config_add_items((EConfig *)ec, l, NULL, NULL, emae_free_auto, emae); + gui->extra_items = l; + + e_config_add_page_check((EConfig *)ec, NULL, emae_check_complete, emae); + + target = em_config_target_new_account(ec, emae->account); + e_config_set_target((EConfig *)ec, (EConfigTarget *)target); + emae->editor = e_config_create_window((EConfig *)ec, NULL, type==EMAE_NOTEBOOK?_("Account Editor"):_("Evolution Account Assistant")); + + /* FIXME: need to hook onto destroy as required */ +} diff --git a/mail/em-account-editor.h b/mail/em-account-editor.h new file mode 100644 index 0000000000..0e84a07b7d --- /dev/null +++ b/mail/em-account-editor.h @@ -0,0 +1,86 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: + * Jeffrey Stedfast <fejj@ximian.com> + * Dan Winship <danw@ximian.com> + * + * Copyright 2001-2003 Ximian, Inc. (www.ximian.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * 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 Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + */ + +#ifndef EM_ACCOUNT_EDITOR_H +#define EM_ACCOUNT_EDITOR_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include <gtk/gtkvbox.h> + +struct _EAccount; + +typedef struct _EMAccountEditor EMAccountEditor; +typedef struct _EMAccountEditorClass EMAccountEditorClass; + +typedef enum { + EMAE_NOTEBOOK, + EMAE_DRUID, +} em_account_editor_t; + +struct _EMAccountEditor { + GObject gobject; + + struct _EMAccountEditorPrivate *priv; + + em_account_editor_t type; + struct _GtkWidget *editor; /* gtknotebook or druid, depending on type */ + + struct _EMConfig *config; /* driver object */ + + struct _EAccount *account; /* working account, must instant apply to this */ + struct _EAccount *original; /* original account, not changed unless commit is invoked */ + + int do_signature:1; /* allow editing signature */ +}; + +struct _EMAccountEditorClass { + GObjectClass gobject_class; +}; + +GType em_account_editor_get_type(void); + +void em_account_editor_construct(EMAccountEditor *emae, struct _EAccount *account, em_account_editor_t type); +EMAccountEditor *em_account_editor_new(struct _EAccount *account, em_account_editor_t type); + +gboolean em_account_editor_save (EMAccountEditor *gui); +void em_account_editor_destroy (EMAccountEditor *gui); + +gboolean em_account_editor_identity_complete (EMAccountEditor *gui, struct _GtkWidget **incomplete); +gboolean em_account_editor_source_complete (EMAccountEditor *gui, struct _GtkWidget **incomplete); +gboolean em_account_editor_transport_complete (EMAccountEditor *gui, struct _GtkWidget **incomplete); +gboolean em_account_editor_management_complete (EMAccountEditor *gui, struct _GtkWidget **incomplete); + +void em_account_editor_build_extra_conf (EMAccountEditor *gui, const char *url); + +void em_account_editor_auto_detect_extra_conf (EMAccountEditor *gui); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* EM_ACCOUNT_EDITOR_H */ diff --git a/mail/em-account-prefs.c b/mail/em-account-prefs.c index 348fb5fd83..2966171472 100644 --- a/mail/em-account-prefs.c +++ b/mail/em-account-prefs.c @@ -32,6 +32,8 @@ #include <gtk/gtkcellrenderertoggle.h> #include <gtk/gtkcellrenderertext.h> +#include <libgnome/gnome-i18n.h> + #include "mail-component.h" #include "mail-config.h" #include "mail-config-druid.h" @@ -44,6 +46,8 @@ #include "em-account-prefs.h" +#include "em-account-editor.h" + static void em_account_prefs_class_init (EMAccountPrefsClass *class); static void em_account_prefs_init (EMAccountPrefs *prefs); static void em_account_prefs_finalise (GObject *obj); @@ -132,13 +136,17 @@ account_add_finished (EMAccountPrefs *prefs, GObject *deadbeef) g_object_unref (prefs); } +#include "em-config.h" + static void account_add_clicked (GtkButton *button, gpointer user_data) { EMAccountPrefs *prefs = (EMAccountPrefs *) user_data; - GtkWidget *parent; if (prefs->druid == NULL) { +#if 0 + GtkWidget *parent; + prefs->druid = (GtkWidget *) mail_config_druid_new (); parent = gtk_widget_get_toplevel ((GtkWidget *) prefs); @@ -150,6 +158,12 @@ account_add_clicked (GtkButton *button, gpointer user_data) gtk_widget_show (prefs->druid); g_object_ref (prefs); +#else + EMAccountEditor *emae; + + emae = em_account_editor_new(NULL, EMAE_DRUID); + gtk_widget_show(emae->editor); +#endif } else { gdk_window_raise (prefs->druid->window); } @@ -182,8 +196,9 @@ account_edit_clicked (GtkButton *button, gpointer user_data) gtk_tree_model_get (model, &iter, 3, &account, -1); if (account) { +#if 0 GtkWidget *parent; - + parent = gtk_widget_get_toplevel ((GtkWidget *) prefs); parent = GTK_WIDGET_TOPLEVEL (parent) ? parent : NULL; @@ -192,6 +207,15 @@ account_edit_clicked (GtkButton *button, gpointer user_data) g_object_weak_ref ((GObject *) prefs->editor, (GWeakNotify) account_edit_finished, prefs); gtk_widget_show (prefs->editor); g_object_ref (prefs); +#endif + /* test foo */ + { + EMAccountEditor *emae; + + emae = em_account_editor_new(account, EMAE_NOTEBOOK); + gtk_widget_show(emae->editor); + } + } } else { gdk_window_raise (prefs->editor->window); @@ -231,8 +255,6 @@ account_delete_clicked (GtkButton *button, gpointer user_data) mail_config_write (); - mail_autoreceive_setup (); - gtk_list_store_remove ((GtkListStore *) model, &iter); len = e_list_length ((EList *) accounts); @@ -270,12 +292,30 @@ account_default_clicked (GtkButton *button, gpointer user_data) } static void -account_able_clicked (GtkButton *button, gpointer user_data) +account_able_changed(EAccount *account) { MailComponent *component = mail_component_peek (); + + /* FIXME: do this directly by listening to the mail accounts changed events in the relevant components */ + + if (account->source->url) { + if (account->enabled) + mail_component_load_store_by_uri (component, + account->source->url, + account->name); + else + mail_component_remove_store_by_uri (component, account->source->url); + } + + mail_config_write (); +} + +static void +account_able_clicked (GtkButton *button, gpointer user_data) +{ EMAccountPrefs *prefs = user_data; GtkTreeSelection *selection; - EAccount *account = NULL; + EAccount *account; GtkTreeModel *model; GtkTreeIter iter; @@ -286,23 +326,10 @@ account_able_clicked (GtkButton *button, gpointer user_data) gtk_list_store_set ((GtkListStore *) model, &iter, 0, account->enabled, -1); gtk_button_set_label (prefs->mail_able, account->enabled ? _("Disable") : _("Enable")); - } - - if (account) { - /* if the account got disabled, remove it from the - folder-tree, otherwise add it to the folder-tree */ - if (account->source->url) { - if (account->enabled) - mail_component_load_store_by_uri (component, - account->source->url, - account->name); - else - mail_component_remove_store_by_uri (component, account->source->url); - } - - mail_autoreceive_setup (); - - mail_config_write (); + + /* let the rest of the application know it changed */ + e_account_list_change(mail_config_get_accounts(), account); + account_able_changed(account); } } @@ -327,25 +354,13 @@ account_able_toggled (GtkCellRendererToggle *renderer, char *arg1, gpointer user if (gtk_tree_selection_iter_is_selected (selection, &iter)) gtk_button_set_label (prefs->mail_able, account->enabled ? _("Disable") : _("Enable")); + + /* let the rest of the application know it changed */ + e_account_list_change(mail_config_get_accounts(), account); + account_able_changed(account); } gtk_tree_path_free (path); - - if (account) { - MailComponent *component = mail_component_peek (); - - /* if the account got disabled, remove it from the - folder-tree, otherwise add it to the folder-tree */ - if (account->source->url) { - if (account->enabled) - mail_component_load_store_by_uri (component, account->source->url, account->name); - else - mail_component_remove_store_by_uri (component, account->source->url); - } - - mail_autoreceive_setup (); - mail_config_write (); - } } static void diff --git a/mail/em-composer-prefs.c b/mail/em-composer-prefs.c index 053d14cae6..f361d20e74 100644 --- a/mail/em-composer-prefs.c +++ b/mail/em-composer-prefs.c @@ -66,6 +66,7 @@ #include "mail-config.h" #include "mail-signature-editor.h" +#include "em-config.h" #define d(x) @@ -840,6 +841,36 @@ toggle_button_init (EMComposerPrefs *prefs, GtkToggleButton *toggle, int not, co gtk_widget_set_sensitive ((GtkWidget *) toggle, FALSE); } +static GtkWidget * +emcp_widget_glade(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMComposerPrefs *prefs = data; + + return glade_xml_get_widget(prefs->gui, item->label); +} + +/* plugin meta-data */ +static EMConfigItem emcp_items[] = { + { E_CONFIG_BOOK, "", "composer_toplevel", emcp_widget_glade }, + { E_CONFIG_PAGE, "00.general", "vboxGeneral", emcp_widget_glade }, + { E_CONFIG_SECTION, "00.general/00.behavior", "vboxBehavior", emcp_widget_glade }, + { E_CONFIG_SECTION, "00.general/10.alerts", "vboxAlerts", emcp_widget_glade }, + { E_CONFIG_PAGE, "10.signatures", "vboxSignatures", emcp_widget_glade }, + /* signature/signatures and signature/preview parts not usable */ + + { E_CONFIG_PAGE, "20.spellcheck", "vboxSpellChecking", emcp_widget_glade }, + { E_CONFIG_SECTION, "20.spellcheck/00.languages", "vbox178", emcp_widget_glade }, + { E_CONFIG_SECTION, "20.spellcheck/00.options", "vboxOptions", emcp_widget_glade }, +}; + +static void +emcp_free(EConfig *ec, GSList *items, void *data) +{ + /* the prefs data is freed automagically */ + + g_slist_free(items); +} + static void em_composer_prefs_construct (EMComposerPrefs *prefs) { @@ -850,22 +881,23 @@ em_composer_prefs_construct (EMComposerPrefs *prefs) GtkTreeSelection *selection; int style; char *buf; + EMConfig *ec; + EMConfigTargetPrefs *target; + GSList *l; + int i; prefs->gconf = mail_config_get_gconf_client (); - gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "composer_tab", NULL); + gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "composer_toplevel", NULL); prefs->gui = gui; prefs->sig_script_gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "vbox_add_script_signature", NULL); - - /* get our toplevel widget */ - toplevel = glade_xml_get_widget (gui, "toplevel"); - - /* reparent */ - gtk_widget_ref (toplevel); - gtk_container_remove (GTK_CONTAINER (toplevel->parent), toplevel); - gtk_container_add (GTK_CONTAINER (prefs), toplevel); - gtk_widget_unref (toplevel); - + + ec = em_config_new(E_CONFIG_BOOK, "com.novell.evolution.mail.composerPrefs"); + l = NULL; + for (i=0;i<sizeof(emcp_items)/sizeof(emcp_items[0]);i++) + l = g_slist_prepend(l, &emcp_items[i]); + e_config_add_items((EConfig *)ec, l, NULL, NULL, emcp_free, prefs); + /* General tab */ /* Default Behavior */ @@ -988,8 +1020,13 @@ em_composer_prefs_construct (EMComposerPrefs *prefs) g_signal_connect (prefs->sig_preview, "url_requested", G_CALLBACK (url_requested), NULL); gtk_widget_show (GTK_WIDGET (prefs->sig_preview)); gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (prefs->sig_preview)); -} + /* get our toplevel widget */ + target = em_config_target_new_prefs(ec, prefs->gconf); + e_config_set_target((EConfig *)ec, (EConfigTarget *)target); + toplevel = e_config_create_widget((EConfig *)ec); + gtk_container_add (GTK_CONTAINER (prefs), toplevel); +} GtkWidget * em_composer_prefs_new (void) diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c index 025b490472..b6b0474b3f 100644 --- a/mail/em-composer-utils.c +++ b/mail/em-composer-utils.c @@ -28,6 +28,7 @@ #include <gtk/gtkdialog.h> #include <gal/util/e-util.h> +#include <libgnome/gnome-i18n.h> #include "mail-mt.h" #include "mail-ops.h" diff --git a/mail/em-config.c b/mail/em-config.c new file mode 100644 index 0000000000..be8d4c95d4 --- /dev/null +++ b/mail/em-config.c @@ -0,0 +1,321 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright 2004 Novell, Inc. (www.novell.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 <libgnome/gnome-url.h> +#include <libgnomevfs/gnome-vfs-mime.h> + +#include "em-config.h" +#include "e-util/e-msgport.h" +#include <e-util/e-icon-factory.h> +#include "em-utils.h" +#include "em-composer-utils.h" + +#include <camel/camel-store.h> +#include <camel/camel-folder.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-string-utils.h> +#include <camel/camel-mime-utils.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-url.h> + +#include <camel/camel-vee-folder.h> +#include <camel/camel-vtrash-folder.h> + +#include <gconf/gconf.h> +#include <gconf/gconf-client.h> + +#include <gal/util/e-util.h> + +static GObjectClass *emp_parent; + +struct _EMConfigPrivate { + gint account_changed_id; +}; + +static void +emp_init(GObject *o) +{ + EMConfig *emp = (EMConfig *)o; + + emp->priv = g_malloc0(sizeof(*emp->priv)); +} + +static void +emp_finalise(GObject *o) +{ + struct _EMConfigPrivate *p = ((EMConfig *)o)->priv; + + /* Note we can't be unreffed if a target exists, so the target + * will need to be freed first which will clean up any + * listeners */ + + g_free(p); + + ((GObjectClass *)emp_parent)->finalize(o); +} + +static void +emp_target_free(EConfig *ep, EConfigTarget *t) +{ + if (ep->target == t) { + switch (t->type) { + case EM_CONFIG_TARGET_FOLDER: { + /*EMConfigTargetFolder *s = (EMConfigTargetFolder *)t;*/ + break; } + case EM_CONFIG_TARGET_PREFS: { + /*EMConfigTargetPrefs *s = (EMConfigTargetPrefs *)t;*/ + break; } + case EM_CONFIG_TARGET_ACCOUNT: { + EMConfigTargetAccount *s = (EMConfigTargetAccount *)t; + + if (((EMConfig *)ep)->priv->account_changed_id) { + g_signal_handler_disconnect(s->account, ((EMConfig *)ep)->priv->account_changed_id); + ((EMConfig *)ep)->priv->account_changed_id = 0; + } + break; } + } + } + + switch (t->type) { + case EM_CONFIG_TARGET_FOLDER: { + EMConfigTargetFolder *s = (EMConfigTargetFolder *)t; + + g_free(s->uri); + camel_object_unref(s->folder); + break; } + case EM_CONFIG_TARGET_PREFS: { + EMConfigTargetPrefs *s = (EMConfigTargetPrefs *)t; + + if (s->gconf) + g_object_unref(s->gconf); + break; } + case EM_CONFIG_TARGET_ACCOUNT: { + EMConfigTargetAccount *s = (EMConfigTargetAccount *)t; + + g_object_unref(s->account); + break; } + } + + ((EConfigClass *)emp_parent)->target_free(ep, t); +} + +static void +emp_account_changed(struct _EAccount *ea, int id, EMConfig *emc) +{ + e_config_target_changed((EConfig *)emc, E_CONFIG_TARGET_CHANGED_STATE); +} + +static void +emp_set_target(EConfig *ep, EConfigTarget *t) +{ + ((EConfigClass *)emp_parent)->set_target(ep, t); + + if (t) { + switch (t->type) { + case EM_CONFIG_TARGET_FOLDER: { + /*EMConfigTargetFolder *s = (EMConfigTargetFolder *)t;*/ + break; } + case EM_CONFIG_TARGET_PREFS: { + /*EMConfigTargetPrefs *s = (EMConfigTargetPrefs *)t;*/ + break; } + case EM_CONFIG_TARGET_ACCOUNT: { + EMConfigTargetAccount *s = (EMConfigTargetAccount *)t; + + ((EMConfig *)ep)->priv->account_changed_id = g_signal_connect(s->account, "changed", G_CALLBACK(emp_account_changed), ep); + break; } + } + } +} + +static void +emp_class_init(GObjectClass *klass) +{ + klass->finalize = emp_finalise; + ((EConfigClass *)klass)->set_target = emp_set_target; + ((EConfigClass *)klass)->target_free = emp_target_free; +} + +GType +em_config_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMConfigClass), + NULL, NULL, + (GClassInitFunc)emp_class_init, + NULL, NULL, + sizeof(EMConfig), 0, + (GInstanceInitFunc)emp_init + }; + emp_parent = g_type_class_ref(e_config_get_type()); + type = g_type_register_static(e_config_get_type(), "EMConfig", &info, 0); + } + + return type; +} + +EMConfig *em_config_new(int type, const char *menuid) +{ + EMConfig *emp = g_object_new(em_config_get_type(), 0); + + e_config_construct(&emp->config, type, menuid); + + return emp; +} + +EMConfigTargetFolder * +em_config_target_new_folder(EMConfig *emp, struct _CamelFolder *folder, const char *uri) +{ + EMConfigTargetFolder *t = e_config_target_new(&emp->config, EM_CONFIG_TARGET_FOLDER, sizeof(*t)); + + t->uri = g_strdup(uri); + t->folder = folder; + camel_object_ref(folder); + + return t; +} + +EMConfigTargetPrefs * +em_config_target_new_prefs(EMConfig *emp, struct _GConfClient *gconf) +{ + EMConfigTargetPrefs *t = e_config_target_new(&emp->config, EM_CONFIG_TARGET_PREFS, sizeof(*t)); + + t->gconf = gconf; + if (gconf) + g_object_ref(gconf); + + return t; +} + +EMConfigTargetAccount * +em_config_target_new_account(EMConfig *emp, struct _EAccount *account) +{ + EMConfigTargetAccount *t = e_config_target_new(&emp->config, EM_CONFIG_TARGET_ACCOUNT, sizeof(*t)); + + t->account = account; + g_object_ref(account); + + return 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" + mask="select_one" + activate="emp_view_emacs"/> + </menu> + </extension> + +*/ + +static void *emph_parent_class; +#define emph ((EMConfigHook *)eph) + +static const EConfigHookTargetMask emph_no_masks[] = { + { 0 } +}; + +static const EConfigHookTargetMap emph_targets[] = { + { "folder", EM_CONFIG_TARGET_FOLDER, emph_no_masks }, + { "prefs", EM_CONFIG_TARGET_PREFS, emph_no_masks }, + { "account", EM_CONFIG_TARGET_ACCOUNT, emph_no_masks }, + { 0 } +}; + +static void +emph_finalise(GObject *o) +{ + /*EPluginHook *eph = (EPluginHook *)o;*/ + + ((GObjectClass *)emph_parent_class)->finalize(o); +} + +static void +emph_class_init(EPluginHookClass *klass) +{ + int i; + + ((GObjectClass *)klass)->finalize = emph_finalise; + ((EPluginHookClass *)klass)->id = "com.novell.evolution.mail.config:1.0"; + + for (i=0;emph_targets[i].type;i++) + e_config_hook_class_add_target_map((EConfigHookClass *)klass, &emph_targets[i]); + + ((EConfigHookClass *)klass)->config_class = g_type_class_ref(em_config_get_type()); +} + +GType +em_config_hook_get_type(void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof(EMConfigHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL, + sizeof(EMConfigHook), 0, (GInstanceInitFunc) NULL, + }; + + emph_parent_class = g_type_class_ref(e_config_hook_get_type()); + type = g_type_register_static(e_config_hook_get_type(), "EMConfigHook", &info, 0); + } + + return type; +} diff --git a/mail/em-config.h b/mail/em-config.h new file mode 100644 index 0000000000..ff5b06a209 --- /dev/null +++ b/mail/em-config.h @@ -0,0 +1,114 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Authors: Michel Zucchi <notzed@ximian.com> + * + * Copyright 2004 Novell, Inc. (www.novell.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 __EM_CONFIG_H__ +#define __EM_CONFIG_H__ + +#include <glib-object.h> + +#include "e-util/e-config.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +struct _GConfClient; + +typedef struct _EMConfig EMConfig; +typedef struct _EMConfigClass EMConfigClass; + +/* Current target description */ +/* Types of popup tagets */ +enum _em_config_target_t { + EM_CONFIG_TARGET_FOLDER, + EM_CONFIG_TARGET_PREFS, + EM_CONFIG_TARGET_ACCOUNT, +}; + +typedef struct _EMConfigTargetFolder EMConfigTargetFolder; +typedef struct _EMConfigTargetPrefs EMConfigTargetPrefs; +typedef struct _EMConfigTargetAccount EMConfigTargetAccount; + +struct _EMConfigTargetFolder { + EConfigTarget target; + + struct _CamelFolder *folder; + char *uri; +}; + +struct _EMConfigTargetPrefs { + EConfigTarget target; + + /* preferences are global from gconf */ + struct _GConfClient *gconf; +}; + +struct _EMConfigTargetAccount { + EConfigTarget target; + + struct _EAccount *account; + /* Need also: working account, not just real account, so changes can be propagated around + And some mechamism for controlling the gui if we're running inside a druid, e.g. enabling 'next' */ +}; + +typedef struct _EConfigItem EMConfigItem; + +/* The object */ +struct _EMConfig { + EConfig config; + + struct _EMConfigPrivate *priv; +}; + +struct _EMConfigClass { + EConfigClass config_class; +}; + +GType em_config_get_type(void); + +EMConfig *em_config_new(int type, const char *menuid); + +EMConfigTargetFolder *em_config_target_new_folder(EMConfig *emp, struct _CamelFolder *folder, const char *uri); +EMConfigTargetPrefs *em_config_target_new_prefs(EMConfig *emp, struct _GConfClient *gconf); +EMConfigTargetAccount *em_config_target_new_account(EMConfig *emp, struct _EAccount *account); + +/* ********************************************************************** */ + +typedef struct _EMConfigHook EMConfigHook; +typedef struct _EMConfigHookClass EMConfigHookClass; + +struct _EMConfigHook { + EConfigHook hook; +}; + +struct _EMConfigHookClass { + EConfigHookClass hook_class; +}; + +GType em_config_hook_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_CONFIG_H__ */ diff --git a/mail/em-event.c b/mail/em-event.c new file mode 100644 index 0000000000..450d198d4c --- /dev/null +++ b/mail/em-event.c @@ -0,0 +1,185 @@ +/* -*- 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 "em-event.h" +#include "e-util/e-msgport.h" +#include <e-util/e-icon-factory.h> + +#include <camel/camel-store.h> +#include <camel/camel-folder.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-string-utils.h> +#include <camel/camel-mime-utils.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-url.h> + +#include <camel/camel-vee-folder.h> +#include <camel/camel-vtrash-folder.h> + +static GObjectClass *eme_parent; +static EMEvent *em_event; + +static void +eme_init(GObject *o) +{ + /*EMEvent *eme = (EMEvent *)o; */ +} + +static void +eme_finalise(GObject *o) +{ + ((GObjectClass *)eme_parent)->finalize(o); +} + +static void +eme_target_free(EEvent *ep, EEventTarget *t) +{ + switch (t->type) { + case EM_EVENT_TARGET_FOLDER: { + EMEventTargetFolder *s = (EMEventTargetFolder *)t; + + g_free(s->uri); + break; } + } + + ((EEventClass *)eme_parent)->target_free(ep, t); +} + +static void +eme_class_init(GObjectClass *klass) +{ + klass->finalize = eme_finalise; + ((EEventClass *)klass)->target_free = eme_target_free; +} + +GType +em_event_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMEventClass), + NULL, NULL, + (GClassInitFunc)eme_class_init, + NULL, NULL, + sizeof(EMEvent), 0, + (GInstanceInitFunc)eme_init + }; + eme_parent = g_type_class_ref(e_event_get_type()); + type = g_type_register_static(e_event_get_type(), "EMEvent", &info, 0); + } + + return type; +} + +/** + * em_event_peek: + * @void: + * + * Get the singular instance of the mail event handler. + * + * Return value: + **/ +EMEvent *em_event_peek(void) +{ + if (em_event == NULL) { + em_event = g_object_new(em_event_get_type(), 0); + e_event_construct(&em_event->popup, "com.ximian.evolution.mail.events"); + } + + return em_event; +} + +EMEventTargetFolder * +em_event_target_new_folder (EMEvent *eme, const char *uri, guint32 flags) +{ + EMEventTargetFolder *t = e_event_target_new(&eme->popup, EM_EVENT_TARGET_FOLDER, sizeof(*t)); + + t->uri = g_strdup(uri); + t->target.mask = ~flags; + + return t; +} + +/* ********************************************************************** */ + +static void *emeh_parent_class; +#define emeh ((EMEventHook *)eph) + +static const EEventHookTargetMask emeh_folder_masks[] = { + { "newmail", EM_EVENT_FOLDER_NEWMAIL }, + { 0 } +}; +static const EEventHookTargetMap emeh_targets[] = { + { "folder", EM_EVENT_TARGET_FOLDER, emeh_folder_masks }, + { 0 } +}; + +static void +emeh_finalise(GObject *o) +{ + /*EPluginHook *eph = (EPluginHook *)o;*/ + + ((GObjectClass *)emeh_parent_class)->finalize(o); +} + +static void +emeh_class_init(EPluginHookClass *klass) +{ + int i; + + ((GObjectClass *)klass)->finalize = emeh_finalise; + ((EPluginHookClass *)klass)->id = "com.ximian.evolution.mail.events:1.0"; + + for (i=0;emeh_targets[i].type;i++) + e_event_hook_class_add_target_map((EEventHookClass *)klass, &emeh_targets[i]); + + ((EEventHookClass *)klass)->event = (EEvent *)em_event_peek(); +} + +GType +em_event_hook_get_type(void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof(EMEventHookClass), NULL, NULL, (GClassInitFunc) emeh_class_init, NULL, NULL, + sizeof(EMEventHook), 0, (GInstanceInitFunc) NULL, + }; + + emeh_parent_class = g_type_class_ref(e_event_hook_get_type()); + type = g_type_register_static(e_event_hook_get_type(), "EMEventHook", &info, 0); + } + + return type; +} diff --git a/mail/em-event.h b/mail/em-event.h new file mode 100644 index 0000000000..05c62d9f50 --- /dev/null +++ b/mail/em-event.h @@ -0,0 +1,94 @@ +/* -*- 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 __EM_EVENT_H__ +#define __EM_EVENT_H__ + +#include <glib-object.h> + +#include "e-util/e-event.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +typedef struct _EMEvent EMEvent; +typedef struct _EMEventClass EMEventClass; + +/* Current target description */ +/* Types of popup tagets */ +enum _em_event_target_t { + EM_EVENT_TARGET_FOLDER, +}; + +/* Flags that describe TARGET_FOLDER */ +enum { + EM_EVENT_FOLDER_NEWMAIL = 1<< 0, +}; + +typedef struct _EMEventTargetFolder EMEventTargetFolder; + +struct _EMEventTargetFolder { + EEventTarget target; + char *uri; +}; + +typedef struct _EEventItem EMEventItem; + +/* The object */ +struct _EMEvent { + EEvent popup; + + struct _EMEventPrivate *priv; +}; + +struct _EMEventClass { + EEventClass popup_class; +}; + +GType em_event_get_type(void); + +EMEvent *em_event_peek(void); + +EMEventTargetFolder *em_event_target_new_folder(EMEvent *emp, const char *uri, guint32 flags); + +/* ********************************************************************** */ + +typedef struct _EMEventHook EMEventHook; +typedef struct _EMEventHookClass EMEventHookClass; + +struct _EMEventHook { + EEventHook hook; +}; + +struct _EMEventHookClass { + EEventHookClass hook_class; +}; + +GType em_event_hook_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_EVENT_H__ */ diff --git a/mail/em-folder-browser.c b/mail/em-folder-browser.c index e9c14ea600..40633d5d36 100644 --- a/mail/em-folder-browser.c +++ b/mail/em-folder-browser.c @@ -74,6 +74,7 @@ #include "em-folder-browser.h" #include "em-folder-properties.h" #include "em-subscribe-editor.h" +#include "em-menu.h" #include "message-list.h" #include "mail-component.h" @@ -100,6 +101,8 @@ struct _EMFolderBrowserPrivate { guint list_built_id; /* hook onto list-built for delayed 'select first unread' stuff */ char *select_uid; + + EMMenu *menu; /* toplevel menu manager */ }; static void emfb_activate(EMFolderView *emfv, BonoboUIComponent *uic, int state); @@ -284,6 +287,8 @@ GtkWidget *em_folder_browser_new(void) { EMFolderBrowser *emfb = g_object_new(em_folder_browser_get_type(), 0); + ((EMFolderView *)emfb)->menu = em_menu_new("com.novell.evolution.mail.browser"); + return (GtkWidget *)emfb; } diff --git a/mail/em-folder-properties.c b/mail/em-folder-properties.c index 885abcceef..9c32e30a87 100644 --- a/mail/em-folder-properties.c +++ b/mail/em-folder-properties.c @@ -39,8 +39,10 @@ #include <gtk/gtkvbox.h> #include <camel/camel-folder.h> +#include <libgnome/gnome-i18n.h> #include "em-folder-properties.h" +#include "em-config.h" #include "mail-ops.h" #include "mail-mt.h" @@ -50,19 +52,32 @@ struct _prop_data { void *object; CamelArgV *argv; GtkWidget **widgets; + + GSList *properties; + char *name; + int total; + int unread; + EMConfig *config; }; static void emfp_dialog_response (GtkWidget *dialog, int response, struct _prop_data *prop_data) { + if (response == GTK_RESPONSE_OK) + e_config_commit((EConfig *)prop_data->config); + else + e_config_abort((EConfig *)prop_data->config); + + gtk_widget_destroy (dialog); +} + +static void +emfp_commit(EConfig *ec, GSList *items, void *data) +{ + struct _prop_data *prop_data = data; CamelArgV *argv = prop_data->argv; int i; - if (response != GTK_RESPONSE_OK) { - gtk_widget_destroy (dialog); - return; - } - for (i = 0; i < argv->argc; i++) { CamelArg *arg = &argv->argv[i]; @@ -81,136 +96,75 @@ emfp_dialog_response (GtkWidget *dialog, int response, struct _prop_data *prop_d } camel_object_setv (prop_data->object, NULL, argv); - gtk_widget_destroy (dialog); } static void -emfp_dialog_free (void *data) +emfp_free(EConfig *ec, GSList *items, void *data) { struct _prop_data *prop_data = data; int i; + g_slist_free(items); + for (i = 0; i < prop_data->argv->argc; i++) { if ((prop_data->argv->argv[i].tag & CAMEL_ARG_TYPE) == CAMEL_ARG_STR) g_free (prop_data->argv->argv[i].ca_str); } + + camel_object_free (prop_data->object, CAMEL_FOLDER_PROPERTIES, prop_data->properties); + camel_object_free (prop_data->object, CAMEL_FOLDER_NAME, prop_data->name); camel_object_unref (prop_data->object); g_free (prop_data->argv); + g_free (prop_data); } -static void -emfp_dialog_got_folder (char *uri, CamelFolder *folder, void *data) +static GtkWidget * +emfp_get_folder_item(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) { - GtkWidget *dialog, *w, *table, *label, *vbox, *hbox; - struct _prop_data *prop_data; - CamelArgGetV *arggetv; - CamelArgV *argv; - GSList *list, *l; - gint32 count, i; - char *name, *title; char countstr[16]; - int row = 0, total=0, unread=0; - - if (folder == NULL) - return; - - camel_object_get (folder, NULL, CAMEL_FOLDER_PROPERTIES, &list, CAMEL_FOLDER_NAME, &name, - CAMEL_FOLDER_TOTAL, &total, CAMEL_FOLDER_UNREAD, &unread, NULL); - - dialog = gtk_dialog_new_with_buttons (_("Folder Properties"), NULL, - GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OK, GTK_RESPONSE_OK, - NULL); - gtk_window_set_default_size ((GtkWindow *) dialog, 192, 160); - gtk_widget_ensure_style (dialog); - gtk_container_set_border_width ((GtkContainer *) ((GtkDialog *) dialog)->vbox, 0); - gtk_container_set_border_width ((GtkContainer *) ((GtkDialog *) dialog)->vbox, 12); - - vbox = gtk_vbox_new (FALSE, 12); - gtk_container_set_border_width ((GtkContainer *) vbox, 12); - gtk_box_pack_start ((GtkBox *) ((GtkDialog *) dialog)->vbox, vbox, TRUE, TRUE, 0); - gtk_widget_show (vbox); - - title = g_strdup_printf ("<b>%s</b>", name); - label = gtk_label_new (title); - gtk_label_set_use_markup ((GtkLabel *) label, TRUE); - gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.5); - gtk_box_pack_start ((GtkBox *) vbox, label, FALSE, FALSE, 0); - gtk_widget_show (label); - g_free (title); - - hbox = gtk_hbox_new (FALSE, 12); - gtk_box_pack_start ((GtkBox *) vbox, hbox, FALSE, FALSE, 0); - gtk_widget_show (hbox); + GtkWidget *w, *table, *label; + struct _prop_data *prop_data = data; + int row = 0, i; + GSList *l; - label = gtk_label_new (""); - gtk_box_pack_start ((GtkBox *) hbox, label, FALSE, FALSE, 0); - gtk_widget_show (label); + if (old) + return old; - /* TODO: maybe we want some basic properties here, like message counts/approximate size/etc */ - table = gtk_table_new (g_slist_length (list) + 2, 2, FALSE); + table = gtk_table_new (g_slist_length (prop_data->properties) + 2, 2, FALSE); gtk_table_set_row_spacings ((GtkTable *) table, 6); gtk_table_set_col_spacings ((GtkTable *) table, 12); gtk_widget_show (table); - gtk_box_pack_start ((GtkBox *) hbox, table, TRUE, TRUE, 0); + gtk_box_pack_start ((GtkBox *) parent, table, TRUE, TRUE, 0); /* TODO: can this be done in a loop? */ - label = gtk_label_new (ngettext ("Total message:", "Total messages:", total)); + label = gtk_label_new (ngettext ("Total message:", "Total messages:", prop_data->total)); gtk_widget_show (label); gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); - sprintf(countstr, "%d", total); + sprintf(countstr, "%d", prop_data->total); label = gtk_label_new (countstr); gtk_widget_show (label); gtk_misc_set_alignment ((GtkMisc *) label, 1.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 1, 2, row, row+1, GTK_FILL | GTK_EXPAND, 0, 0, 0); row++; - label = gtk_label_new (ngettext ("Unread message:", "Unread messages:", unread)); + label = gtk_label_new (ngettext ("Unread message:", "Unread messages:", prop_data->unread)); gtk_widget_show (label); gtk_misc_set_alignment ((GtkMisc *) label, 0.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); - sprintf(countstr, "%d", unread); + sprintf(countstr, "%d", prop_data->unread); label = gtk_label_new (countstr); gtk_widget_show (label); gtk_misc_set_alignment ((GtkMisc *) label, 1.0, 0.5); gtk_table_attach ((GtkTable *) table, label, 1, 2, row, row+1, GTK_FILL | GTK_EXPAND, 0, 0, 0); row++; - /* build an arggetv/argv to retrieve/store the results */ - count = g_slist_length (list); - arggetv = g_malloc0 (sizeof (*arggetv) + (count - CAMEL_ARGV_MAX) * sizeof (arggetv->argv[0])); - arggetv->argc = count; - argv = g_malloc0 (sizeof (*argv) + (count - CAMEL_ARGV_MAX) * sizeof (argv->argv[0])); - argv->argc = count; - - i = 0; - l = list; - while (l) { - CamelProperty *prop = l->data; - - argv->argv[i].tag = prop->tag; - arggetv->argv[i].tag = prop->tag; - arggetv->argv[i].ca_ptr = &argv->argv[i].ca_ptr; - - l = l->next; - i++; - } - - camel_object_getv (folder, NULL, arggetv); - g_free (arggetv); - - prop_data = g_malloc0 (sizeof (*prop_data)); - prop_data->widgets = g_malloc0 (sizeof (prop_data->widgets[0]) * count); - prop_data->argv = argv; - /* setup the ui with the values retrieved */ - l = list; + l = prop_data->properties; i = 0; while (l) { CamelProperty *prop = l->data; @@ -218,7 +172,7 @@ emfp_dialog_got_folder (char *uri, CamelFolder *folder, void *data) switch (prop->tag & CAMEL_ARG_TYPE) { case CAMEL_ARG_BOO: w = gtk_check_button_new_with_label (prop->description); - gtk_toggle_button_set_active ((GtkToggleButton *) w, argv->argv[i].ca_int != 0); + gtk_toggle_button_set_active ((GtkToggleButton *) w, prop_data->argv->argv[i].ca_int != 0); gtk_widget_show (w); gtk_table_attach ((GtkTable *) table, w, 0, 2, row, row + 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); prop_data->widgets[i] = w; @@ -231,10 +185,10 @@ emfp_dialog_got_folder (char *uri, CamelFolder *folder, void *data) w = gtk_entry_new (); gtk_widget_show (w); - if (argv->argv[i].ca_str) { - gtk_entry_set_text ((GtkEntry *) w, argv->argv[i].ca_str); - camel_object_free (folder, argv->argv[i].tag, argv->argv[i].ca_str); - argv->argv[i].ca_str = NULL; + if (prop_data->argv->argv[i].ca_str) { + gtk_entry_set_text ((GtkEntry *) w, prop_data->argv->argv[i].ca_str); + camel_object_free (prop_data->object, prop_data->argv->argv[i].tag, prop_data->argv->argv[i].ca_str); + prop_data->argv->argv[i].ca_str = NULL; } gtk_table_attach ((GtkTable *) table, w, 1, 2, row, row + 1, GTK_FILL | GTK_EXPAND, 0, 0, 0); prop_data->widgets[i] = w; @@ -247,17 +201,95 @@ emfp_dialog_got_folder (char *uri, CamelFolder *folder, void *data) row++; l = l->next; } - + + return table; +} + +#define EMFP_FOLDER_SECTION (2) + +static EMConfigItem emfp_items[] = { + { E_CONFIG_BOOK, "", NULL }, + { E_CONFIG_PAGE, "00.general", N_("General") }, + { E_CONFIG_SECTION, "00.general/00.folder", NULL /* set by code */ }, + { E_CONFIG_ITEM, "00.general/00.folder/00.info", NULL, emfp_get_folder_item }, +}; + +static void +emfp_dialog_got_folder (char *uri, CamelFolder *folder, void *data) +{ + GtkWidget *dialog, *w; + struct _prop_data *prop_data; + GSList *l; + gint32 count, i; + EMConfig *ec; + EMConfigTargetFolder *target; + CamelArgGetV *arggetv; + CamelArgV *argv; + + if (folder == NULL) + return; + + prop_data = g_malloc0 (sizeof (*prop_data)); prop_data->object = folder; camel_object_ref (folder); + + camel_object_get (folder, NULL, CAMEL_FOLDER_PROPERTIES, &prop_data->properties, CAMEL_FOLDER_NAME, &prop_data->name, + CAMEL_FOLDER_TOTAL, &prop_data->total, CAMEL_FOLDER_UNREAD, &prop_data->unread, NULL); + + emfp_items[EMFP_FOLDER_SECTION].label = prop_data->name; + + count = g_slist_length (prop_data->properties); + + prop_data->widgets = g_malloc0 (sizeof (prop_data->widgets[0]) * count); + + /* build an arggetv/argv to retrieve/store the results */ + argv = g_malloc0 (sizeof (*argv) + (count - CAMEL_ARGV_MAX) * sizeof (argv->argv[0])); + argv->argc = count; + arggetv = g_malloc0 (sizeof (*arggetv) + (count - CAMEL_ARGV_MAX) * sizeof (arggetv->argv[0])); + arggetv->argc = count; - camel_object_free (folder, CAMEL_FOLDER_PROPERTIES, list); - camel_object_free (folder, CAMEL_FOLDER_NAME, name); + i = 0; + l = prop_data->properties; + while (l) { + CamelProperty *prop = l->data; + + argv->argv[i].tag = prop->tag; + arggetv->argv[i].tag = prop->tag; + arggetv->argv[i].ca_ptr = &argv->argv[i].ca_ptr; + + l = l->next; + i++; + } + camel_object_getv (prop_data->object, NULL, arggetv); + g_free (arggetv); + prop_data->argv = argv; + + dialog = gtk_dialog_new_with_buttons (_("Folder Properties"), NULL, + GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + gtk_window_set_default_size ((GtkWindow *) dialog, 192, 160); + gtk_widget_ensure_style (dialog); + gtk_container_set_border_width ((GtkContainer *) ((GtkDialog *) dialog)->vbox, 12); + + ec = em_config_new(E_CONFIG_BOOK, "com.novell.evolution.mail.folderConfig"); + prop_data->config = ec; + l = NULL; + for (i=0;i<sizeof(emfp_items)/sizeof(emfp_items[0]);i++) + l = g_slist_prepend(l, &emfp_items[i]); + e_config_add_items((EConfig *)ec, l, emfp_commit, NULL, emfp_free, prop_data); + + target = em_config_target_new_folder(ec, folder, uri); + e_config_set_target((EConfig *)ec, (EConfigTarget *)target); + w = e_config_create_widget((EConfig *)ec); + + gtk_box_pack_start ((GtkBox *) ((GtkDialog *) dialog)->vbox, w, TRUE, TRUE, 0); + /* we do 'apply on ok' ... since instant apply may apply some very long running tasks */ g_signal_connect (dialog, "response", G_CALLBACK (emfp_dialog_response), prop_data); - g_object_set_data_full ((GObject *) dialog, "e-prop-data", prop_data, emfp_dialog_free); gtk_widget_show (dialog); } diff --git a/mail/em-folder-selection-button.c b/mail/em-folder-selection-button.c index 9124918301..b727891781 100644 --- a/mail/em-folder-selection-button.c +++ b/mail/em-folder-selection-button.c @@ -31,6 +31,7 @@ #include <gtk/gtkhbox.h> #include <gal/util/e-util.h> +#include <libgnome/gnome-i18n.h> #include "mail-component.h" #include "em-folder-tree.h" diff --git a/mail/em-folder-tree-model.c b/mail/em-folder-tree-model.c index 6947dc5ef4..4f1a8dda67 100644 --- a/mail/em-folder-tree-model.c +++ b/mail/em-folder-tree-model.c @@ -20,7 +20,6 @@ * */ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -38,6 +37,7 @@ #include <e-util/e-mktemp.h> #include <gal/util/e-xml-utils.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-file-utils.h> @@ -427,7 +427,7 @@ em_folder_tree_model_set_folder_info (EMFolderTreeModel *model, GtkTreeIter *ite gboolean load = FALSE; struct _CamelFolder *folder; gboolean emitted = FALSE; - + if (!fully_loaded) load = fi->child == NULL && !(fi->flags & (CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_NOINFERIORS)); diff --git a/mail/em-folder-tree.c b/mail/em-folder-tree.c index 5d8005ed40..5c8c384af8 100644 --- a/mail/em-folder-tree.c +++ b/mail/em-folder-tree.c @@ -37,6 +37,7 @@ #include <gtk/gtk.h> #include <gdk-pixbuf/gdk-pixbuf.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-session.h> #include <camel/camel-store.h> @@ -987,36 +988,48 @@ tree_drag_data_action(struct _DragDataReceivedAsync *m) } static void -emft_drop_popup_copy(GtkWidget *item, struct _DragDataReceivedAsync *m) +emft_drop_popup_copy(EPopup *ep, EPopupItem *item, void *data) { + struct _DragDataReceivedAsync *m = data; + m->action = GDK_ACTION_COPY; tree_drag_data_action(m); } static void -emft_drop_popup_move(GtkWidget *item, struct _DragDataReceivedAsync *m) +emft_drop_popup_move(EPopup *ep, EPopupItem *item, void *data) { + struct _DragDataReceivedAsync *m = data; + m->action = GDK_ACTION_MOVE; tree_drag_data_action(m); } static void -emft_drop_popup_cancel(GtkWidget *item, struct _DragDataReceivedAsync *m) +emft_drop_popup_cancel(EPopup *ep, EPopupItem *item, void *data) { + struct _DragDataReceivedAsync *m = data; + m->aborted = TRUE; mail_msg_free(&m->msg); } -static EMPopupItem emft_drop_popup_menu[] = { - { EM_POPUP_ITEM, "00.emc.00", N_("_Copy to Folder"), G_CALLBACK (emft_drop_popup_copy), NULL, NULL, 1 }, - { EM_POPUP_ITEM, "00.emc.01", N_("_Move to Folder"), G_CALLBACK (emft_drop_popup_move), NULL, NULL, 1 }, - { EM_POPUP_ITEM, "00.emc.02", N_("_Copy"), G_CALLBACK (emft_drop_popup_copy), NULL, "stock_folder-copy", 2 }, - { EM_POPUP_ITEM, "00.emc.03", N_("_Move"), G_CALLBACK (emft_drop_popup_move), NULL, "stock_folder-move", 2 }, - { EM_POPUP_BAR, "10.emc" }, - { EM_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), G_CALLBACK (emft_drop_popup_cancel), NULL, "stock_cancel", 0 }, +static EPopupItem emft_drop_popup_menu[] = { + { E_POPUP_ITEM, "00.emc.00", N_("_Copy to Folder"), emft_drop_popup_copy, NULL, NULL, 1 }, + { E_POPUP_ITEM, "00.emc.01", N_("_Move to Folder"), emft_drop_popup_move, NULL, NULL, 1 }, + { E_POPUP_ITEM, "00.emc.02", N_("_Copy"), emft_drop_popup_copy, NULL, "stock_folder-copy", 2 }, + { E_POPUP_ITEM, "00.emc.03", N_("_Move"), emft_drop_popup_move, NULL, "stock_folder-move", 2 }, + { E_POPUP_BAR, "10.emc" }, + { E_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), emft_drop_popup_cancel, NULL, "stock_cancel", 0 }, }; static void +emft_drop_popup_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); +} + +static void tree_drag_data_received(GtkWidget *widget, GdkDragContext *context, int x, int y, GtkSelectionData *selection, guint info, guint time, EMFolderTree *emft) { struct _EMFolderTreePrivate *priv = emft->priv; @@ -1082,15 +1095,13 @@ tree_drag_data_received(GtkWidget *widget, GdkDragContext *context, int x, int y mask = ~2; for (i=0;i<sizeof(emft_drop_popup_menu)/sizeof(emft_drop_popup_menu[0]);i++) { - EMPopupItem *item = &emft_drop_popup_menu[i]; + EPopupItem *item = &emft_drop_popup_menu[i]; - if ((item->mask & mask) == 0) { - item->activate_data = m; + if ((item->visible & mask) == 0) menus = g_slist_append(menus, item); - } } - em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); - menu = em_popup_create_menu_once(emp, NULL, mask, mask); + e_popup_add_items((EPopup *)emp, menus, emft_drop_popup_free, m); + menu = e_popup_create_menu_once((EPopup *)emp, NULL, mask, mask); gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } else { tree_drag_data_action(m); @@ -2192,8 +2203,9 @@ fail: } static void -emft_popup_copy (GtkWidget *item, EMFolderTree *emft) +emft_popup_copy(EPopup *ep, EPopupItem *item, void *data) { + EMFolderTree *emft = data; struct _copy_folder_data *cfd; cfd = g_malloc (sizeof (*cfd)); @@ -2205,8 +2217,9 @@ emft_popup_copy (GtkWidget *item, EMFolderTree *emft) } static void -emft_popup_move (GtkWidget *item, EMFolderTree *emft) +emft_popup_move(EPopup *ep, EPopupItem *item, void *data) { + EMFolderTree *emft = data; struct _copy_folder_data *cfd; cfd = g_malloc (sizeof (*cfd)); @@ -2419,8 +2432,10 @@ emft_popup_new_folder_response (EMFolderSelector *emfs, int response, EMFolderTr } static void -emft_popup_new_folder (GtkWidget *item, EMFolderTree *emft) +emft_popup_new_folder (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderTree *emft = data; + EMFolderTree *folder_tree; GtkWidget *dialog; char *uri; @@ -2545,8 +2560,9 @@ emft_popup_delete_response (GtkWidget *dialog, int response, EMFolderTree *emft) } static void -emft_popup_delete_folder (GtkWidget *item, EMFolderTree *emft) +emft_popup_delete_folder (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderTree *emft = data; struct _EMFolderTreePrivate *priv = emft->priv; GtkTreeSelection *selection; CamelStore *local, *store; @@ -2580,8 +2596,9 @@ emft_popup_delete_folder (GtkWidget *item, EMFolderTree *emft) } static void -emft_popup_rename_folder (GtkWidget *item, EMFolderTree *emft) +emft_popup_rename_folder (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderTree *emft = data; struct _EMFolderTreePrivate *priv = emft->priv; char *prompt, *full_name, *name, *new_name, *uri; GtkTreeSelection *selection; @@ -2676,8 +2693,9 @@ emft_popup_rename_folder (GtkWidget *item, EMFolderTree *emft) static void -emft_popup_properties (GtkWidget *item, EMFolderTree *emft) +emft_popup_properties (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderTree *emft = data; struct _EMFolderTreePrivate *priv = emft->priv; GtkTreeSelection *selection; GtkTreeModel *model; @@ -2693,33 +2711,39 @@ emft_popup_properties (GtkWidget *item, EMFolderTree *emft) g_free (uri); } -static EMPopupItem emft_popup_menu[] = { +static EPopupItem emft_popup_menu[] = { #if 0 - { EM_POPUP_ITEM, "00.emc.00", N_("_View"), G_CALLBACK (emft_popup_view), NULL, NULL, EM_POPUP_FOLDER_SELECT }, - { EM_POPUP_ITEM, "00.emc.01", N_("Open in _New Window"), G_CALLBACK (emft_popup_open_new), NULL, NULL, EM_POPUP_FOLDER_SELECT }, + { E_POPUP_ITEM, "00.emc.00", N_("_View"), emft_popup_view, NULL, NULL, EM_POPUP_FOLDER_SELECT }, + { E_POPUP_ITEM, "00.emc.01", N_("Open in _New Window"), emft_popup_open_new, NULL, NULL, EM_POPUP_FOLDER_SELECT }, - { EM_POPUP_BAR, "10.emc" }, + { E_POPUP_BAR, "10.emc" }, #endif - { EM_POPUP_ITEM, "10.emc.00", N_("_Copy..."), G_CALLBACK (emft_popup_copy), NULL, "stock_folder-copy", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT }, - { EM_POPUP_ITEM, "10.emc.01", N_("_Move..."), G_CALLBACK (emft_popup_move), NULL, "stock_folder-move", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, + { E_POPUP_ITEM, "10.emc.00", N_("_Copy..."), emft_popup_copy, NULL, "stock_folder-copy", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT }, + { E_POPUP_ITEM, "10.emc.01", N_("_Move..."), emft_popup_move, NULL, "stock_folder-move", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, - { EM_POPUP_BAR, "20.emc" }, + { E_POPUP_BAR, "20.emc" }, /* FIXME: need to disable for nochildren folders */ - { EM_POPUP_ITEM, "20.emc.00", N_("_New Folder..."), G_CALLBACK (emft_popup_new_folder), NULL, "stock_folder", EM_POPUP_FOLDER_INFERIORS }, + { E_POPUP_ITEM, "20.emc.00", N_("_New Folder..."), emft_popup_new_folder, NULL, "stock_folder", EM_POPUP_FOLDER_INFERIORS }, /* FIXME: need to disable for undeletable folders */ - { EM_POPUP_ITEM, "20.emc.01", N_("_Delete"), G_CALLBACK (emft_popup_delete_folder), NULL, "stock_delete", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, - { EM_POPUP_ITEM, "20.emc.01", N_("_Rename..."), G_CALLBACK (emft_popup_rename_folder), NULL, NULL, EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, + { E_POPUP_ITEM, "20.emc.01", N_("_Delete"), emft_popup_delete_folder, NULL, "stock_delete", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, + { E_POPUP_ITEM, "20.emc.01", N_("_Rename..."), emft_popup_rename_folder, NULL, NULL, EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_DELETE }, - { EM_POPUP_BAR, "80.emc" }, - { EM_POPUP_ITEM, "80.emc.00", N_("_Properties"), G_CALLBACK (emft_popup_properties), NULL, "stock_folder-properties", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT } + { E_POPUP_BAR, "80.emc" }, + { E_POPUP_ITEM, "80.emc.00", N_("_Properties"), emft_popup_properties, NULL, "stock_folder-properties", EM_POPUP_FOLDER_FOLDER|EM_POPUP_FOLDER_SELECT } }; +static void +emft_popup_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); +} + static gboolean emft_tree_button_press (GtkTreeView *treeview, GdkEventButton *event, EMFolderTree *emft) { GtkTreeSelection *selection; CamelStore *local, *store; - EMPopupTarget *target; + EMPopupTargetFolder *target; GtkTreePath *tree_path; GtkTreeModel *model; GtkTreeIter iter; @@ -2789,18 +2813,14 @@ emft_tree_button_press (GtkTreeView *treeview, GdkEventButton *event, EMFolderTr emp = em_popup_new ("com.ximian.mail.storageset.popup.select"); /* FIXME: pass valid fi->flags here */ - target = em_popup_target_new_folder (uri, info_flags, flags); + target = em_popup_target_new_folder (emp, uri, info_flags, flags); - for (i = 0; i < sizeof (emft_popup_menu) / sizeof (emft_popup_menu[0]); i++) { - EMPopupItem *item = &emft_popup_menu[i]; - - item->activate_data = emft; - menus = g_slist_prepend (menus, item); - } + for (i = 0; i < sizeof (emft_popup_menu) / sizeof (emft_popup_menu[0]); i++) + menus = g_slist_prepend (menus, &emft_popup_menu[i]); - em_popup_add_items (emp, menus, (GDestroyNotify) g_slist_free); + e_popup_add_items ((EPopup *)emp, menus, emft_popup_free, emft); - menu = em_popup_create_menu_once (emp, target, 0, target->mask); + menu = e_popup_create_menu_once ((EPopup *)emp, (EPopupTarget *)target, 0, target->target.mask); if (event == NULL || event->type == GDK_KEY_PRESS) { /* FIXME: menu pos function */ diff --git a/mail/em-folder-view.c b/mail/em-folder-view.c index 00f918ed80..ef9007a683 100644 --- a/mail/em-folder-view.c +++ b/mail/em-folder-view.c @@ -77,6 +77,7 @@ #include "em-utils.h" #include "em-composer-utils.h" #include "em-marshal.h" +#include "em-menu.h" #include <gtkhtml/gtkhtml.h> #include <gtkhtml/htmlobject.h> @@ -590,22 +591,17 @@ emfv_selection_clear_event(GtkWidget *widget, GdkEventSelection *event, EMFolder /* Popup menu In many cases these are the functions called by the bonobo callbacks too */ -struct _emfv_label_item { - EMPopupItem item; - - EMFolderView *emfv; - const char *label; -}; - static void -emfv_popup_open(GtkWidget *w, EMFolderView *emfv) +emfv_popup_open(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_open_selected(emfv); } static void -emfv_popup_edit (GtkWidget *w, EMFolderView *emfv) +emfv_popup_edit (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; if (!em_utils_check_user_can_send_mail((GtkWidget *)emfv)) @@ -616,8 +612,9 @@ emfv_popup_edit (GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_saveas(GtkWidget *w, EMFolderView *emfv) +emfv_popup_saveas(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; uids = message_list_get_selected(emfv->list); @@ -625,32 +622,37 @@ emfv_popup_saveas(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_print(GtkWidget *w, EMFolderView *emfv) +emfv_popup_print(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_print(emfv, FALSE); } static void -emfv_popup_reply_sender(GtkWidget *w, EMFolderView *emfv) +emfv_popup_reply_sender(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; emfv_message_reply(emfv, REPLY_MODE_SENDER); } static void -emfv_popup_reply_list(GtkWidget *w, EMFolderView *emfv) +emfv_popup_reply_list(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; emfv_message_reply(emfv, REPLY_MODE_LIST); } static void -emfv_popup_reply_all(GtkWidget *w, EMFolderView *emfv) +emfv_popup_reply_all(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; emfv_message_reply(emfv, REPLY_MODE_ALL); } static void -emfv_popup_forward(GtkWidget *w, EMFolderView *emfv) +emfv_popup_forward(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; if (!em_utils_check_user_can_send_mail((GtkWidget *)emfv)) @@ -661,16 +663,18 @@ emfv_popup_forward(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_flag_followup(GtkWidget *w, EMFolderView *emfv) +emfv_popup_flag_followup(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids = message_list_get_selected(emfv->list); em_utils_flag_for_followup((GtkWidget *)emfv, emfv->folder, uids); } static void -emfv_popup_flag_completed(GtkWidget *w, EMFolderView *emfv) +emfv_popup_flag_completed(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; uids = message_list_get_selected(emfv->list); @@ -678,22 +682,25 @@ emfv_popup_flag_completed(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_flag_clear(GtkWidget *w, EMFolderView *emfv) +emfv_popup_flag_clear(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids = message_list_get_selected(emfv->list); em_utils_flag_for_followup_clear((GtkWidget *)emfv, emfv->folder, uids); } static void -emfv_popup_mark_read(GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_read(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_SEEN, CAMEL_MESSAGE_SEEN); } static void -emfv_popup_mark_unread(GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_unread(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_DELETED, 0); if (emfv->priv->seen_id) { @@ -703,20 +710,23 @@ emfv_popup_mark_unread(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_mark_important(GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_important(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_FLAGGED); } static void -emfv_popup_mark_unimportant(GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_unimportant(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_FLAGGED, 0); } static void -emfv_popup_mark_junk (GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_junk (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; uids = message_list_get_selected(emfv->list); @@ -730,8 +740,9 @@ emfv_popup_mark_junk (GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_mark_nojunk (GtkWidget *w, EMFolderView *emfv) +emfv_popup_mark_nojunk (EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; uids = message_list_get_selected(emfv->list); @@ -745,8 +756,9 @@ emfv_popup_mark_nojunk (GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_delete(GtkWidget *w, EMFolderView *emfv) +emfv_popup_delete(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids; uids = message_list_get_selected(emfv->list); @@ -760,8 +772,9 @@ emfv_popup_delete(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_undelete(GtkWidget *w, EMFolderView *emfv) +emfv_popup_undelete(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; em_folder_view_mark_selected(emfv, CAMEL_MESSAGE_DELETED, 0); } @@ -790,8 +803,9 @@ emfv_popup_move_cb(const char *uri, void *data) } static void -emfv_popup_move(GtkWidget *w, EMFolderView *emfv) +emfv_popup_move(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; struct _move_data *d; d = g_malloc(sizeof(*d)); @@ -804,8 +818,9 @@ emfv_popup_move(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_copy(GtkWidget *w, EMFolderView *emfv) +emfv_popup_copy(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; struct _move_data *d; d = g_malloc(sizeof(*d)); @@ -830,20 +845,24 @@ emfv_set_label(EMFolderView *emfv, const char *label) } static void -emfv_popup_label_clear(GtkWidget *w, EMFolderView *emfv) +emfv_popup_label_clear(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; emfv_set_label(emfv, NULL); } static void -emfv_popup_label_set(GtkWidget *w, struct _emfv_label_item *item) +emfv_popup_label_set(EPopup *ep, EPopupItem *pitem, void *data) { - emfv_set_label(item->emfv, item->label); + EMFolderView *emfv = data; + + emfv_set_label(emfv, pitem->user_data); } static void -emfv_popup_add_sender(GtkWidget *w, EMFolderView *emfv) +emfv_popup_add_sender(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids = message_list_get_selected(emfv->list); CamelMessageInfo *info; const char *addr; @@ -858,16 +877,18 @@ emfv_popup_add_sender(GtkWidget *w, EMFolderView *emfv) } static void -emfv_popup_apply_filters(GtkWidget *w, EMFolderView *emfv) +emfv_popup_apply_filters(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids = message_list_get_selected(emfv->list); mail_filter_on_demand(emfv->folder, uids); } static void -emfv_popup_filter_junk(GtkWidget *w, EMFolderView *emfv) +emfv_popup_filter_junk(EPopup *ep, EPopupItem *pitem, void *data) { + EMFolderView *emfv = data; GPtrArray *uids = message_list_get_selected(emfv->list); mail_filter_junk(emfv->folder, uids); @@ -878,8 +899,9 @@ emfv_popup_filter_junk(GtkWidget *w, EMFolderView *emfv) #define EMFV_POPUP_AUTO_TYPE(autotype, name, type) \ static void \ -name(GtkWidget *w, EMFolderView *emfv) \ +name(EPopup *ep, EPopupItem *item, void *data) \ { \ + EMFolderView *emfv = data; \ autotype(emfv, type); \ } @@ -895,83 +917,87 @@ EMFV_POPUP_AUTO_TYPE(filter_type_current, emfv_popup_filter_mlist, AUTO_MLIST) /* TODO: Move some of these to be 'standard' menu's */ -static EMPopupItem emfv_popup_menu[] = { - { EM_POPUP_ITEM, "00.emfv.00", N_("_Open"), G_CALLBACK(emfv_popup_open), NULL, NULL, 0 }, - { EM_POPUP_ITEM, "00.emfv.01", N_("_Edit as New Message..."), G_CALLBACK(emfv_popup_edit), NULL, NULL, EM_POPUP_SELECT_EDIT }, - { EM_POPUP_ITEM, "00.emfv.02", N_("_Save As..."), G_CALLBACK(emfv_popup_saveas), NULL, "stock_save-as", 0 }, - { EM_POPUP_ITEM, "00.emfv.03", N_("_Print"), G_CALLBACK(emfv_popup_print), NULL, "stock_print", 0 }, - - { EM_POPUP_BAR, "10.emfv" }, - { EM_POPUP_ITEM, "10.emfv.00", N_("_Reply to Sender"), G_CALLBACK(emfv_popup_reply_sender), NULL, "stock_mail-reply", EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "10.emfv.01", N_("Reply to _List"), G_CALLBACK(emfv_popup_reply_list), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, - { EM_POPUP_ITEM, "10.emfv.02", N_("Reply to _All"), G_CALLBACK(emfv_popup_reply_all), NULL, "stock_mail-reply-to-all", EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "10.emfv.03", N_("_Forward"), G_CALLBACK(emfv_popup_forward), NULL, "stock_mail-forward", EM_POPUP_SELECT_MANY }, - - { EM_POPUP_BAR, "20.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_FLAG_FOLLOWUP|EM_POPUP_SELECT_FLAG_COMPLETED|EM_POPUP_SELECT_FLAG_CLEAR }, - { EM_POPUP_ITEM, "20.emfv.00", N_("Follo_w Up..."), G_CALLBACK(emfv_popup_flag_followup), NULL, "stock_mail-flag-for-followup", EM_POPUP_SELECT_FLAG_FOLLOWUP }, - { EM_POPUP_ITEM, "20.emfv.01", N_("Fla_g Completed"), G_CALLBACK(emfv_popup_flag_completed), NULL, NULL, EM_POPUP_SELECT_FLAG_COMPLETED }, - { EM_POPUP_ITEM, "20.emfv.02", N_("Cl_ear Flag"), G_CALLBACK(emfv_popup_flag_clear), NULL, NULL, EM_POPUP_SELECT_FLAG_CLEAR }, - - { EM_POPUP_BAR, "30.emfv" }, - { EM_POPUP_ITEM, "30.emfv.00", N_("Mar_k as Read"), G_CALLBACK(emfv_popup_mark_read), NULL, "stock_mail-open", EM_POPUP_SELECT_MARK_READ }, - { EM_POPUP_ITEM, "30.emfv.01", N_("Mark as _Unread"), G_CALLBACK(emfv_popup_mark_unread), NULL, "stock_mail-unread", EM_POPUP_SELECT_MARK_UNREAD }, - { EM_POPUP_ITEM, "30.emfv.02", N_("Mark as _Important"), G_CALLBACK(emfv_popup_mark_important), NULL, "stock_mail-priority-high", EM_POPUP_SELECT_MARK_IMPORTANT }, - { EM_POPUP_ITEM, "30.emfv.03", N_("_Mark as Unimportant"), G_CALLBACK(emfv_popup_mark_unimportant), NULL, NULL, EM_POPUP_SELECT_MARK_UNIMPORTANT }, - { EM_POPUP_ITEM, "30.emfv.04", N_("Mark as _Junk"), G_CALLBACK(emfv_popup_mark_junk), NULL, "stock_spam", EM_POPUP_SELECT_MARK_JUNK }, - { EM_POPUP_ITEM, "30.emfv.05", N_("Mark as _Not Junk"), G_CALLBACK(emfv_popup_mark_nojunk), NULL, "stock_not-spam", EM_POPUP_SELECT_MARK_NOJUNK }, - - { EM_POPUP_BAR, "40.emfv" }, - { EM_POPUP_ITEM, "40.emfv.00", N_("_Delete"), G_CALLBACK(emfv_popup_delete), NULL, "stock_delete", EM_POPUP_SELECT_DELETE }, - { EM_POPUP_ITEM, "40.emfv.01", N_("U_ndelete"), G_CALLBACK(emfv_popup_undelete), NULL, "stock_undelete", EM_POPUP_SELECT_UNDELETE }, - - { EM_POPUP_BAR, "50.emfv" }, - { EM_POPUP_ITEM, "50.emfv.00", N_("Mo_ve to Folder..."), G_CALLBACK(emfv_popup_move) }, - { EM_POPUP_ITEM, "50.emfv.01", N_("_Copy to Folder..."), G_CALLBACK(emfv_popup_copy) }, - - { EM_POPUP_BAR, "60.label" }, - { EM_POPUP_SUBMENU, "60.label.00", N_("Label") }, - { EM_POPUP_IMAGE, "60.label.00/00.label", N_("None"), G_CALLBACK(emfv_popup_label_clear) }, - { EM_POPUP_BAR, "60.label.00/00.label.00" }, - - { EM_POPUP_BAR, "70.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ADD_SENDER }, - { EM_POPUP_ITEM, "70.emfv.00", N_("Add Sender to Address_book"), G_CALLBACK(emfv_popup_add_sender), NULL, NULL, EM_POPUP_SELECT_ADD_SENDER }, - - { EM_POPUP_BAR, "80.emfv" }, - { EM_POPUP_ITEM, "80.emfv.00", N_("Appl_y Filters"), G_CALLBACK(emfv_popup_apply_filters), NULL, "stock_mail-filters-apply" }, - { EM_POPUP_ITEM, "80.emfv.01", N_("F_ilter Junk"), G_CALLBACK(emfv_popup_filter_junk), NULL, "stock_spam" }, - - { EM_POPUP_BAR, "90.filter", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_SUBMENU, "90.filter.00", N_("Crea_te Rule From Message"), NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/00.00", N_("VFolder on _Subject"), G_CALLBACK(emfv_popup_vfolder_subject), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/00.01", N_("VFolder on Se_nder"), G_CALLBACK(emfv_popup_vfolder_sender), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/00.02", N_("VFolder on _Recipients"), G_CALLBACK(emfv_popup_vfolder_recipients), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/00.03", N_("VFolder on Mailing _List"), - G_CALLBACK(emfv_popup_vfolder_mlist), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, - - { EM_POPUP_BAR, "90.filter.00/10", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/10.00", N_("Filter on Sub_ject"), G_CALLBACK(emfv_popup_filter_subject), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/10.01", N_("Filter on Sen_der"), G_CALLBACK(emfv_popup_filter_sender), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/10.02", N_("Filter on Re_cipients"), G_CALLBACK(emfv_popup_filter_recipients), NULL, NULL, EM_POPUP_SELECT_ONE }, - { EM_POPUP_ITEM, "90.filter.00/10.03", N_("Filter on _Mailing List"), - G_CALLBACK(emfv_popup_filter_mlist), NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, +static EPopupItem emfv_popup_menu[] = { + { E_POPUP_ITEM, "00.emfv.00", N_("_Open"), emfv_popup_open, NULL, NULL, 0 }, + { E_POPUP_ITEM, "00.emfv.01", N_("_Edit as New Message..."), emfv_popup_edit, NULL, NULL, EM_POPUP_SELECT_EDIT }, + { E_POPUP_ITEM, "00.emfv.02", N_("_Save As..."), emfv_popup_saveas, NULL, "stock_save-as", 0 }, + { E_POPUP_ITEM, "00.emfv.03", N_("_Print"), emfv_popup_print, NULL, "stock_print", 0 }, + + { E_POPUP_BAR, "10.emfv" }, + { E_POPUP_ITEM, "10.emfv.00", N_("_Reply to Sender"), emfv_popup_reply_sender, NULL, "stock_mail-reply", EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "10.emfv.01", N_("Reply to _List"), emfv_popup_reply_list, NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, + { E_POPUP_ITEM, "10.emfv.02", N_("Reply to _All"), emfv_popup_reply_all, NULL, "stock_mail-reply-to-all", EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "10.emfv.03", N_("_Forward"), emfv_popup_forward, NULL, "stock_mail-forward", EM_POPUP_SELECT_MANY }, + + { E_POPUP_BAR, "20.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_FLAG_FOLLOWUP|EM_POPUP_SELECT_FLAG_COMPLETED|EM_POPUP_SELECT_FLAG_CLEAR }, + { E_POPUP_ITEM, "20.emfv.00", N_("Follo_w Up..."), emfv_popup_flag_followup, NULL, "stock_mail-flag-for-followup", EM_POPUP_SELECT_FLAG_FOLLOWUP }, + { E_POPUP_ITEM, "20.emfv.01", N_("Fla_g Completed"), emfv_popup_flag_completed, NULL, NULL, EM_POPUP_SELECT_FLAG_COMPLETED }, + { E_POPUP_ITEM, "20.emfv.02", N_("Cl_ear Flag"), emfv_popup_flag_clear, NULL, NULL, EM_POPUP_SELECT_FLAG_CLEAR }, + + { E_POPUP_BAR, "30.emfv" }, + { E_POPUP_ITEM, "30.emfv.00", N_("Mar_k as Read"), emfv_popup_mark_read, NULL, "stock_mail-open", EM_POPUP_SELECT_MARK_READ }, + { E_POPUP_ITEM, "30.emfv.01", N_("Mark as _Unread"), emfv_popup_mark_unread, NULL, "stock_mail-unread", EM_POPUP_SELECT_MARK_UNREAD }, + { E_POPUP_ITEM, "30.emfv.02", N_("Mark as _Important"), emfv_popup_mark_important, NULL, "stock_mail-priority-high", EM_POPUP_SELECT_MARK_IMPORTANT }, + { E_POPUP_ITEM, "30.emfv.03", N_("_Mark as Unimportant"), emfv_popup_mark_unimportant, NULL, NULL, EM_POPUP_SELECT_MARK_UNIMPORTANT }, + { E_POPUP_ITEM, "30.emfv.04", N_("Mark as _Junk"), emfv_popup_mark_junk, NULL, "stock_spam", EM_POPUP_SELECT_MARK_JUNK }, + { E_POPUP_ITEM, "30.emfv.05", N_("Mark as _Not Junk"), emfv_popup_mark_nojunk, NULL, "stock_not-spam", EM_POPUP_SELECT_MARK_NOJUNK }, + + { E_POPUP_BAR, "40.emfv" }, + { E_POPUP_ITEM, "40.emfv.00", N_("_Delete"), emfv_popup_delete, NULL, "stock_delete", EM_POPUP_SELECT_DELETE }, + { E_POPUP_ITEM, "40.emfv.01", N_("U_ndelete"), emfv_popup_undelete, NULL, "stock_undelete", EM_POPUP_SELECT_UNDELETE }, + + { E_POPUP_BAR, "50.emfv" }, + { E_POPUP_ITEM, "50.emfv.00", N_("Mo_ve to Folder..."), emfv_popup_move }, + { E_POPUP_ITEM, "50.emfv.01", N_("_Copy to Folder..."), emfv_popup_copy }, + + { E_POPUP_BAR, "60.label" }, + { E_POPUP_SUBMENU, "60.label.00", N_("Label") }, + { E_POPUP_IMAGE, "60.label.00/00.label", N_("None"), emfv_popup_label_clear }, + { E_POPUP_BAR, "60.label.00/00.label.00" }, + + { E_POPUP_BAR, "70.emfv", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ADD_SENDER }, + { E_POPUP_ITEM, "70.emfv.00", N_("Add Sender to Address_book"), emfv_popup_add_sender, NULL, NULL, EM_POPUP_SELECT_ADD_SENDER }, + + { E_POPUP_BAR, "80.emfv" }, + { E_POPUP_ITEM, "80.emfv.00", N_("Appl_y Filters"), emfv_popup_apply_filters, NULL, "stock_mail-filters-apply" }, + { E_POPUP_ITEM, "80.emfv.01", N_("F_ilter Junk"), emfv_popup_filter_junk, NULL, "stock_spam" }, + + { E_POPUP_BAR, "90.filter", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_SUBMENU, "90.filter.00", N_("Crea_te Rule From Message"), NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/00.00", N_("VFolder on _Subject"), emfv_popup_vfolder_subject, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/00.01", N_("VFolder on Se_nder"), emfv_popup_vfolder_sender, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/00.02", N_("VFolder on _Recipients"), emfv_popup_vfolder_recipients, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/00.03", N_("VFolder on Mailing _List"), + emfv_popup_vfolder_mlist, NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, + + { E_POPUP_BAR, "90.filter.00/10", NULL, NULL, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/10.00", N_("Filter on Sub_ject"), emfv_popup_filter_subject, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/10.01", N_("Filter on Sen_der"), emfv_popup_filter_sender, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/10.02", N_("Filter on Re_cipients"), emfv_popup_filter_recipients, NULL, NULL, EM_POPUP_SELECT_ONE }, + { E_POPUP_ITEM, "90.filter.00/10.03", N_("Filter on _Mailing List"), + emfv_popup_filter_mlist, NULL, NULL, EM_POPUP_SELECT_ONE|EM_POPUP_SELECT_MAILING_LIST }, }; static void -emfv_popup_labels_free(void *data) +emfv_popup_labels_free(EPopup *ep, GSList *l, void *data) { - GSList *l = data; - while (l) { GSList *n = l->next; - struct _emfv_label_item *item = l->data; + EPopupItem *item = l->data; - g_free(item->item.path); + g_free(item->path); g_free(item); g_slist_free_1(l); l = n; } } + +static void +emfv_popup_items_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); +} static void emfv_popup(EMFolderView *emfv, GdkEvent *event) @@ -979,37 +1005,31 @@ emfv_popup(EMFolderView *emfv, GdkEvent *event) GSList *menus = NULL, *l, *label_list = NULL; GtkMenu *menu; EMPopup *emp; - EMPopupTarget *target; + EMPopupTargetSelect *target; int i; emp = em_popup_new("com.ximian.mail.folderview.popup.select"); - target = em_folder_view_get_popup_target(emfv); + target = em_folder_view_get_popup_target(emfv, emp); - for (i=0;i<sizeof(emfv_popup_menu)/sizeof(emfv_popup_menu[0]);i++) { - EMPopupItem *item = &emfv_popup_menu[i]; + for (i=0;i<sizeof(emfv_popup_menu)/sizeof(emfv_popup_menu[0]);i++) + menus = g_slist_prepend(menus, &emfv_popup_menu[i]); - item->activate_data = emfv; - menus = g_slist_prepend(menus, item); - } - - em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); + e_popup_add_items((EPopup *)emp, menus, emfv_popup_items_free, emfv); i = 1; for (l = mail_config_get_labels(); l; l = l->next) { - struct _emfv_label_item *item; + EPopupItem *item; MailConfigLabel *label = l->data; GdkPixmap *pixmap; GdkColor colour; GdkGC *gc; item = g_malloc0(sizeof(*item)); - item->item.type = EM_POPUP_IMAGE; - item->item.path = g_strdup_printf("60.label.00/00.label.%02d", i++); - item->item.label = label->name; - item->item.activate = G_CALLBACK(emfv_popup_label_set); - item->item.activate_data = item; - item->emfv = emfv; - item->label = label->tag; + item->type = E_POPUP_IMAGE; + item->path = g_strdup_printf("60.label.00/00.label.%02d", i++); + item->label = label->name; + item->activate = emfv_popup_label_set; + item->user_data = label->tag; gdk_color_parse(label->colour, &colour); gdk_color_alloc(gdk_colormap_get_system(), &colour); @@ -1020,15 +1040,15 @@ emfv_popup(EMFolderView *emfv, GdkEvent *event) gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, 16, 16); gdk_gc_unref(gc); - item->item.image = gtk_image_new_from_pixmap(pixmap, NULL); - gtk_widget_show(item->item.image); + item->image = gtk_image_new_from_pixmap(pixmap, NULL); + gtk_widget_show(item->image); label_list = g_slist_prepend(label_list, item); } - em_popup_add_items(emp, label_list, emfv_popup_labels_free); + e_popup_add_items((EPopup *)emp, label_list, emfv_popup_labels_free, emfv); - menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + menu = e_popup_create_menu_once((EPopup *)emp, (EPopupTarget *)target, target->target.mask, target->target.mask); if (event == NULL || event->type == GDK_KEY_PRESS) { /* FIXME: menu pos function */ @@ -1047,7 +1067,7 @@ emfv_popup(EMFolderView *emfv, GdkEvent *event) static void \ from(BonoboUIComponent *uid, void *data, const char *path) \ { \ - to(NULL, (EMFolderView *)data); \ + to(NULL, NULL, data); \ } EMFV_MAP_CALLBACK(emfv_add_sender_addressbook, emfv_popup_add_sender) @@ -1619,15 +1639,31 @@ emfv_enable_menus(EMFolderView *emfv) guint32 disable_mask; GString *name; GSList *l; - EMPopupTarget *t; if (emfv->uic == NULL) return; + { + if (emfv->menu) { + if (emfv->folder) { + EMMenuTargetSelect *t; + + t = em_menu_target_new_select(emfv->menu, emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); + e_menu_update_target((EMenu *)emfv->menu, t); + } else { + e_menu_update_target((EMenu *)emfv->menu, NULL); + } + } + } + if (emfv->folder) { - t = em_folder_view_get_popup_target(emfv); - disable_mask = t->mask; - em_popup_target_free(t); + EMPopup *emp = em_popup_new("dummy"); + EMPopupTargetSelect *t; + + t = em_folder_view_get_popup_target(emfv, emp); + disable_mask = t->target.mask; + e_popup_target_free((EPopup *)emp, t); + g_object_unref(emp); } else { disable_mask = ~0; } @@ -1720,6 +1756,9 @@ emfv_activate(EMFolderView *emfv, BonoboUIComponent *uic, int act) { struct _EMFolderViewPrivate *p = emfv->priv; + if (emfv->menu) + e_menu_activate((EMenu *)emfv->menu, uic, act); + if (act) { em_format_mode_t style; gboolean state; @@ -1856,29 +1895,29 @@ int em_folder_view_print(EMFolderView *emfv, int preview) return 0; } -EMPopupTarget * -em_folder_view_get_popup_target(EMFolderView *emfv) +EMPopupTargetSelect * +em_folder_view_get_popup_target(EMFolderView *emfv, EMPopup *emp) { - EMPopupTarget *t; + EMPopupTargetSelect *t; - t = em_popup_target_new_select(emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); - t->widget = (GtkWidget *)emfv; + t = em_popup_target_new_select(emp, emfv->folder, emfv->folder_uri, message_list_get_selected(emfv->list)); + t->target.widget = (GtkWidget *)emfv; if (emfv->list->threaded) - t->mask &= ~EM_FOLDER_VIEW_SELECT_THREADED; + t->target.mask &= ~EM_FOLDER_VIEW_SELECT_THREADED; if (message_list_hidden(emfv->list) != 0) - t->mask &= ~EM_FOLDER_VIEW_SELECT_HIDDEN; + t->target.mask &= ~EM_FOLDER_VIEW_SELECT_HIDDEN; if (message_list_can_select(emfv->list, MESSAGE_LIST_SELECT_NEXT, 0, 0)) - t->mask &= ~EM_FOLDER_VIEW_SELECT_NEXT_MSG; + t->target.mask &= ~EM_FOLDER_VIEW_SELECT_NEXT_MSG; if (message_list_can_select(emfv->list, MESSAGE_LIST_SELECT_PREVIOUS, 0, 0)) - t->mask &= ~EM_FOLDER_VIEW_SELECT_PREV_MSG; + t->target.mask &= ~EM_FOLDER_VIEW_SELECT_PREV_MSG; /* See bug #54770 */ if (!emfv->hide_deleted) - t->mask &= ~EM_POPUP_SELECT_DELETE; + t->target.mask &= ~EM_POPUP_SELECT_DELETE; return t; } @@ -2092,38 +2131,32 @@ emfv_format_link_clicked(EMFormatHTMLDisplay *efhd, const char *uri, EMFolderVie } } -struct _EMFVPopupItem { - EMPopupItem item; - - EMFolderView *emfv; - char *uri; -}; - static void -emp_uri_popup_link_copy(GtkWidget *w, struct _EMFVPopupItem *item) +emp_uri_popup_link_copy(EPopup *ep, EPopupItem *pitem, void *data) { - struct _EMFolderViewPrivate *p = item->emfv->priv; + EMFolderView *emfv = data; + struct _EMFolderViewPrivate *p = emfv->priv; g_free(p->selection_uri); - p->selection_uri = g_strdup(item->uri); + p->selection_uri = g_strdup(pitem->user_data); gtk_selection_owner_set(p->invisible, GDK_SELECTION_PRIMARY, gtk_get_current_event_time()); gtk_selection_owner_set(p->invisible, GDK_SELECTION_CLIPBOARD, gtk_get_current_event_time()); } -static struct _EMFVPopupItem emfv_uri_popups[] = { - { { EM_POPUP_ITEM, "00.uri.01", N_("_Copy Link Location"), G_CALLBACK(emp_uri_popup_link_copy), NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, }, +static EPopupItem emfv_uri_popups[] = { + { E_POPUP_ITEM, "00.uri.01", N_("_Copy Link Location"), emp_uri_popup_link_copy, NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, }; static void -emfv_uri_popup_free(GSList *list) +emfv_uri_popup_free(EPopup *ep, GSList *list, void *data) { while (list) { GSList *n = list->next; - struct _EMFVPopupItem *item = list->data; + struct _EPopupItem *item = list->data; - g_free(item->uri); - g_object_unref(item->emfv); + g_free(item->user_data); + item->user_data = NULL; g_slist_free_1(list); list = n; @@ -2134,7 +2167,7 @@ static int emfv_format_popup_event(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const char *uri, CamelMimePart *part, EMFolderView *emfv) { EMPopup *emp; - EMPopupTarget *target; + EPopupTarget *target; GtkMenu *menu; if (uri == NULL && part == NULL) { @@ -2153,24 +2186,23 @@ emfv_format_popup_event(EMFormatHTMLDisplay *efhd, GdkEventButton *event, const emp = em_popup_new("com.ximian.mail.folderview.popup.uri"); if (part) - target = em_popup_target_new_part(part, NULL); + target = (EPopupTarget *)em_popup_target_new_part(emp, part, NULL); else { GSList *menus = NULL; int i; + EMPopupTargetURI *t; - target = em_popup_target_new_uri(uri); + t = em_popup_target_new_uri(emp, uri); + target = (EPopupTarget *)t; for (i=0;i<sizeof(emfv_uri_popups)/sizeof(emfv_uri_popups[0]);i++) { - emfv_uri_popups[i].item.activate_data = &emfv_uri_popups[i]; - emfv_uri_popups[i].emfv = emfv; - g_object_ref(emfv); - emfv_uri_popups[i].uri = g_strdup(target->data.uri); + emfv_uri_popups[i].user_data = g_strdup(t->uri); menus = g_slist_prepend(menus, &emfv_uri_popups[i]); } - em_popup_add_items(emp, menus, (GDestroyNotify)emfv_uri_popup_free); + e_popup_add_items((EPopup *)emp, menus, emfv_uri_popup_free, emfv); } - menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + menu = e_popup_create_menu_once((EPopup *)emp, target, target->mask, target->mask); gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); return TRUE; diff --git a/mail/em-folder-view.h b/mail/em-folder-view.h index 3cf9586bb4..a6ece9fcb1 100644 --- a/mail/em-folder-view.h +++ b/mail/em-folder-view.h @@ -75,6 +75,9 @@ struct _EMFolderView { GSList *ui_files; /* const char * list, TODO: should this be on class? */ const char *ui_app_name; + /* used to manage some menus, particularly plugins */ + struct _EMMenu *menu; + /* for proxying jobs to main or other threads */ struct _MailAsyncEvent *async; @@ -119,7 +122,7 @@ GtkWidget *em_folder_view_new(void); #define em_folder_view_set_folder_uri(emfv, uri) EM_FOLDER_VIEW_GET_CLASS (emfv)->set_folder_uri((emfv), (uri)) #define em_folder_view_set_message(emfv, uid, nomarkseen) EM_FOLDER_VIEW_GET_CLASS (emfv)->set_message((emfv), (uid), (nomarkseen)) -struct _EMPopupTarget *em_folder_view_get_popup_target(EMFolderView *emfv); +EMPopupTargetSelect *em_folder_view_get_popup_target(EMFolderView *emfv, EMPopup *emp); int em_folder_view_mark_selected(EMFolderView *emfv, guint32 mask, guint32 set); int em_folder_view_open_selected(EMFolderView *emfv); diff --git a/mail/em-format-hook.c b/mail/em-format-hook.c new file mode 100644 index 0000000000..c62e75bb67 --- /dev/null +++ b/mail/em-format-hook.c @@ -0,0 +1,270 @@ +/* -*- 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 "em-format-hook.h" + +#include <e-util/e-icon-factory.h> + +#include <libgnome/gnome-i18n.h> + + +/* class name -> klass map for EMFormat and subclasses */ +static GHashTable *emfh_types; + +/* ********************************************************************** */ + +/* Mail formatter handler plugin */ + +/* + <hook class="com.novell.evolution.mail.format:1.0"> + <group id="EMFormatHTML"> + <item flags="inline,inline_disposition" + mime_type="text/vcard" + format="format_vcard"/> + </group> + </hook> + +*/ + +static void *emfh_parent_class; +#define emfh ((EMFormatHook *)eph) + +static const EPluginHookTargetKey emfh_flag_map[] = { + { "inline", EM_FORMAT_HANDLER_INLINE }, + { "inline_disposition", EM_FORMAT_HANDLER_INLINE_DISPOSITION }, + { 0 } +}; + +static void +emfh_format_format(EMFormat *md, struct _CamelStream *stream, struct _CamelMimePart *part, const EMFormatHandler *info) +{ + struct _EMFormatHookItem *item = (EMFormatHookItem *)info; + EMFormatHookTarget target = { + md, stream, part, item + }; + + e_plugin_invoke(item->hook->hook.plugin, item->format, &target); +} + +static void +emfh_free_item(struct _EMFormatHookItem *item) +{ + /* FIXME: remove from formatter class */ + + g_free(item->handler.mime_type); + g_free(item->format); + g_free(item); +} + +static void +emfh_free_group(struct _EMFormatHookGroup *group) +{ + g_slist_foreach(group->items, (GFunc)emfh_free_item, NULL); + g_slist_free(group->items); + + g_free(group->id); + g_free(group); +} + +static struct _EMFormatHookItem * +emfh_construct_item(EPluginHook *eph, EMFormatHookGroup *group, xmlNodePtr root) +{ + struct _EMFormatHookItem *item; + + printf(" loading group item\n"); + item = g_malloc0(sizeof(*item)); + + item->handler.mime_type = e_plugin_xml_prop(root, "mime_type"); + item->handler.flags = e_plugin_hook_mask(root, emfh_flag_map, "flags"); + item->format = e_plugin_xml_prop(root, "format"); + + item->handler.handler = emfh_format_format; + item->hook = emfh; + + if (item->handler.mime_type == NULL || item->format == NULL) + goto error; + + printf(" type='%s' format='%s'\n", item->handler.mime_type, item->format); + + return item; +error: + printf("error!\n"); + emfh_free_item(item); + return NULL; +} + +static struct _EMFormatHookGroup * +emfh_construct_group(EPluginHook *eph, xmlNodePtr root) +{ + struct _EMFormatHookGroup *group; + xmlNodePtr node; + + printf(" loading group\n"); + group = g_malloc0(sizeof(*group)); + + group->id = e_plugin_xml_prop(root, "id"); + if (group->id == NULL) + goto error; + + node = root->children; + while (node) { + if (0 == strcmp(node->name, "item")) { + struct _EMFormatHookItem *item; + + item = emfh_construct_item(eph, group, node); + if (item) + group->items = g_slist_append(group->items, item); + } + node = node->next; + } + + return group; +error: + emfh_free_group(group); + return NULL; +} + +static int +emfh_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root) +{ + xmlNodePtr node; + + printf("loading format hook\n"); + + if (((EPluginHookClass *)emfh_parent_class)->construct(eph, ep, root) == -1) + return -1; + + node = root->children; + while (node) { + if (strcmp(node->name, "group") == 0) { + struct _EMFormatHookGroup *group; + + group = emfh_construct_group(eph, node); + if (group) { + EMFormatClass *klass; + + if (emfh_types + && (klass = g_hash_table_lookup(emfh_types, group->id))) { + GSList *l = group->items; + + for (;l;l=g_slist_next(l)) { + EMFormatHookItem *item = l->data; + /* TODO: only add handlers if enabled? */ + em_format_class_add_handler(klass, &item->handler); + } + } + /* We don't actually need to keep this around once its set on the class */ + emfh->groups = g_slist_append(emfh->groups, group); + } + } + node = node->next; + } + + eph->plugin = ep; + + return 0; +} + +static void +emfh_enable(EPluginHook *eph, int state) +{ + GSList *g, *l; + EMFormatClass *klass; + + g = emfh->groups; + if (emfh_types == NULL) + return; + + for (;g;g=g_slist_next(g)) { + struct _EMFormatHookGroup *group = g->data; + + klass = g_hash_table_lookup(emfh_types, group->id); + for (l=group->items;l;g=g_slist_next(l)) { + EMFormatHookItem *item = l->data; + + if (state) + em_format_class_add_handler(klass, &item->handler); + else + em_format_class_remove_handler(klass, &item->handler); + } + } +} + +static void +emfh_finalise(GObject *o) +{ + EPluginHook *eph = (EPluginHook *)o; + + g_slist_foreach(emfh->groups, (GFunc)emfh_free_group, NULL); + g_slist_free(emfh->groups); + + ((GObjectClass *)emfh_parent_class)->finalize(o); +} + +static void +emfh_class_init(EPluginHookClass *klass) +{ + ((GObjectClass *)klass)->finalize = emfh_finalise; + klass->construct = emfh_construct; + klass->enable = emfh_enable; + klass->id = "com.novell.evolution.mail.format:1.0"; +} + +GType +em_format_hook_get_type(void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof(EMFormatHookClass), NULL, NULL, (GClassInitFunc) emfh_class_init, NULL, NULL, + sizeof(EMFormatHook), 0, (GInstanceInitFunc) NULL, + }; + + emfh_parent_class = g_type_class_ref(e_plugin_hook_get_type()); + type = g_type_register_static(e_plugin_hook_get_type(), "EMFormatHook", &info, 0); + } + + return type; +} + +void em_format_hook_register_type(GType type) +{ + EMFormatClass *klass; + + if (emfh_types == NULL) + emfh_types = g_hash_table_new(g_str_hash, g_str_equal); + + printf("registering formatter type '%s'\n", g_type_name(type)); + + klass = g_type_class_ref(type); + g_hash_table_insert(emfh_types, (void *)g_type_name(type), klass); +} diff --git a/mail/em-format-hook.h b/mail/em-format-hook.h new file mode 100644 index 0000000000..20010604f5 --- /dev/null +++ b/mail/em-format-hook.h @@ -0,0 +1,97 @@ +/* -*- 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 __EM_FORMAT_HOOK_H__ +#define __EM_FORMAT_HOOK_H__ + +#include <glib-object.h> +#include "e-util/e-msgport.h" +#include "e-util/e-plugin.h" + +#include "em-format.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +typedef struct _EMFormatHookItem EMFormatHookItem; +typedef struct _EMFormatHookGroup EMFormatHookGroup; +typedef struct _EMFormatHook EMFormatHook; +typedef struct _EMFormatHookClass EMFormatHookClass; + +typedef struct _EMFormatHookTarget EMFormatHookTarget; + +typedef void (*EMFormatHookFunc)(struct _EPlugin *plugin, EMFormatHookTarget *data); + +struct _EMFormatHookTarget { + struct _EMFormat *format; + struct _CamelStream *stream; + struct _CamelMimePart *part; + struct _EMFormatHookItem *item; +}; + +struct _EMFormatHookItem { + EMFormatHandler handler; + + struct _EMFormatHook *hook; /* parent pointer */ + char *format; /* format handler */ +}; + +struct _EMFormatHookGroup { + struct _EMFormatHook *hook; /* parent pointer */ + char *id; /* target formatter id */ + GSList *items; /* items to consider */ +}; + +/** + * struct _EMFormatHook - Mail formatter hook. + * + * @hook: + * @groups: + * + * The Mail formatter hook links all of the plugin formatter hooks + * into the relevent formatter classes. + **/ +struct _EMFormatHook { + EPluginHook hook; + + GSList *groups; +}; + +struct _EMFormatHookClass { + EPluginHookClass hook_class; + + /* which class to add matching items to */ + GHashTable *format_classes; +}; + +GType em_format_hook_get_type(void); + +/* register a type as a possible formatter hook point */ +void em_format_hook_register_type(GType type); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_FORMAT_HOOK_H__ */ diff --git a/mail/em-format-html-display.c b/mail/em-format-html-display.c index 96e65e2d5c..e73c4af6f2 100644 --- a/mail/em-format-html-display.c +++ b/mail/em-format-html-display.c @@ -52,12 +52,7 @@ #include <glade/glade.h> #include <libgnomevfs/gnome-vfs-mime-handlers.h> - -#if 0 -#include <libgnomevfs/gnome-vfs-utils.h> -#include <libgnomevfs/gnome-vfs-mime-utils.h> -#include <libgnomevfs/gnome-vfs-mime.h> -#endif +#include <libgnome/gnome-i18n.h> #include <bonobo/bonobo-control-frame.h> #include <bonobo/bonobo-stream-memory.h> @@ -1043,21 +1038,35 @@ static void efhd_format_source(EMFormat *emf, CamelStream *stream, CamelMimePart /* if it hasn't been processed yet, format the attachment */ static void -efhd_attachment_show(GtkWidget *w, struct _attach_puri *info) +efhd_attachment_show(EPopup *ep, EPopupItem *item, void *data) { + struct _attach_puri *info = data; + d(printf("show attachment button called\n")); info->shown = ~info->shown; em_format_set_inline(info->puri.format, info->puri.part_id, info->shown); } -static EMPopupItem efhd_menu_items[] = { - { EM_POPUP_BAR, "05.display", }, - { EM_POPUP_ITEM, "05.display.00", N_("_View Inline"), G_CALLBACK(efhd_attachment_show) }, - { EM_POPUP_ITEM, "05.display.00", N_("_Hide"), G_CALLBACK(efhd_attachment_show) }, +static void +efhd_attachment_button_show(GtkWidget *w, void *data) +{ + efhd_attachment_show(NULL, NULL, data); +} + +static EPopupItem efhd_menu_items[] = { + { E_POPUP_BAR, "05.display", }, + { E_POPUP_ITEM, "05.display.00", N_("_View Inline"), efhd_attachment_show }, + { E_POPUP_ITEM, "05.display.00", N_("_Hide"), efhd_attachment_show }, }; static void +efhd_menu_items_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); +} + +static void efhd_popup_place_widget(GtkMenu *menu, int *x, int *y, gboolean *push_in, gpointer user_data) { GtkWidget *w = user_data; @@ -1073,8 +1082,8 @@ efhd_attachment_popup(GtkWidget *w, GdkEventButton *event, struct _attach_puri * GtkMenu *menu; GSList *menus = NULL; EMPopup *emp; - EMPopupTarget *target; - EMPopupItem *item; + EMPopupTargetPart *target; + EPopupItem *item; d(printf("attachment popup, button %d\n", event->button)); @@ -1084,22 +1093,20 @@ efhd_attachment_popup(GtkWidget *w, GdkEventButton *event, struct _attach_puri * } emp = em_popup_new("com.ximian.mail.formathtmldisplay.popup.part"); - target = em_popup_target_new_part(info->puri.part, info->handle?info->handle->mime_type:NULL); - target->widget = w; + target = em_popup_target_new_part(emp, info->puri.part, info->handle?info->handle->mime_type:NULL); + target->target.widget = w; /* add our local menus */ if (info->handle) { /* show/hide menus, only if we have an inline handler */ - efhd_menu_items[0].activate_data = info; menus = g_slist_prepend(menus, &efhd_menu_items[0]); item = &efhd_menu_items[info->shown?2:1]; - item->activate_data = info; menus = g_slist_prepend(menus, item); } - em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); + e_popup_add_items((EPopup *)emp, menus, efhd_menu_items_free, info); - menu = em_popup_create_menu_once(emp, target, target->mask, target->mask); + menu = e_popup_create_menu_once((EPopup *)emp, (EPopupTarget *)target, target->target.mask, target->target.mask); if (event) gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time); else @@ -1217,7 +1224,7 @@ efhd_attachment_button(EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObje button = gtk_button_new(); if (info->handle) - g_signal_connect(button, "clicked", G_CALLBACK(efhd_attachment_show), info); + g_signal_connect(button, "clicked", G_CALLBACK(efhd_attachment_button_show), info); else { gtk_widget_set_sensitive(button, FALSE); GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS); diff --git a/mail/em-format-html.c b/mail/em-format-html.c index 60baac02d5..6d72ce3f1e 100644 --- a/mail/em-format-html.c +++ b/mail/em-format-html.c @@ -41,9 +41,7 @@ #include <gtkhtml/gtkhtml-stream.h> #include <gtkhtml/htmlengine.h> -#include <libgnomevfs/gnome-vfs-utils.h> -#include <libgnomevfs/gnome-vfs-mime-utils.h> -#include <libgnomevfs/gnome-vfs-mime-handlers.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-mime-message.h> #include <camel/camel-stream.h> diff --git a/mail/em-format-html.h b/mail/em-format-html.h index 8ad399315a..ae6747dfdc 100644 --- a/mail/em-format-html.h +++ b/mail/em-format-html.h @@ -49,8 +49,32 @@ struct _CamelStream; typedef struct _EMFormatHTMLJob EMFormatHTMLJob; +/** + * struct _EMFormatHTMLJob - A formatting job. + * + * @next: Double linked list header. + * @prev: Double linked list header. + * @format: Set by allocation function. + * @stream: Free for use by caller. + * @puri_level: Set by allocation function. + * @base: Set by allocation function, used to save state. + * @callback: This callback will always be invoked, only once, even if the user + * cancelled the display. So the callback should free any extra data + * it allocated every time it is called. + * @u: Union data, free for caller to use. + * + * This object is used to queue a long-running-task which cannot be + * processed in the primary thread. When its turn comes, the job will + * be de-queued and the @callback invoked to perform its processing, + * restoring various state to match the original state. This is used + * for image loading and other internal tasks. + * + * This object is struct-subclassable. Only em_format_html_job_new() + * may be used to allocate these. + **/ struct _EMFormatHTMLJob { - struct _EMFormatHTMLJob *next, *prev; + struct _EMFormatHTMLJob *next; + struct _EMFormatHTMLJob *prev; EMFormatHTML *format; struct _CamelStream *stream; @@ -75,8 +99,27 @@ typedef struct _EMFormatHTMLPObject EMFormatHTMLPObject; typedef gboolean (*EMFormatHTMLPObjectFunc)(EMFormatHTML *md, struct _GtkHTMLEmbedded *eb, EMFormatHTMLPObject *pobject); +/** + * struct _EMFormatHTMLPObject - Pending object. + * + * @next: Double linked list header. + * @prev: Double linked list header. + * @free: Invoked when the object is no longer needed. + * @format: The parent formatter. + * @classid: The assigned class id as passed to add_pobject(). + * @func: Callback function. + * @part: The part as passed to add_pobject(). + * + * This structure is used to track OBJECT tags which have been + * inserted into the HTML stream. When GtkHTML requests them the + * @func will be invoked to create the embedded widget. + * + * This object is struct-subclassable. Only + * em_format_html_add_pobject() may be used to allocate these. + **/ struct _EMFormatHTMLPObject { - struct _EMFormatHTMLPObject *next, *prev; + struct _EMFormatHTMLPObject *next; + struct _EMFormatHTMLPObject *prev; void (*free)(struct _EMFormatHTMLPObject *); struct _EMFormatHTML *format; @@ -93,6 +136,33 @@ struct _EMFormatHTMLPObject { #define EM_FORMAT_HTML_VPAD "<table cellspacing=0 cellpadding=3><tr><td><a name=\"padding\"></a></td></tr></table>\n" +/** + * struct _EMFormatHTML - HTML formatter object. + * + * @format: + * @priv: + * @html: + * @pending_object_list: + * @headers: + * @text_html_flags: + * @body_colour: + * @text_colour: + * @frame_colour: + * @content_colour: + * @citation_colour: + * @load_http:2: + * @load_http_now:1: + * @mark_citations:1: + * @simple_headers:1: + * @hide_headers:1: + * @show_rupert:1: + * + * Most of these fields are private or read-only. + * + * The base HTML formatter object. This object drives HTML generation + * into a GtkHTML parser. It also handles text to HTML conversion, + * multipart/related objects and inline images. + **/ struct _EMFormatHTML { EMFormat format; diff --git a/mail/em-format-quote.c b/mail/em-format-quote.c index 59f75181f9..88dcfdc7c8 100644 --- a/mail/em-format-quote.c +++ b/mail/em-format-quote.c @@ -35,6 +35,7 @@ #include <camel/camel-url.h> #include <gal/util/e-iconv.h> +#include <libgnome/gnome-i18n.h> #include "em-stripsig-filter.h" #include "em-format-quote.h" @@ -493,10 +494,11 @@ emfq_text_html(EMFormat *emf, CamelStream *stream, CamelMimePart *part, EMFormat em_format_format_text(emf, stream, camel_medium_get_content_object((CamelMedium *)part)); } -static const char *type_remove_table[] = { - "message/external-body", - "multipart/appledouble", -}; +static void +emfq_ignore(EMFormat *emf, CamelStream *stream, CamelMimePart *part, EMFormatHandler *info) +{ + /* NOOP */ +} static EMFormatHandler type_builtin_table[] = { { "text/plain",(EMFormatFunc)emfq_text_plain }, @@ -504,6 +506,8 @@ static EMFormatHandler type_builtin_table[] = { { "text/richtext",(EMFormatFunc)emfq_text_enriched }, { "text/html",(EMFormatFunc)emfq_text_html }, /* { "multipart/related",(EMFormatFunc)emfq_multipart_related },*/ + { "message/external-body", (EMFormatFunc)emfq_ignore }, + { "multipart/appledouble", (EMFormatFunc)emfq_ignore }, }; static void @@ -511,9 +515,6 @@ emfq_builtin_init(EMFormatQuoteClass *efhc) { int i; - for (i = 0; i < sizeof(type_remove_table) / sizeof(type_remove_table[0]); i++) - em_format_class_remove_handler((EMFormatClass *) efhc, type_remove_table[i]); - for (i=0;i<sizeof(type_builtin_table)/sizeof(type_builtin_table[0]);i++) em_format_class_add_handler((EMFormatClass *)efhc, &type_builtin_table[i]); } diff --git a/mail/em-format.c b/mail/em-format.c index c34d66c5a2..39b039e2b6 100644 --- a/mail/em-format.c +++ b/mail/em-format.c @@ -31,6 +31,7 @@ #include <libgnomevfs/gnome-vfs-mime.h> #include <libgnomevfs/gnome-vfs-mime-utils.h> #include <libgnomevfs/gnome-vfs-mime-handlers.h> +#include <libgnome/gnome-i18n.h> #include <e-util/e-msgport.h> #include <camel/camel-url.h> @@ -201,8 +202,10 @@ em_format_get_type(void) * @emfc: EMFormatClass * @info: Callback information. * - * Add a mime type handler to this class. This is only used by implementing - * classes. + * Add a mime type handler to this class. This is only used by + * implementing classes. The @info.old pointer will automatically be + * setup to point to the old hanlder if one was already set. This can + * be used for overrides a fallback. * * When a mime type described by @info is encountered, the callback will * be invoked. Note that @info may be extended by sub-classes if @@ -213,26 +216,36 @@ em_format_get_type(void) void em_format_class_add_handler(EMFormatClass *emfc, EMFormatHandler *info) { + printf("adding format handler to '%s' '%s'\n", g_type_name_from_class((GTypeClass *)emfc), info->mime_type); + info->old = g_hash_table_lookup(emfc->type_handlers, info->mime_type); g_hash_table_insert(emfc->type_handlers, info->mime_type, info); - /* FIXME: do we care? This is really gui stuff */ - /* - if (info->applications == NULL) - info->applications = gnome_vfs_mime_get_short_list_applications(info->mime_type);*/ } - /** * em_format_class_remove_handler: - * @emfc: EMFormatClass - * @mime_type: mime-type of handler to remove - * - * Remove a mime type handler from this class. This is only used by - * implementing classes. + * @emfc: + * @info: + * + * Remove a handler. @info must be a value which was previously + * added. **/ void -em_format_class_remove_handler (EMFormatClass *emfc, const char *mime_type) +em_format_class_remove_handler(EMFormatClass *emfc, EMFormatHandler *info) { - g_hash_table_remove (emfc->type_handlers, mime_type); + EMFormatHandler *current; + + /* TODO: thread issues? */ + + current = g_hash_table_lookup(emfc->type_handlers, info->mime_type); + if (current == info) { + current = info->old; + g_hash_table_insert(emfc->type_handlers, current->mime_type, current); + } else { + while (current && current->old != info) + current = current->old; + g_return_if_fail(current != NULL); + current->old = info->old; + } } /** @@ -312,6 +325,8 @@ em_format_add_puri(EMFormat *emf, size_t size, const char *cid, CamelMimePart *p EMFormatPURI *puri; const char *tmp; + printf("adding puri for part: %s\n", emf->part_id->str); + g_assert(size >= sizeof(*puri)); puri = g_malloc0(size); @@ -425,13 +440,13 @@ em_format_find_visible_puri(EMFormat *emf, const char *uri) EMFormatPURI *pw; struct _EMFormatPURITree *ptree; - d(printf("checking for visible uri '%s'\n", uri)); + (printf("checking for visible uri '%s'\n", uri)); ptree = emf->pending_uri_level; while (ptree) { pw = (EMFormatPURI *)ptree->uri_list.head; while (pw->next) { - d(printf(" pw->uri = '%s' pw->cid = '%s\n", pw->uri?pw->uri:"", pw->cid)); + (printf(" pw->uri = '%s' pw->cid = '%s\n", pw->uri?pw->uri:"", pw->cid)); if ((pw->uri && !strcmp(pw->uri, uri)) || !strcmp(pw->cid, uri)) return pw; pw = pw->next; @@ -468,6 +483,8 @@ emf_clear_puri_node(struct _EMFormatPURITree *node) pw = (EMFormatPURI *)node->uri_list.head; pn = pw->next; while (pn) { + if (pw->free) + pw->free(pw); g_free(pw->uri); g_free(pw->cid); g_free(pw->part_id); diff --git a/mail/em-format.h b/mail/em-format.h index 8cbdc69a1a..ac12853fd2 100644 --- a/mail/em-format.h +++ b/mail/em-format.h @@ -53,25 +53,70 @@ typedef enum _em_format_mode_t { EM_FORMAT_SOURCE, } em_format_mode_t; -/* can be subclassed/extended ... */ +/** + * struct _EMFormatHandler - MIME type handler. + * + * @mime_type: Type this handler handles. + * @handler: The handler callback. + * @flags: Handling flags, see enum _em_format_handler_t. + * @old: The last handler set on this type. Allows overrides to + * fallback to previous implementation. + * + **/ struct _EMFormatHandler { char *mime_type; EMFormatFunc handler; guint32 flags; - GList *applications; /* gnome vfs short-list of applications, do we care? */ + + struct _EMFormatHandler *old; +}; + +/** + * enum _em_format_handler_t - Format handler flags. + * + * @EM_FORMAT_HANDLER_INLINE: This type should be shown expanded + * inline by default. + * @EM_FORMAT_HANDLER_INLINE_DISPOSITION: This type should always be + * shown inline, despite what the Content-Disposition suggests. + * + **/ +enum _em_format_handler_t { + EM_FORMAT_HANDLER_INLINE = 1<<0, + EM_FORMAT_HANDLER_INLINE_DISPOSITION = 1<<1, }; -/* inline by default */ -#define EM_FORMAT_HANDLER_INLINE (1<<0) -/* inline by default, and override content-disposition always */ -#define EM_FORMAT_HANDLER_INLINE_DISPOSITION (1<<1) typedef struct _EMFormatPURI EMFormatPURI; typedef void (*EMFormatPURIFunc)(EMFormat *md, struct _CamelStream *stream, EMFormatPURI *puri); +/** + * struct _EMFormatPURI - Pending URI object. + * + * @next: Double-linked list header. + * @prev: Double-linked list header. + * @free: May be set by allocator and will be called when no longer needed. + * @format: + * @uri: Calculated URI of the part, if the part has one in its + * Content-Location field. + * @cid: The RFC2046 Content-Id of the part. If none is present, a unique value + * is calculated from @part_id. + * @part_id: A unique identifier for each part. + * @func: Callback for when the URI is requested. The callback writes + * its data to the supplied stream. + * @part: + * @use_count: + * + * This is used for multipart/related, and other formatters which may + * need to include a reference to out-of-band data in the content + * stream. + * + * This object may be subclassed as a struct. + **/ struct _EMFormatPURI { - struct _EMFormatPURI *next, *prev; + struct _EMFormatPURI *next; + struct _EMFormatPURI *prev; + void (*free)(struct _EMFormatPURI *p); /* optional callback for freeing user-fields */ struct _EMFormat *format; char *uri; /* will be the location of the part, may be empty */ @@ -84,9 +129,23 @@ struct _EMFormatPURI { unsigned int use_count; /* used by multipart/related to see if it was accessed */ }; -/* used to stack pending uri's for visibility (multipart/related) */ +/** + * struct _EMFormatPURITree - Pending URI visibility tree. + * + * @next: Double-linked list header. + * @prev: Double-linked list header. + * @parent: Parent in tree. + * @uri_list: List of EMFormatPURI objects at this level. + * @children: Child nodes of EMFormatPURITree. + * + * This structure is used internally to form a visibility tree of + * parts in the current formatting stream. This is to implement the + * part resolution rules for RFC2387 to implement multipart/related. + **/ struct _EMFormatPURITree { - struct _EMFormatPURITree *next, *prev, *parent; + struct _EMFormatPURITree *next; + struct _EMFormatPURITree *prev; + struct _EMFormatPURITree *parent; EDList uri_list; EDList children; @@ -102,6 +161,34 @@ struct _EMFormatHeader { #define EM_FORMAT_HEADER_BOLD (1<<0) #define EM_FORMAT_HEADER_LAST (1<<4) /* reserve 4 slots */ +/** + * struct _EMFormat - Mail formatter object. + * + * @parent: + * @priv: + * @message: + * @folder: + * @uid: + * @part_id: + * @header_list: + * @session: + * @base url: + * @snoop_mime_type: + * @valid: + * @valid_parent: + * @inline_table: + * @pending_uri_table: + * @pending_uri_tree: + * @pending_uri_level: + * @mode: + * @charset: + * @default_charset: + * + * Most fields are private or read-only. + * + * This is the base MIME formatter class. It provides no formatting + * itself, but drives most of the basic types, including multipart / * types. + **/ struct _EMFormat { GObject parent; @@ -200,7 +287,7 @@ char *em_format_describe_part(struct _CamelMimePart *part, const char *mimetype) GType em_format_get_type(void); void em_format_class_add_handler(EMFormatClass *emfc, EMFormatHandler *info); -void em_format_class_remove_handler (EMFormatClass *emfc, const char *mime_type); +void em_format_class_remove_handler(EMFormatClass *emfc, EMFormatHandler *info); #define em_format_find_handler(emf, type) ((EMFormatClass *)G_OBJECT_GET_CLASS(emf))->find_handler((emf), (type)) const EMFormatHandler *em_format_fallback_handler(EMFormat *emf, const char *mime_type); diff --git a/mail/em-junk-filter.c b/mail/em-junk-filter.c index 3c9627af66..efc1cfdc3a 100644 --- a/mail/em-junk-filter.c +++ b/mail/em-junk-filter.c @@ -37,6 +37,7 @@ #include <camel/camel-file-utils.h> #include <camel/camel-data-wrapper.h> #include <camel/camel-stream-fs.h> +#include <camel/camel-i18n.h> #include "mail-session.h" #include "em-junk-filter.h" diff --git a/mail/em-mailer-prefs.c b/mail/em-mailer-prefs.c index 0232ff6707..c619aeba97 100644 --- a/mail/em-mailer-prefs.c +++ b/mail/em-mailer-prefs.c @@ -55,6 +55,7 @@ #include <gtk/gtkmenuitem.h> #include "mail-config.h" +#include "em-config.h" static void em_mailer_prefs_class_init (EMMailerPrefsClass *class); static void em_mailer_prefs_init (EMMailerPrefs *dialog); @@ -660,6 +661,42 @@ notify_sound_changed (GtkWidget *widget, EMMailerPrefs *prefs) gconf_client_set_string (prefs->gconf, "/apps/evolution/mail/notify/sound", filename, NULL); } +static GtkWidget * +emmp_widget_glade(EConfig *ec, EConfigItem *item, struct _GtkWidget *parent, struct _GtkWidget *old, void *data) +{ + EMMailerPrefs *prefs = data; + + return glade_xml_get_widget(prefs->gui, item->label); +} + +/* plugin meta-data */ +static EMConfigItem emmp_items[] = { + { E_CONFIG_BOOK, "", "preferences_toplevel", emmp_widget_glade }, + { E_CONFIG_PAGE, "00.general", "vboxGeneral", emmp_widget_glade }, + { E_CONFIG_SECTION, "00.general/00.fonts", "vboxMessageFonts", emmp_widget_glade }, + { E_CONFIG_SECTION, "00.general/10.display", "vboxMessageDisplay", emmp_widget_glade }, + { E_CONFIG_SECTION, "00.general/20.delete", "vboxDeletingMail", emmp_widget_glade }, + { E_CONFIG_SECTION, "00.general/30.newmail", "vboxNewMailNotify", emmp_widget_glade }, + { E_CONFIG_PAGE, "10.html", "vboxHtmlMail", emmp_widget_glade }, + { E_CONFIG_SECTION, "10.html/00.general", "vbox173", emmp_widget_glade }, + { E_CONFIG_SECTION, "10.html/10.images", "vbox190", emmp_widget_glade }, + { E_CONFIG_PAGE, "20.labels", "frameColours", emmp_widget_glade }, + /* this is a table, so we can't use it { E_CONFIG_SECTION, "20.labels/00.labels", "tableColours", emmp_widget_glade }, */ + { E_CONFIG_PAGE, "30.headers", "vboxHeaderTab", emmp_widget_glade }, + /* no subvbox for section { E_CONFIG_PAGE, "30.headers/00.headers", "vbox199", emmp_widget_glade }, */ + { E_CONFIG_PAGE, "40.junk", "vbox161", emmp_widget_glade }, + /* no subvbox for section { E_CONFIG_SECTION, "40.junk/00.general", xxx, emmp_widget_glade } */ + { E_CONFIG_SECTION, "40.junk/10.options", "vbox204", emmp_widget_glade }, +}; + +static void +emmp_free(EConfig *ec, GSList *items, void *data) +{ + /* the prefs data is freed automagically */ + + g_slist_free(items); +} + static void em_mailer_prefs_construct (EMMailerPrefs *prefs) { @@ -673,19 +710,19 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs) GladeXML *gui; gboolean locked; int val, i; - - gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "preferences_tab", NULL); + EMConfig *ec; + EMConfigTargetPrefs *target; + GSList *l; + + gui = glade_xml_new (EVOLUTION_GLADEDIR "/mail-config.glade", "preferences_toplevel", NULL); prefs->gui = gui; - - /* get our toplevel widget */ - toplevel = glade_xml_get_widget (gui, "toplevel"); - - /* reparent */ - gtk_widget_ref (toplevel); - gtk_container_remove (GTK_CONTAINER (toplevel->parent), toplevel); - gtk_container_add (GTK_CONTAINER (prefs), toplevel); - gtk_widget_unref (toplevel); - + + ec = em_config_new(E_CONFIG_BOOK, "com.novell.evolution.mail.prefs"); + l = NULL; + for (i=0;i<sizeof(emmp_items)/sizeof(emmp_items[0]);i++) + l = g_slist_prepend(l, &emmp_items[i]); + e_config_add_items((EConfig *)ec, l, NULL, NULL, emmp_free, prefs); + /* General tab */ /* Message Display */ @@ -964,6 +1001,12 @@ em_mailer_prefs_construct (EMMailerPrefs *prefs) toggle_button_init (prefs, prefs->sa_local_tests_only, TRUE, "/apps/evolution/mail/junk/sa/local_only", G_CALLBACK (toggle_button_toggled_not)); + + /* get our toplevel widget */ + target = em_config_target_new_prefs(ec, prefs->gconf); + e_config_set_target((EConfig *)ec, (EConfigTarget *)target); + toplevel = e_config_create_widget((EConfig *)ec); + gtk_container_add (GTK_CONTAINER (prefs), toplevel); } GtkWidget * diff --git a/mail/em-menu.c b/mail/em-menu.c new file mode 100644 index 0000000000..79a5564f51 --- /dev/null +++ b/mail/em-menu.c @@ -0,0 +1,338 @@ +/* -*- 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 <libgnome/gnome-url.h> +#include <libgnomevfs/gnome-vfs-mime.h> + +#include "em-menu.h" +#include "e-util/e-msgport.h" +#include <e-util/e-icon-factory.h> +#include "em-utils.h" +#include "em-composer-utils.h" + +#include <camel/camel-store.h> +#include <camel/camel-folder.h> +#include <camel/camel-mime-message.h> +#include <camel/camel-string-utils.h> +#include <camel/camel-mime-utils.h> +#include <camel/camel-mime-part.h> +#include <camel/camel-url.h> + +#include <camel/camel-vee-folder.h> +#include <camel/camel-vtrash-folder.h> + +#include <gconf/gconf.h> +#include <gconf/gconf-client.h> + +#include <gal/util/e-util.h> + +static void emp_standard_menu_factory(EMenu *emp, void *data); + +static GObjectClass *emp_parent; + +static void +emp_init(GObject *o) +{ + /*EMMenu *emp = (EMMenu *)o; */ +} + +static void +emp_finalise(GObject *o) +{ + ((GObjectClass *)emp_parent)->finalize(o); +} + +static void +emp_target_free(EMenu *ep, EMenuTarget *t) +{ + switch (t->type) { + case EM_MENU_TARGET_SELECT: { + EMMenuTargetSelect *s = (EMMenuTargetSelect *)t; + + if (s->folder) + camel_object_unref(s->folder); + g_free(s->uri); + if (s->uids) + em_utils_uids_free(s->uids); + break; } + } + + ((EMenuClass *)emp_parent)->target_free(ep, t); +} + +static void +emp_class_init(GObjectClass *klass) +{ + printf("em menu class init\n"); + + klass->finalize = emp_finalise; + ((EMenuClass *)klass)->target_free = emp_target_free; + + e_menu_class_add_factory((EMenuClass *)klass, NULL, (EMenuFactoryFunc)emp_standard_menu_factory, NULL); +} + +GType +em_menu_get_type(void) +{ + static GType type = 0; + + if (type == 0) { + static const GTypeInfo info = { + sizeof(EMMenuClass), + NULL, NULL, + (GClassInitFunc)emp_class_init, + NULL, NULL, + sizeof(EMMenu), 0, + (GInstanceInitFunc)emp_init + }; + emp_parent = g_type_class_ref(e_menu_get_type()); + type = g_type_register_static(e_menu_get_type(), "EMMenu", &info, 0); + } + + return type; +} + +EMMenu *em_menu_new(const char *menuid) +{ + EMMenu *emp = g_object_new(em_menu_get_type(), 0); + + e_menu_construct(&emp->popup, menuid); + + return emp; +} + +/** + * em_menu_target_new_select: + * @folder: The selection will ref this for the life of it. + * @folder_uri: + * @uids: The selection will free this when done with it. + * + * Create a new selection popup target. + * + * Return value: + **/ +EMMenuTargetSelect * +em_menu_target_new_select(EMMenu *emp, struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids) +{ + EMMenuTargetSelect *t = e_menu_target_new(&emp->popup, EM_MENU_TARGET_SELECT, sizeof(*t)); + guint32 mask = ~0; + int i; + const char *tmp; + + /* NB: This is identical to em-popup-target-new-select function */ + + t->uids = uids; + t->folder = folder; + t->uri = g_strdup(folder_uri); + + if (folder == NULL) { + t->target.mask = mask; + + return t; + } + + camel_object_ref(folder); + mask &= ~EM_MENU_SELECT_FOLDER; + + if (em_utils_folder_is_sent(folder, folder_uri)) + mask &= ~EM_MENU_SELECT_EDIT; + + if (!(em_utils_folder_is_drafts(folder, folder_uri) + || em_utils_folder_is_outbox(folder, folder_uri)) + && uids->len == 1) + mask &= ~EM_MENU_SELECT_ADD_SENDER; + + if (uids->len == 1) + mask &= ~EM_MENU_SELECT_ONE; + + if (uids->len >= 1) + mask &= ~EM_MENU_SELECT_MANY; + + for (i = 0; i < uids->len; i++) { + CamelMessageInfo *info = camel_folder_get_message_info(folder, uids->pdata[i]); + + if (info == NULL) + continue; + + if (info->flags & CAMEL_MESSAGE_SEEN) + mask &= ~EM_MENU_SELECT_MARK_UNREAD; + else + mask &= ~EM_MENU_SELECT_MARK_READ; + + if (info->flags & CAMEL_MESSAGE_DELETED) + mask &= ~EM_MENU_SELECT_UNDELETE; + else + mask &= ~EM_MENU_SELECT_DELETE; + + if (info->flags & CAMEL_MESSAGE_FLAGGED) + mask &= ~EM_MENU_SELECT_MARK_UNIMPORTANT; + else + mask &= ~EM_MENU_SELECT_MARK_IMPORTANT; + + if (info->flags & CAMEL_MESSAGE_JUNK) + mask &= ~EM_MENU_SELECT_MARK_NOJUNK; + else + mask &= ~EM_MENU_SELECT_MARK_JUNK; + + tmp = camel_tag_get (&info->user_tags, "follow-up"); + if (tmp && *tmp) { + mask &= ~EM_MENU_SELECT_FLAG_CLEAR; + tmp = camel_tag_get(&info->user_tags, "completed-on"); + if (tmp == NULL || *tmp == 0) + mask &= ~EM_MENU_SELECT_FLAG_COMPLETED; + } else + mask &= ~EM_MENU_SELECT_FLAG_FOLLOWUP; + + if (i == 0 && uids->len == 1 + && (tmp = camel_message_info_mlist(info)) + && tmp[0] != 0) + mask &= ~EM_MENU_SELECT_MAILING_LIST; + + camel_folder_free_message_info(folder, info); + } + + t->target.mask = mask; + + return t; +} + +static void +emp_standard_menu_factory(EMenu *emp, void *data) +{ + /* noop */ +} + +/* ********************************************************************** */ + +/* 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" + mask="select_one" + activate="emp_view_emacs"/> + </menu> + </extension> + +*/ + +static void *emph_parent_class; +#define emph ((EMMenuHook *)eph) + +static const EMenuHookTargetMask emph_select_masks[] = { + { "one", EM_MENU_SELECT_ONE }, + { "many", EM_MENU_SELECT_MANY }, + { "mark_read", EM_MENU_SELECT_MARK_READ }, + { "mark_unread", EM_MENU_SELECT_MARK_UNREAD }, + { "delete", EM_MENU_SELECT_DELETE }, + { "undelete", EM_MENU_SELECT_UNDELETE }, + { "mailing_list", EM_MENU_SELECT_MAILING_LIST }, + { "resend", EM_MENU_SELECT_EDIT }, + { "mark_important", EM_MENU_SELECT_MARK_IMPORTANT }, + { "mark_unimportant", EM_MENU_SELECT_MARK_UNIMPORTANT }, + { "flag_followup", EM_MENU_SELECT_FLAG_FOLLOWUP }, + { "flag_completed", EM_MENU_SELECT_FLAG_COMPLETED }, + { "flag_clear", EM_MENU_SELECT_FLAG_CLEAR }, + { "add_sender", EM_MENU_SELECT_ADD_SENDER }, + { "mark_junk", EM_MENU_SELECT_MARK_JUNK }, + { "mark_nojunk", EM_MENU_SELECT_MARK_NOJUNK }, + { "folder", EM_MENU_SELECT_FOLDER }, + { 0 } +}; + +static const EMenuHookTargetMap emph_targets[] = { + { "select", EM_MENU_TARGET_SELECT, emph_select_masks }, + { 0 } +}; + +static void +emph_finalise(GObject *o) +{ + /*EPluginHook *eph = (EPluginHook *)o;*/ + + ((GObjectClass *)emph_parent_class)->finalize(o); +} + +static void +emph_class_init(EPluginHookClass *klass) +{ + int i; + + ((GObjectClass *)klass)->finalize = emph_finalise; + ((EPluginHookClass *)klass)->id = "com.ximian.evolution.mail.bonobomenu:1.0"; + + for (i=0;emph_targets[i].type;i++) + e_menu_hook_class_add_target_map((EMenuHookClass *)klass, &emph_targets[i]); + + /* FIXME: leaks parent set class? */ + ((EMenuHookClass *)klass)->menu_class = g_type_class_ref(em_menu_get_type()); +} + +GType +em_menu_hook_get_type(void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof(EMMenuHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL, + sizeof(EMMenuHook), 0, (GInstanceInitFunc) NULL, + }; + + emph_parent_class = g_type_class_ref(e_menu_hook_get_type()); + type = g_type_register_static(e_menu_hook_get_type(), "EMMenuHook", &info, 0); + } + + return type; +} diff --git a/mail/em-menu.h b/mail/em-menu.h new file mode 100644 index 0000000000..5f9183b860 --- /dev/null +++ b/mail/em-menu.h @@ -0,0 +1,113 @@ +/* -*- 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 __EM_MENU_H__ +#define __EM_MENU_H__ + +#include <glib-object.h> + +#include "e-util/e-menu.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +typedef struct _EMMenu EMMenu; +typedef struct _EMMenuClass EMMenuClass; + +/* Current target description */ +/* Types of popup tagets */ +enum _em_menu_target_t { + EM_MENU_TARGET_SELECT, +}; + +/* Flags that describe a TARGET_SELECT */ +enum { + EM_MENU_SELECT_ONE = 1<<1, + EM_MENU_SELECT_MANY = 1<<2, + EM_MENU_SELECT_MARK_READ = 1<<3, + EM_MENU_SELECT_MARK_UNREAD = 1<<4, + EM_MENU_SELECT_DELETE = 1<<5, + EM_MENU_SELECT_UNDELETE = 1<<6, + EM_MENU_SELECT_MAILING_LIST = 1<<7, + EM_MENU_SELECT_EDIT = 1<<8, + EM_MENU_SELECT_MARK_IMPORTANT = 1<<9, + EM_MENU_SELECT_MARK_UNIMPORTANT = 1<<10, + EM_MENU_SELECT_FLAG_FOLLOWUP = 1<<11, + EM_MENU_SELECT_FLAG_COMPLETED = 1<<12, + EM_MENU_SELECT_FLAG_CLEAR = 1<<13, + EM_MENU_SELECT_ADD_SENDER = 1<<14, + EM_MENU_SELECT_MARK_JUNK = 1<<15, + EM_MENU_SELECT_MARK_NOJUNK = 1<<16, + EM_MENU_SELECT_FOLDER = 1<<17, /* do we have any folder at all? */ + EM_MENU_SELECT_LAST = 1<<18 /* reserve 2 slots */ +}; + +typedef struct _EMMenuTargetSelect EMMenuTargetSelect; + +struct _EMMenuTargetSelect { + EMenuTarget target; + struct _CamelFolder *folder; + char *uri; + GPtrArray *uids; +}; + +typedef struct _EMenuItem EMMenuItem; + +/* The object */ +struct _EMMenu { + EMenu popup; + + struct _EMMenuPrivate *priv; +}; + +struct _EMMenuClass { + EMenuClass popup_class; +}; + +GType em_menu_get_type(void); + +EMMenu *em_menu_new(const char *menuid); + +EMMenuTargetSelect *em_menu_target_new_select(EMMenu *emp, struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids); + +/* ********************************************************************** */ + +typedef struct _EMMenuHook EMMenuHook; +typedef struct _EMMenuHookClass EMMenuHookClass; + +struct _EMMenuHook { + EMenuHook hook; +}; + +struct _EMMenuHookClass { + EMenuHookClass hook_class; +}; + +GType em_menu_hook_get_type(void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __EM_MENU_H__ */ diff --git a/mail/em-message-browser.c b/mail/em-message-browser.c index 4f3a2aaa2c..98fc1bd319 100644 --- a/mail/em-message-browser.c +++ b/mail/em-message-browser.c @@ -41,6 +41,7 @@ #include "em-format-html-display.h" #include "em-message-browser.h" +#include "em-menu.h" #include "evolution-shell-component-utils.h" /* Pixmap stuff, sigh */ @@ -175,6 +176,8 @@ GtkWidget *em_message_browser_new(void) { EMMessageBrowser *emmb = g_object_new(em_message_browser_get_type(), 0); + ((EMFolderView *)emmb)->menu = em_menu_new("com.novell.evolution.mail.messagebrowser"); + return (GtkWidget *)emmb; } diff --git a/mail/em-popup.c b/mail/em-popup.c index 28e5121c51..8b580e3147 100644 --- a/mail/em-popup.c +++ b/mail/em-popup.c @@ -40,6 +40,7 @@ #include <libgnome/gnome-url.h> #include <libgnomevfs/gnome-vfs-mime.h> +#include <libgnome/gnome-i18n.h> #include "em-popup.h" #include "e-util/e-msgport.h" @@ -63,71 +64,69 @@ #include <gal/util/e-util.h> -static void emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data); - -struct _EMPopupFactory { - struct _EMPopupFactory *next, *prev; - - char *menuid; - EMPopupFactoryFunc factory; - void *factory_data; -}; - -struct _menu_node { - struct _menu_node *next, *prev; - - GSList *menu; - GDestroyNotify freefunc; -}; - -struct _EMPopupPrivate { - EDList menus; -}; - -static EDList emp_factories = E_DLIST_INITIALISER(emp_factories); +static void emp_standard_menu_factory(EPopup *emp, void *data); static GObjectClass *emp_parent; static void emp_init(GObject *o) { - EMPopup *emp = (EMPopup *)o; - struct _EMPopupPrivate *p; - - p = emp->priv = g_malloc0(sizeof(struct _EMPopupPrivate)); - - e_dlist_init(&p->menus); + /*EMPopup *emp = (EMPopup *)o; */ } static void emp_finalise(GObject *o) { - EMPopup *emp = (EMPopup *)o; - struct _EMPopupPrivate *p = emp->priv; - struct _menu_node *mnode, *nnode; + ((GObjectClass *)emp_parent)->finalize(o); +} + +static void +emp_target_free(EPopup *ep, EPopupTarget *t) +{ + switch (t->type) { + case EM_POPUP_TARGET_SELECT: { + EMPopupTargetSelect *s = (EMPopupTargetSelect *)t; + + if (s->folder) + camel_object_unref(s->folder); + g_free(s->uri); + if (s->uids) + em_utils_uids_free(s->uids); + break; } + case EM_POPUP_TARGET_URI: { + EMPopupTargetURI *s = (EMPopupTargetURI *)t; - g_free(emp->menuid); + g_free(s->uri); + break; } + case EM_POPUP_TARGET_PART: { + EMPopupTargetPart *s = (EMPopupTargetPart *)t; - mnode = (struct _menu_node *)p->menus.head; - nnode = mnode->next; - while (nnode) { - if (mnode->freefunc) - mnode->freefunc(mnode->menu); + camel_object_unref(s->part); + g_free(s->mime_type); + break; } + case EM_POPUP_TARGET_FOLDER: { + EMPopupTargetFolder *s = (EMPopupTargetFolder *)t; - g_free(mnode); - mnode = nnode; - nnode = nnode->next; - } + g_free(s->uri); + break; } + case EM_POPUP_TARGET_ATTACHMENTS: { + EMPopupTargetAttachments *s = (EMPopupTargetAttachments *)t; - g_free(p); + g_slist_foreach(s->attachments, (GFunc)g_object_unref, NULL); + g_slist_free(s->attachments); + break; } + } - ((GObjectClass *)emp_parent)->finalize(o); + ((EPopupClass *)emp_parent)->target_free(ep, t); } static void emp_class_init(GObjectClass *klass) { klass->finalize = emp_finalise; + ((EPopupClass *)klass)->target_free = emp_target_free; + + e_popup_class_add_factory((EPopupClass *)klass, NULL, emp_standard_menu_factory, NULL); } GType @@ -144,11 +143,8 @@ em_popup_get_type(void) sizeof(EMPopup), 0, (GInstanceInitFunc)emp_init }; - emp_parent = g_type_class_ref(G_TYPE_OBJECT); - type = g_type_register_static(G_TYPE_OBJECT, "EMPopup", &info, 0); - - /* FIXME: this should probably sit somewhere in global setup */ - em_popup_static_add_factory(NULL, (EMPopupFactoryFunc)emp_standard_menu_factory, NULL); + emp_parent = g_type_class_ref(e_popup_get_type()); + type = g_type_register_static(e_popup_get_type(), "EMPopup", &info, 0); } return type; @@ -158,291 +154,12 @@ EMPopup *em_popup_new(const char *menuid) { EMPopup *emp = g_object_new(em_popup_get_type(), 0); - emp->menuid = g_strdup(menuid); + e_popup_construct(&emp->popup, menuid); return emp; } /** - * em_popup_add_items: - * @emp: - * @items: - * @freefunc: - * - * Add new EMPopupItems to the menu's. Any with the same path - * will override previously defined menu items, at menu building - * time. - **/ -void -em_popup_add_items(EMPopup *emp, GSList *items, GDestroyNotify freefunc) -{ - struct _menu_node *node; - - node = g_malloc(sizeof(*node)); - node->menu = items; - node->freefunc = freefunc; - e_dlist_addtail(&emp->priv->menus, (EDListNode *)node); -} - -/** - * em_popup_add_static_items: - * @emp: - * @target: Target of this menu. - * - * Will load up any matching menu items from an installed - * popup factory. If the menuid of @emp is NULL, then this - * has no effect. - * - **/ -void -em_popup_add_static_items(EMPopup *emp, EMPopupTarget *target) -{ - struct _EMPopupFactory *f; - - if (emp->menuid == NULL || target == NULL) - return; - - /* setup the menu itself */ - f = (struct _EMPopupFactory *)emp_factories.head; - while (f->next) { - if (f->menuid == NULL - || !strcmp(f->menuid, emp->menuid)) { - f->factory(emp, target, f->factory_data); - } - f = f->next; - } -} - -static int -emp_cmp(const void *ap, const void *bp) -{ - struct _EMPopupItem *a = *((void **)ap); - struct _EMPopupItem *b = *((void **)bp); - - return strcmp(a->path, b->path); -} - -/** - * em_popup_create: - * @menuitems: - * @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. - * - * TEMPORARY code to create a menu from a list of items. - * - * The menu items are merged based on their path element, and - * built into a menu tree. - * - * Return value: - **/ -GtkMenu * -em_popup_create_menu(EMPopup *emp, guint32 hide_mask, guint32 disable_mask) -{ - struct _EMPopupPrivate *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); - /*char *domain = NULL;*/ - int i; - - /* 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) - g_ptr_array_add(items, l->data); - mnode = nnode; - nnode = nnode->next; - } - - qsort(items->pdata, items->len, sizeof(items->pdata[0]), emp_cmp); - - topmenu = (GtkMenu *)gtk_menu_new(); - for (i=0;i<items->len;i++) { - GtkWidget *label; - struct _EMPopupItem *item = items->pdata[i]; - GtkMenu *thismenu; - GtkMenuItem *menuitem; - char *tmp; - - /* for bar's, the mask is exclusive or */ - if (item->mask) { - if ((item->type & EM_POPUP_TYPE_MASK) == EM_POPUP_BAR) { - if ((item->mask & hide_mask) == item->mask) - continue; - } else if (item->mask & 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 & EM_POPUP_TYPE_MASK) { - case EM_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 EM_POPUP_TOGGLE: - menuitem = (GtkMenuItem *)gtk_check_menu_item_new(); - gtk_check_menu_item_set_active((GtkCheckMenuItem *)menuitem, item->type & EM_POPUP_ACTIVE); - break; - case EM_POPUP_RADIO: - menuitem = (GtkMenuItem *)gtk_radio_menu_item_new(g_hash_table_lookup(group_hash, ppath->str)); - 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 & EM_POPUP_ACTIVE); - break; - case EM_POPUP_IMAGE: - menuitem = (GtkMenuItem *)gtk_image_menu_item_new(); - gtk_image_menu_item_set_image((GtkImageMenuItem *)menuitem, item->image); - break; - case EM_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 EM_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", item->activate, item->activate_data); - - gtk_menu_shell_append((GtkMenuShell *)thismenu, (GtkWidget *)menuitem); - - if (item->mask & 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 -emp_popup_done(GtkWidget *w, EMPopup *emp) -{ - gtk_widget_destroy(w); - g_object_unref(emp); -} - -/** - * em_popup_create_menu_once: - * @emp: EMPopup, 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 EMPopup is unreffed. - * - * Return value: A menu, to popup. - **/ -GtkMenu * -em_popup_create_menu_once(EMPopup *emp, EMPopupTarget *target, guint32 hide_mask, guint32 disable_mask) -{ - GtkMenu *menu; - - if (target) - em_popup_add_static_items(emp, target); - - menu = em_popup_create_menu(emp, hide_mask, disable_mask); - - if (target) - g_signal_connect_swapped(menu, "selection_done", G_CALLBACK(em_popup_target_free), target); - g_signal_connect(menu, "selection_done", G_CALLBACK(emp_popup_done), emp); - - return menu; -} - -/* ********************************************************************** */ - -/** - * em_popup_static_add_factory: - * @menuid: - * @func: - * @data: - * - * Add a popup factory which will be called to add_items() any - * extra menu's if wants to do the current PopupTarget. - * - * TODO: Make the menuid a pattern? - * - * Return value: A handle to the factory. - **/ -EMPopupFactory * -em_popup_static_add_factory(const char *menuid, EMPopupFactoryFunc func, void *data) -{ - struct _EMPopupFactory *f = g_malloc0(sizeof(*f)); - - f->menuid = g_strdup(menuid); - f->factory = func; - f->factory_data = data; - e_dlist_addtail(&emp_factories, (EDListNode *)f); - - return f; -} - -/** - * em_popup_static_remove_factory: - * @f: - * - * Remove a popup factory. - **/ -void -em_popup_static_remove_factory(EMPopupFactory *f) -{ - e_dlist_remove((EDListNode *)f); - g_free(f->menuid); - g_free(f); -} - -/** * em_popup_target_new_select: * @folder: The selection will ref this for the life of it. * @folder_uri: @@ -452,21 +169,20 @@ em_popup_static_remove_factory(EMPopupFactory *f) * * Return value: **/ -EMPopupTarget * -em_popup_target_new_select(struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids) +EMPopupTargetSelect * +em_popup_target_new_select(EMPopup *emp, struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids) { - EMPopupTarget *t = g_malloc0(sizeof(*t)); + EMPopupTargetSelect *t = e_popup_target_new(&emp->popup, EM_POPUP_TARGET_SELECT, sizeof(*t)); guint32 mask = ~0; int i; const char *tmp; - t->type = EM_POPUP_TARGET_SELECT; - t->data.select.uids = uids; - t->data.select.folder = folder; - t->data.select.folder_uri = g_strdup(folder_uri); + t->uids = uids; + t->folder = folder; + t->uri = g_strdup(folder_uri); if (folder == NULL) { - t->mask = mask; + t->target.mask = mask; return t; } @@ -531,19 +247,18 @@ em_popup_target_new_select(struct _CamelFolder *folder, const char *folder_uri, camel_folder_free_message_info(folder, info); } - t->mask = mask; + t->target.mask = mask; return t; } -EMPopupTarget * -em_popup_target_new_uri(const char *uri) +EMPopupTargetURI * +em_popup_target_new_uri(EMPopup *emp, const char *uri) { - EMPopupTarget *t = g_malloc0(sizeof(*t)); + EMPopupTargetURI *t = e_popup_target_new(&emp->popup, EM_POPUP_TARGET_URI, sizeof(*t)); guint32 mask = ~0; - t->type = EM_POPUP_TARGET_URI; - t->data.uri = g_strdup(uri); + t->uri = g_strdup(uri); if (g_ascii_strncasecmp(uri, "http:", 5) == 0 || g_ascii_strncasecmp(uri, "https:", 6) == 0) @@ -553,48 +268,46 @@ em_popup_target_new_uri(const char *uri) else mask &= ~EM_POPUP_URI_NOT_MAILTO; - t->mask = mask; + t->target.mask = mask; return t; } -EMPopupTarget * -em_popup_target_new_part(struct _CamelMimePart *part, const char *mime_type) +EMPopupTargetPart * +em_popup_target_new_part(EMPopup *emp, struct _CamelMimePart *part, const char *mime_type) { - EMPopupTarget *t = g_malloc0(sizeof(*t)); + EMPopupTargetPart *t = e_popup_target_new(&emp->popup, EM_POPUP_TARGET_PART, sizeof(*t)); guint32 mask = ~0; - t->type = EM_POPUP_TARGET_PART; - t->data.part.part = part; + t->part = part; camel_object_ref(part); if (mime_type) - t->data.part.mime_type = g_strdup(mime_type); + t->mime_type = g_strdup(mime_type); else - t->data.part.mime_type = camel_data_wrapper_get_mime_type((CamelDataWrapper *)part); + t->mime_type = camel_data_wrapper_get_mime_type((CamelDataWrapper *)part); - camel_strdown(t->data.part.mime_type); + camel_strdown(t->mime_type); if (CAMEL_IS_MIME_MESSAGE(camel_medium_get_content_object((CamelMedium *)part))) mask &= ~EM_POPUP_PART_MESSAGE; - if (strncmp(t->data.part.mime_type, "image/", 6) == 0) + if (strncmp(t->mime_type, "image/", 6) == 0) mask &= ~EM_POPUP_PART_IMAGE; - t->mask = mask; + t->target.mask = mask; return t; } /* TODO: This should be based on the CamelFolderInfo, but ... em-folder-tree doesn't keep it? */ -EMPopupTarget * -em_popup_target_new_folder (const char *uri, guint32 info_flags, guint32 popup_flags) +EMPopupTargetFolder * +em_popup_target_new_folder (EMPopup *emp, const char *uri, guint32 info_flags, guint32 popup_flags) { - EMPopupTarget *t = g_malloc0(sizeof(*t)); + EMPopupTargetFolder *t = e_popup_target_new(&emp->popup, EM_POPUP_TARGET_FOLDER, sizeof(*t)); guint32 mask = ~0; CamelURL *url; - t->type = EM_POPUP_TARGET_FOLDER; - t->data.folder.folder_uri = g_strdup(uri); + t->uri = g_strdup(uri); if (popup_flags & EM_POPUP_FOLDER_STORE) mask &= ~(EM_POPUP_FOLDER_STORE|EM_POPUP_FOLDER_INFERIORS); @@ -629,91 +342,63 @@ em_popup_target_new_folder (const char *uri, guint32 info_flags, guint32 popup_f camel_url_free(url); done: - t->mask = mask; + t->target.mask = mask; return t; } -void -em_popup_target_free(EMPopupTarget *t) -{ - switch (t->type) { - case EM_POPUP_TARGET_SELECT: - if (t->data.select.folder) - camel_object_unref(t->data.select.folder); - g_free(t->data.select.folder_uri); - if (t->data.select.uids) - em_utils_uids_free(t->data.select.uids); - break; - case EM_POPUP_TARGET_URI: - g_free(t->data.uri); - break; - case EM_POPUP_TARGET_PART: - camel_object_unref(t->data.part.part); - g_free(t->data.part.mime_type); - break; - case EM_POPUP_TARGET_FOLDER: - g_free(t->data.folder.folder_uri); - break; - } - - g_free(t); -} - -/* ********************************************************************** */ - -#if 0 -/* TODO: flesh these out where possible */ -static void -emp_popup_open(GtkWidget *w, EMFolderView *emfv) +/** + * em_popup_target_new_attachments: + * @emp: + * @attachments: A list of EMsgComposerAttachment objects, reffed for + * the list. Will be unreff'd once finished with. + * + * Owns the list @attachments and their items after they're passed in. + * + * Return value: + **/ +EMPopupTargetAttachments * +em_popup_target_new_attachments(EMPopup *emp, GSList *attachments) { - em_folder_view_open_selected(emfv); -} + EMPopupTargetAttachments *t = e_popup_target_new(&emp->popup, EM_POPUP_TARGET_ATTACHMENTS, sizeof(*t)); + guint32 mask = ~0; + int len = g_slist_length(attachments); -static void -emp_popup_edit (GtkWidget *w, EMPopupTarget *t) -{ - if (!em_utils_check_user_can_send_mail(t->widget)) - return; - - em_utils_edit_messages(t->widget, t->data.select.folder, em_utils_uids_copy(t->data.select.uids), FALSE); -} + t->attachments = attachments; + if (len > 0) + mask &= ~ EM_POPUP_ATTACHMENTS_MANY; + if (len == 1) + mask &= ~ EM_POPUP_ATTACHMENTS_ONE; + t->target.mask = mask; -static void -emp_popup_saveas(GtkWidget *w, EMPopupTarget *t) -{ - em_utils_save_messages(t->widget, t->data.select.folder, em_utils_uids_copy(t->data.select.uids)); + return t; } -static EMPopupItem emp_standard_select_popups[] = { - /*{ EM_POPUP_ITEM, "00.select.00", N_("_Open"), G_CALLBACK(emp_popup_open), NULL, NULL, 0 },*/ - { EM_POPUP_ITEM, "00.select.01", N_("_Edit as New Message..."), G_CALLBACK(emp_popup_edit), NULL, NULL, EM_POPUP_SELECT_EDIT }, - { EM_POPUP_ITEM, "00.select.02", N_("_Save As..."), G_CALLBACK(emp_popup_saveas), NULL, "stock_save-as", 0 }, -}; -#endif - /* ********************************************************************** */ static void -emp_part_popup_saveas(GtkWidget *w, EMPopupTarget *t) +emp_part_popup_saveas(EPopup *ep, EPopupItem *item, void *data) { - em_utils_save_part(w, _("Save As..."), t->data.part.part); + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; + + em_utils_save_part(ep->target->widget, _("Save As..."), t->part); } static void -emp_part_popup_set_background(GtkWidget *w, EMPopupTarget *t) +emp_part_popup_set_background(EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; GConfClient *gconf; char *str, *filename, *path, *extension; unsigned int i=1; - filename = g_strdup(camel_mime_part_get_filename(t->data.part.part)); + filename = g_strdup(camel_mime_part_get_filename(t->part)); /* if filename is blank, create a default filename based on MIME type */ if (!filename || !filename[0]) { CamelContentType *ct; - ct = camel_mime_part_get_content_type(t->data.part.part); + ct = camel_mime_part_get_content_type(t->part); g_free (filename); filename = g_strdup_printf (_("untitled_image.%s"), ct->subtype); } @@ -737,7 +422,7 @@ emp_part_popup_set_background(GtkWidget *w, EMPopupTarget *t) g_free(filename); - if (em_utils_save_part_to_file(w, path, t->data.part.part)) { + if (em_utils_save_part_to_file(ep->target->widget, path, t->part)) { gconf = gconf_client_get_default(); /* if the filename hasn't changed, blank the filename before @@ -767,64 +452,68 @@ emp_part_popup_set_background(GtkWidget *w, EMPopupTarget *t) } static void -emp_part_popup_reply_sender (GtkWidget *w, EMPopupTarget *t) +emp_part_popup_reply_sender(EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; CamelMimeMessage *message; - message = (CamelMimeMessage *) camel_medium_get_content_object ((CamelMedium *) t->data.part.part); - em_utils_reply_to_message (NULL, NULL, message, REPLY_MODE_SENDER, NULL); + message = (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->part); + em_utils_reply_to_message(NULL, NULL, message, REPLY_MODE_SENDER, NULL); } static void -emp_part_popup_reply_list (GtkWidget *w, EMPopupTarget *t) +emp_part_popup_reply_list (EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; CamelMimeMessage *message; - message = (CamelMimeMessage *) camel_medium_get_content_object ((CamelMedium *) t->data.part.part); - em_utils_reply_to_message (NULL, NULL, message, REPLY_MODE_LIST, NULL); + message = (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->part); + em_utils_reply_to_message(NULL, NULL, message, REPLY_MODE_LIST, NULL); } static void -emp_part_popup_reply_all (GtkWidget *w, EMPopupTarget *t) +emp_part_popup_reply_all (EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; CamelMimeMessage *message; - message = (CamelMimeMessage *) camel_medium_get_content_object ((CamelMedium *) t->data.part.part); - em_utils_reply_to_message (NULL, NULL, message, REPLY_MODE_ALL, NULL); + message = (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *)t->part); + em_utils_reply_to_message(NULL, NULL, message, REPLY_MODE_ALL, NULL); } static void -emp_part_popup_forward (GtkWidget *w, EMPopupTarget *t) +emp_part_popup_forward (EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetPart *t = (EMPopupTargetPart *)ep->target; CamelMimeMessage *message; /* TODO: have a emfv specific override so we can get the parent folder uri */ - message = (CamelMimeMessage *) camel_medium_get_content_object ((CamelMedium *) t->data.part.part); - em_utils_forward_message (message, NULL); + message = (CamelMimeMessage *)camel_medium_get_content_object((CamelMedium *) t->part); + em_utils_forward_message(message, NULL); } static EMPopupItem emp_standard_object_popups[] = { - { EM_POPUP_ITEM, "00.part.00", N_("_Save As..."), G_CALLBACK(emp_part_popup_saveas), NULL, "stock_save-as", 0 }, - { EM_POPUP_ITEM, "00.part.10", N_("Set as _Background"), G_CALLBACK(emp_part_popup_set_background), NULL, NULL, EM_POPUP_PART_IMAGE }, - { EM_POPUP_BAR, "10.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, - { EM_POPUP_ITEM, "10.part.00", N_("_Reply to sender"), G_CALLBACK(emp_part_popup_reply_sender), NULL, "stock_mail-reply" , EM_POPUP_PART_MESSAGE }, - { EM_POPUP_ITEM, "10.part.01", N_("Reply to _List"), G_CALLBACK(emp_part_popup_reply_list), NULL, NULL, EM_POPUP_PART_MESSAGE}, - { EM_POPUP_ITEM, "10.part.03", N_("Reply to _All"), G_CALLBACK(emp_part_popup_reply_all), NULL, "stock_mail-reply-to-all", EM_POPUP_PART_MESSAGE}, - { EM_POPUP_BAR, "20.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, - { EM_POPUP_ITEM, "20.part.00", N_("_Forward"), G_CALLBACK(emp_part_popup_forward), NULL, "stock_mail-forward", EM_POPUP_PART_MESSAGE }, - + { E_POPUP_ITEM, "00.part.00", N_("_Save As..."), emp_part_popup_saveas, NULL, "stock_save-as", 0 }, + { E_POPUP_ITEM, "00.part.10", N_("Set as _Background"), emp_part_popup_set_background, NULL, NULL, EM_POPUP_PART_IMAGE }, + { E_POPUP_BAR, "10.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, + { E_POPUP_ITEM, "10.part.00", N_("_Reply to sender"), emp_part_popup_reply_sender, NULL, "stock_mail-reply" , EM_POPUP_PART_MESSAGE }, + { E_POPUP_ITEM, "10.part.01", N_("Reply to _List"), emp_part_popup_reply_list, NULL, NULL, EM_POPUP_PART_MESSAGE}, + { E_POPUP_ITEM, "10.part.03", N_("Reply to _All"), emp_part_popup_reply_all, NULL, "stock_mail-reply-to-all", EM_POPUP_PART_MESSAGE}, + { E_POPUP_BAR, "20.part", NULL, NULL, NULL, NULL, EM_POPUP_PART_MESSAGE }, + { E_POPUP_ITEM, "20.part.00", N_("_Forward"), emp_part_popup_forward, NULL, "stock_mail-forward", EM_POPUP_PART_MESSAGE }, }; -static const EMPopupItem emp_standard_part_apps_bar = { EM_POPUP_BAR, "99.object" }; +static const EPopupItem emp_standard_part_apps_bar = { E_POPUP_BAR, "99.object" }; /* ********************************************************************** */ static void -emp_uri_popup_link_open(GtkWidget *w, EMPopupTarget *t) +emp_uri_popup_link_open(EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetURI *t = (EMPopupTargetURI *)ep->target; GError *err = NULL; - gnome_url_show(t->data.uri, &err); + gnome_url_show(t->uri, &err); if (err) { g_warning("gnome_url_show: %s", err->message); g_error_free(err); @@ -832,33 +521,36 @@ emp_uri_popup_link_open(GtkWidget *w, EMPopupTarget *t) } static void -emp_uri_popup_address_send (GtkWidget *w, EMPopupTarget *t) +emp_uri_popup_address_send(EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetURI *t = (EMPopupTargetURI *)ep->target; + /* TODO: have an emfv specific override to get the from uri */ - em_utils_compose_new_message_with_mailto (t->data.uri, NULL); + em_utils_compose_new_message_with_mailto(t->uri, NULL); } static void -emp_uri_popup_address_add(GtkWidget *w, EMPopupTarget *t) +emp_uri_popup_address_add(EPopup *ep, EPopupItem *item, void *data) { + EMPopupTargetURI *t = (EMPopupTargetURI *)ep->target; CamelURL *url; - url = camel_url_new(t->data.uri, NULL); + url = camel_url_new(t->uri, NULL); if (url == NULL) { - g_warning("cannot parse url '%s'", t->data.uri); + g_warning("cannot parse url '%s'", t->uri); return; } if (url->path && url->path[0]) - em_utils_add_address(w, url->path); + em_utils_add_address(ep->target->widget, url->path); camel_url_free(url); } -static EMPopupItem emp_standard_uri_popups[] = { - { EM_POPUP_ITEM, "00.uri.00", N_("_Open Link in Browser"), G_CALLBACK(emp_uri_popup_link_open), NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, - { EM_POPUP_ITEM, "00.uri.10", N_("Se_nd message to..."), G_CALLBACK(emp_uri_popup_address_send), NULL, NULL, EM_POPUP_URI_MAILTO }, - { EM_POPUP_ITEM, "00.uri.20", N_("_Add to Addressbook"), G_CALLBACK(emp_uri_popup_address_add), NULL, NULL, EM_POPUP_URI_MAILTO }, +static EPopupItem emp_standard_uri_popups[] = { + { E_POPUP_ITEM, "00.uri.00", N_("_Open Link in Browser"), emp_uri_popup_link_open, NULL, NULL, EM_POPUP_URI_NOT_MAILTO }, + { E_POPUP_ITEM, "00.uri.10", N_("Se_nd message to..."), emp_uri_popup_address_send, NULL, NULL, EM_POPUP_URI_MAILTO }, + { E_POPUP_ITEM, "00.uri.20", N_("_Add to Addressbook"), emp_uri_popup_address_add, NULL, NULL, EM_POPUP_URI_MAILTO }, }; /* ********************************************************************** */ @@ -867,23 +559,21 @@ static EMPopupItem emp_standard_uri_popups[] = { #include <libgnomevfs/gnome-vfs-mime-handlers.h> -struct _open_in_item { - EMPopupItem item; - EMPopupTarget *target; - GnomeVFSMimeApplication *app; -}; + EMPopupTargetPart *target; static void -emp_apps_open_in(GtkWidget *w, struct _open_in_item *item) +emp_apps_open_in(EPopup *ep, EPopupItem *item, void *data) { char *path; + EMPopupTargetPart *target = (EMPopupTargetPart *)ep->target; - path = em_utils_temp_save_part(item->target->widget, item->target->data.part.part); + path = em_utils_temp_save_part(target->target.widget, target->part); if (path) { - int douri = (item->app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS); + GnomeVFSMimeApplication *app = item->user_data; + int douri = (app->expects_uris == GNOME_VFS_MIME_APPLICATION_ARGUMENT_TYPE_URIS); char *command; - if (item->app->requires_terminal) { + if (app->requires_terminal) { char *term, *args = NULL; GConfClient *gconf; @@ -896,11 +586,11 @@ emp_apps_open_in(GtkWidget *w, struct _open_in_item *item) return; command = g_strdup_printf ("%s%s%s %s %s%s &", term, args ? " " : "", args ? args : "", - item->app->command, douri ? "file://" : "", path); + app->command, douri ? "file://" : "", path); g_free (term); g_free (args); } else { - command = g_strdup_printf ("%s %s%s &", item->app->command, douri ? "file://" : "", path); + command = g_strdup_printf ("%s %s%s &", app->command, douri ? "file://" : "", path); } /* FIXME: Do not use system here */ @@ -911,14 +601,14 @@ emp_apps_open_in(GtkWidget *w, struct _open_in_item *item) } static void -emp_apps_popup_free(GSList *free_list) +emp_apps_popup_free(EPopup *ep, GSList *free_list, void *data) { while (free_list) { GSList *n = free_list->next; - struct _open_in_item *item = free_list->data; + EPopupItem *item = free_list->data; - g_free(item->item.path); - g_free(item->item.label); + g_free(item->path); + g_free(item->label); g_free(item); g_slist_free_1(free_list); @@ -927,13 +617,19 @@ emp_apps_popup_free(GSList *free_list) } static void -emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data) +emp_standard_items_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); +} + +static void +emp_standard_menu_factory(EPopup *emp, void *data) { int i, len; - EMPopupItem *items; + EPopupItem *items; GSList *menus = NULL; - switch (target->type) { + switch (emp->target->type) { #if 0 case EM_POPUP_TARGET_SELECT: return; @@ -941,18 +637,21 @@ emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data) len = LEN(emp_standard_select_popups); break; #endif - case EM_POPUP_TARGET_URI: + case EM_POPUP_TARGET_URI: { + /*EMPopupTargetURI *t = (EMPopupTargetURI *)target;*/ + items = emp_standard_uri_popups; len = LEN(emp_standard_uri_popups); - break; + break; } case EM_POPUP_TARGET_PART: { - GList *apps = gnome_vfs_mime_get_short_list_applications(target->data.part.mime_type); + EMPopupTargetPart *t = (EMPopupTargetPart *)emp->target; + GList *apps = gnome_vfs_mime_get_short_list_applications(t->mime_type); /* FIXME: use the snoop_part stuff from em-format.c */ - if (apps == NULL && strcmp(target->data.part.mime_type, "application/octet-stream") == 0) { + if (apps == NULL && strcmp(t->mime_type, "application/octet-stream") == 0) { const char *filename, *name_type; - filename = camel_mime_part_get_filename(target->data.part.part); + filename = camel_mime_part_get_filename(t->part); if (filename) { /* GNOME-VFS will misidentify TNEF attachments as MPEG */ @@ -974,25 +673,23 @@ emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data) for (l = apps, i = 0; l; l = l->next, i++) { GnomeVFSMimeApplication *app = l->data; - struct _open_in_item *item; + EPopupItem *item; if (app->requires_terminal) continue; item = g_malloc0(sizeof(*item)); - item->item.type = EM_POPUP_ITEM; - item->item.path = g_strdup_printf("99.object.%02d", i); - item->item.label = g_strdup_printf(_("Open in %s..."), app->name); - item->item.activate = G_CALLBACK(emp_apps_open_in); - item->item.activate_data = item; - item->target = target; - item->app = app; + item->type = E_POPUP_ITEM; + item->path = g_strdup_printf("99.object.%02d", i); + item->label = g_strdup_printf(_("Open in %s..."), app->name); + item->activate = emp_apps_open_in; + item->user_data = app; open_menus = g_slist_prepend(open_menus, item); } if (open_menus) - em_popup_add_items(emp, open_menus, (GDestroyNotify)emp_apps_popup_free); + e_popup_add_items(emp, open_menus, emp_apps_popup_free, NULL); g_string_free(label, TRUE); g_list_free(apps); @@ -1007,12 +704,139 @@ emp_standard_menu_factory(EMPopup *emp, EMPopupTarget *target, void *data) } for (i=0;i<len;i++) { - if ((items[i].mask & target->mask) == 0) { - items[i].activate_data = target; + if ((items[i].visible & emp->target->mask) == 0) menus = g_slist_prepend(menus, &items[i]); - } } if (menus) - em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); + e_popup_add_items(emp, menus, emp_standard_items_free, NULL); +} + +/* ********************************************************************** */ + +/* 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" + mask="select_one" + activate="emp_view_emacs"/> + </menu> + </extension> + +*/ + +static void *emph_parent_class; +#define emph ((EMPopupHook *)eph) + +static const EPopupHookTargetMask emph_select_masks[] = { + { "one", EM_POPUP_SELECT_ONE }, + { "many", EM_POPUP_SELECT_MANY }, + { "mark_read", EM_POPUP_SELECT_MARK_READ }, + { "mark_unread", EM_POPUP_SELECT_MARK_UNREAD }, + { "delete", EM_POPUP_SELECT_DELETE }, + { "undelete", EM_POPUP_SELECT_UNDELETE }, + { "mailing_list", EM_POPUP_SELECT_MAILING_LIST }, + { "resend", EM_POPUP_SELECT_EDIT }, + { "mark_important", EM_POPUP_SELECT_MARK_IMPORTANT }, + { "mark_unimportant", EM_POPUP_SELECT_MARK_UNIMPORTANT }, + { "flag_followup", EM_POPUP_SELECT_FLAG_FOLLOWUP }, + { "flag_completed", EM_POPUP_SELECT_FLAG_COMPLETED }, + { "flag_clear", EM_POPUP_SELECT_FLAG_CLEAR }, + { "add_sender", EM_POPUP_SELECT_ADD_SENDER }, + { "mark_junk", EM_POPUP_SELECT_MARK_JUNK }, + { "mark_nojunk", EM_POPUP_SELECT_MARK_NOJUNK }, + { "folder", EM_POPUP_SELECT_FOLDER }, + { 0 } +}; + +static const EPopupHookTargetMask emph_uri_masks[] = { + { "http", EM_POPUP_URI_HTTP }, + { "mailto", EM_POPUP_URI_MAILTO }, + { "notmailto", EM_POPUP_URI_NOT_MAILTO }, + { 0 } +}; + +static const EPopupHookTargetMask emph_part_masks[] = { + { "message", EM_POPUP_PART_MESSAGE }, + { "image", EM_POPUP_PART_IMAGE }, + { 0 } +}; + +static const EPopupHookTargetMask emph_folder_masks[] = { + { "folder", EM_POPUP_FOLDER_FOLDER }, + { "store", EM_POPUP_FOLDER_STORE }, + { "inferiors", EM_POPUP_FOLDER_INFERIORS }, + { "delete", EM_POPUP_FOLDER_DELETE }, + { "select", EM_POPUP_FOLDER_SELECT }, + { 0 } +}; + +static const EPopupHookTargetMask emph_attachments_masks[] = { + { "one", EM_POPUP_ATTACHMENTS_ONE }, + { "many", EM_POPUP_ATTACHMENTS_MANY }, + { 0 } +}; + +static const EPopupHookTargetMap emph_targets[] = { + { "select", EM_POPUP_TARGET_SELECT, emph_select_masks }, + { "uri", EM_POPUP_TARGET_URI, emph_uri_masks }, + { "part", EM_POPUP_TARGET_PART, emph_part_masks }, + { "folder", EM_POPUP_TARGET_FOLDER, emph_folder_masks }, + { "attachments", EM_POPUP_TARGET_ATTACHMENTS, emph_attachments_masks }, + { 0 } +}; + +static void +emph_finalise(GObject *o) +{ + /*EPluginHook *eph = (EPluginHook *)o;*/ + + ((GObjectClass *)emph_parent_class)->finalize(o); +} + +static void +emph_class_init(EPluginHookClass *klass) +{ + int i; + + ((GObjectClass *)klass)->finalize = emph_finalise; + ((EPluginHookClass *)klass)->id = "com.ximian.evolution.mail.popup:1.0"; + + for (i=0;emph_targets[i].type;i++) + e_popup_hook_class_add_target_map((EPopupHookClass *)klass, &emph_targets[i]); + + ((EPopupHookClass *)klass)->popup_class = g_type_class_ref(em_popup_get_type()); +} + +GType +em_popup_hook_get_type(void) +{ + static GType type = 0; + + if (!type) { + static const GTypeInfo info = { + sizeof(EMPopupHookClass), NULL, NULL, (GClassInitFunc) emph_class_init, NULL, NULL, + sizeof(EMPopupHook), 0, (GInstanceInitFunc) NULL, + }; + + emph_parent_class = g_type_class_ref(e_popup_hook_get_type()); + type = g_type_register_static(e_popup_hook_get_type(), "EMPopupHook", &info, 0); + } + + return type; } diff --git a/mail/em-popup.h b/mail/em-popup.h index 8f235d19e0..d35f171629 100644 --- a/mail/em-popup.h +++ b/mail/em-popup.h @@ -25,55 +25,71 @@ #include <glib-object.h> +#include "e-util/e-popup.h" + #ifdef __cplusplus extern "C" { #pragma } #endif /* __cplusplus */ -/* NB: This is TEMPORARY, to be replaced by EggMenu, if it does what we need? */ - typedef struct _EMPopup EMPopup; typedef struct _EMPopupClass EMPopupClass; -typedef struct _EMPopupItem EMPopupItem; -typedef struct _EMPopupFactory EMPopupFactory; /* anonymous type */ -typedef struct _EMPopupTarget EMPopupTarget; - -typedef void (*EMPopupFactoryFunc)(EMPopup *emp, EMPopupTarget *target, void *data); - -/* Menu item descriptions */ -enum _em_popup_t { - EM_POPUP_ITEM = 0, - EM_POPUP_TOGGLE, - EM_POPUP_RADIO, - EM_POPUP_IMAGE, - EM_POPUP_SUBMENU, - EM_POPUP_BAR, - EM_POPUP_TYPE_MASK = 0xffff, - EM_POPUP_ACTIVE = 0x10000, -}; - -struct _EMPopupItem { - enum _em_popup_t type; - char *path; /* absolute path! must sort ascii-lexographically into the right spot */ - char *label; - GCallback activate; - void *activate_data; - void *image; /* char* for item type, GtkWidget * for image type */ - guint32 mask; -}; - -/* Current target description */ -/* Types of popup tagets */ +/** + * enum _em_popup_target_t - A list of mail popup target types. + * + * @EM_POPUP_TARGET_SELECT: A selection of messages. + * @EM_POPUP_TARGET_URI: A URI. + * @EM_POPUP_TARGET_PART: A CamelMimePart message part. + * @EM_POPUP_TARGET_FOLDER: A folder URI. + * @EM_POPUP_TARGET_ATTACHMENTS: A list of attachments. + * + * Defines the value of the targetid for all EMPopup target types. + **/ enum _em_popup_target_t { EM_POPUP_TARGET_SELECT, EM_POPUP_TARGET_URI, EM_POPUP_TARGET_PART, EM_POPUP_TARGET_FOLDER, + EM_POPUP_TARGET_ATTACHMENTS, }; -/* Flags that describe a TARGET_SELECT */ -enum { +/** + * enum _em_popup_target_select_t - EMPopupTargetSelect qualifiers. + * + * @EM_POPUP_SELECT_ONE: Only one item is selected. + * @EM_POPUP_SELECT_MANY: One ore more items are selected. + * @EM_POPUP_SELECT_MARK_READ: Message(s) are unseen and can be + * marked seen. + * @EM_POPUP_SELECT_MARK_UNREAD: Message(s) are seen and can be + * marked unseen. + * @EM_POPUP_SELECT_DELETE: Message(s) are undeleted and can + * be marked deleted. + * @EM_POPUP_SELECT_UNDELETE: Message(s) are deleted and can be + * undeleted. + * @EM_POPUP_SELECT_MAILING_LIST: If one message is selected, and it + * contains a message list tag. + * @EM_POPUP_SELECT_EDIT: The message can be opened for editing (the + * folder is a sent folder). + * @EM_POPUP_SELECT_MARK_IMPORTANT: Message(s) are not marked + * important. + * @EM_POPUP_SELECT_MARK_UNIMPORTANT: Message(s) are marked + * important. + * @EM_POPUP_SELECT_FLAG_FOLLOWUP: Message(s) are not flagged for + * followup. + * @EM_POPUP_SELECT_FLAG_COMPLETED: Message(s) are not flagged completed. + * @EM_POPUP_SELECT_FLAG_CLEAR: Message(s) are flagged for followup. + * @EM_POPUP_SELECT_ADD_SENDER: The message contains sender addresses + * which might be added to the addressbook. i.e. it isn't a message in + * the Sent or Drafts folders. + * @EM_POPUP_SELECT_MARK_JUNK: Message(s) are not marked as junk. + * @EM_POPUP_SELECT_MARK_NOJUNK: Message(s) are marked as junk. + * @EM_POPUP_SELECT_FOLDER: A folder is set on the selection. + * @EM_POPUP_SELECT_LAST: The last bit used, can be used to add + * additional types from derived application code. + * + **/ +enum _em_popup_target_select_t { EM_POPUP_SELECT_ONE = 1<<1, EM_POPUP_SELECT_MANY = 1<<2, EM_POPUP_SELECT_MARK_READ = 1<<3, @@ -91,24 +107,47 @@ enum { EM_POPUP_SELECT_MARK_JUNK = 1<<15, EM_POPUP_SELECT_MARK_NOJUNK = 1<<16, EM_POPUP_SELECT_FOLDER = 1<<17, /* do we have any folder at all? */ - EM_POPUP_SELECT_LAST = 1<<18 /* reserve 2 slots */ + EM_POPUP_SELECT_LAST = 1<<18, /* reserve 2 slots */ }; -/* Flags that describe a TARGET_URI */ -enum { +/** + * enum _em_popup_target_uri_t - EMPopupTargetURI qualifiers. + * + * @EM_POPUP_URI_HTTP: This is a HTTP or HTTPS url. + * @EM_POPUP_URI_MAILTO: This is a MAILTO url. + * @EM_POPUP_URI_NOT_MAILTO: This is not a MAILTO url. + * + **/ +enum _em_popup_target_uri_t { EM_POPUP_URI_HTTP = 1<<0, EM_POPUP_URI_MAILTO = 1<<1, EM_POPUP_URI_NOT_MAILTO = 1<<2, }; -/* Flags that describe TARGET_PART */ -enum { +/** + * enum _em_popup_target_part_t - EMPopupTargetPart qualifiers. + * + * @EM_POPUP_PART_MESSAGE: This is a message type. + * @EM_POPUP_PART_IMAGE: This is an image type. + * + **/ +enum _em_popup_target_part_t { EM_POPUP_PART_MESSAGE = 1<<0, EM_POPUP_PART_IMAGE = 1<<1, }; -/* Flags that describe TARGET_FOLDER */ -enum { +/** + * enum _em_popup_target_folder_t - EMPopupTargetFolder qualifiers. + * + * @EM_POPUP_FOLDER_FOLDER: This is a normal folder. + * @EM_POPUP_FOLDER_STORE: This is a store. + * @EM_POPUP_FOLDER_INFERIORS: This folder may have child folders. + * @EM_POPUP_FOLDER_DELETE: This folder can be deleted or renamed. + * @EM_POPUP_FOLDER_SELECT: This folder exists and can be selected or + * opened. + * + **/ +enum _em_popup_target_folder_t { EM_POPUP_FOLDER_FOLDER = 1<<0, /* normal folder */ EM_POPUP_FOLDER_STORE = 1<<1, /* root/nonselectable folder, i.e. store */ EM_POPUP_FOLDER_INFERIORS = 1<<2, /* folder can have children */ @@ -116,57 +155,137 @@ enum { EM_POPUP_FOLDER_SELECT = 1<<4, /* folder can be selected/opened */ }; -struct _EMPopupTarget { - enum _em_popup_target_t type; - guint32 mask; /* depends on type, see above */ - struct _GtkWidget *widget; /* used if you need a parent toplevel, if available */ - union { - char *uri; - struct { - struct _CamelFolder *folder; - char *folder_uri; - GPtrArray *uids; - } select; - struct { - char *mime_type; - struct _CamelMimePart *part; - } part; - struct { - char *folder_uri; - } folder; - } data; +/** + * enum _em_popup_target_attachments_t - EMPopupTargetAttachments qualifiers. + * + * @EM_POPUP_ATTACHMENTS_ONE: There is one and only one attachment selected. + * @EM_POPUP_ATTACHMENTS_MANY: There is one or more attachments selected. + * + **/ +enum _em_popup_target_attachments_t { + EM_POPUP_ATTACHMENTS_ONE = 1<<0, /* only 1 selected */ + EM_POPUP_ATTACHMENTS_MANY = 1<<1, /* one or more selected */ +}; + +typedef struct _EMPopupTargetSelect EMPopupTargetSelect; +typedef struct _EMPopupTargetURI EMPopupTargetURI; +typedef struct _EMPopupTargetPart EMPopupTargetPart; +typedef struct _EMPopupTargetFolder EMPopupTargetFolder; +typedef struct _EMPopupTargetAttachments EMPopupTargetAttachments; + +/** + * struct _EMPopupTargetURI - An inline URI. + * + * @target: Superclass. + * @uri: The encoded URI to which this target applies. + * + * Used to represent popup-menu context on any URI object. + **/ +struct _EMPopupTargetURI { + EPopupTarget target; + char *uri; +}; + +/** + * struct _EMPopupTargetSelect - A list of messages. + * + * @target: Superclass. + * @folder: The CamelFolder of the selected messages. + * @uri: The encoded URI represending this folder. + * @uids: An array of UID strings of messages within @folder. + * + * Used to represent a selection of messages as context for a popup + * menu. All items may be NULL if the current view has no active + * folder selected. + **/ +struct _EMPopupTargetSelect { + EPopupTarget target; + struct _CamelFolder *folder; + char *uri; + GPtrArray *uids; +}; + +/** + * struct _EMPopupTargetPart - A Camel object. + * + * @target: Superclass. + * @mime_type: MIME type of the part. This may be a calculated type + * not matching the @part's MIME type. + * @part: A CamelMimePart representing a message or attachment. + * + * Used to represent a message part as context for a popup menu. This + * is used for both attachments and inline-images. + **/ +struct _EMPopupTargetPart { + EPopupTarget target; + char *mime_type; + struct _CamelMimePart *part; +}; + +/** + * struct _EMPopupTargetFolder - A folder uri. + * + * @target: Superclass. + * @uri: A folder URI. + * + * This target is used to represent folder context. + **/ +struct _EMPopupTargetFolder { + EPopupTarget target; + char *uri; +}; + +/** + * struct _EMPopupTargetAttachments - A list of composer attachments. + * + * @target: Superclass. + * @attachments: A GSList list of EMsgComposer attachments. + * + * This target is used to represent a selected list of attachments in + * the message composer attachment area. + **/ +struct _EMPopupTargetAttachments { + EPopupTarget target; + GSList *attachments; }; +typedef struct _EPopupItem EMPopupItem; + /* The object */ struct _EMPopup { - GObject object; + EPopup popup; struct _EMPopupPrivate *priv; - - char *menuid; }; struct _EMPopupClass { - GObjectClass object_class; + EPopupClass popup_class; }; GType em_popup_get_type(void); -/* Static class methods */ -EMPopupFactory *em_popup_static_add_factory(const char *menuid, EMPopupFactoryFunc func, void *data); -void em_popup_static_remove_factory(EMPopupFactory *f); - EMPopup *em_popup_new(const char *menuid); -void em_popup_add_items(EMPopup *, GSList *items, GDestroyNotify freefunc); -void em_popup_add_static_items(EMPopup *emp, EMPopupTarget *target); -struct _GtkMenu *em_popup_create_menu(EMPopup *, guint32 hide_mask, guint32 disable_mask); -struct _GtkMenu *em_popup_create_menu_once(EMPopup *emp, EMPopupTarget *, guint32 hide_mask, guint32 disable_mask); - -EMPopupTarget *em_popup_target_new_uri(const char *uri); -EMPopupTarget *em_popup_target_new_select(struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids); -EMPopupTarget *em_popup_target_new_part(struct _CamelMimePart *part, const char *mime_type); -EMPopupTarget *em_popup_target_new_folder(const char *uri, guint32 info_flags, guint32 popup_flags); -void em_popup_target_free(EMPopupTarget *target); + +EMPopupTargetURI *em_popup_target_new_uri(EMPopup *emp, const char *uri); +EMPopupTargetSelect *em_popup_target_new_select(EMPopup *emp, struct _CamelFolder *folder, const char *folder_uri, GPtrArray *uids); +EMPopupTargetPart *em_popup_target_new_part(EMPopup *emp, struct _CamelMimePart *part, const char *mime_type); +EMPopupTargetFolder *em_popup_target_new_folder(EMPopup *emp, const char *uri, guint32 info_flags, guint32 popup_flags); +EMPopupTargetAttachments *em_popup_target_new_attachments(EMPopup *emp, GSList *attachments); + +/* ********************************************************************** */ + +typedef struct _EMPopupHook EMPopupHook; +typedef struct _EMPopupHookClass EMPopupHookClass; + +struct _EMPopupHook { + EPopupHook hook; +}; + +struct _EMPopupHookClass { + EPopupHookClass hook_class; +}; + +GType em_popup_hook_get_type(void); #ifdef __cplusplus } diff --git a/mail/em-subscribe-editor.c b/mail/em-subscribe-editor.c index da53eb1a53..08954e9c8c 100644 --- a/mail/em-subscribe-editor.c +++ b/mail/em-subscribe-editor.c @@ -43,6 +43,7 @@ #include "mail-config.h" #include <glade/glade.h> +#include <libgnome/gnome-i18n.h> #include <gtk/gtkdialog.h> #include <gtk/gtkscrolledwindow.h> diff --git a/mail/em-utils.c b/mail/em-utils.c index 8c22fe8926..2adf723f67 100644 --- a/mail/em-utils.c +++ b/mail/em-utils.c @@ -44,13 +44,13 @@ #include <libgnomevfs/gnome-vfs-mime.h> #include <libgnomevfs/gnome-vfs-mime-utils.h> #include <libgnomevfs/gnome-vfs-mime-handlers.h> +#include <libgnome/gnome-i18n.h> #include "mail-component.h" #include "mail-mt.h" #include "mail-ops.h" #include "mail-tools.h" #include "mail-config.h" -#include "mail-config-druid.h" #include "message-tag-followup.h" #include <e-util/e-mktemp.h> @@ -63,6 +63,7 @@ #include "em-utils.h" #include "em-composer-utils.h" #include "em-format-quote.h" +#include "em-account-editor.h" static void emu_save_part_done (CamelMimePart *part, char *name, int done, void *data); @@ -178,19 +179,18 @@ druid_destroy_cb (gpointer user_data, GObject *deadbeef) gboolean em_utils_configure_account (GtkWidget *parent) { - MailConfigDruid *druid; - - druid = mail_config_druid_new (); - + EMAccountEditor *emae; + + emae = em_account_editor_new(NULL, EMAE_DRUID); if (parent != NULL) - e_dialog_set_transient_for ((GtkWindow *) druid, parent); - - g_object_weak_ref ((GObject *) druid, (GWeakNotify) druid_destroy_cb, NULL); - gtk_widget_show ((GtkWidget *) druid); - gtk_grab_add ((GtkWidget *) druid); - gtk_main (); + e_dialog_set_transient_for((GtkWindow *)emae->editor, parent); + + g_object_weak_ref((GObject *)emae->editor, (GWeakNotify) druid_destroy_cb, NULL); + gtk_widget_show(emae->editor); + gtk_grab_add(emae->editor); + gtk_main(); - return mail_config_is_configured (); + return mail_config_is_configured(); } /** diff --git a/mail/importers/evolution-mbox-importer.c b/mail/importers/evolution-mbox-importer.c index 78b6c7d27b..3630277cdd 100644 --- a/mail/importers/evolution-mbox-importer.c +++ b/mail/importers/evolution-mbox-importer.c @@ -41,6 +41,7 @@ #include <gtk/gtkprogressbar.h> #include <bonobo/bonobo-control.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-exception.h> diff --git a/mail/importers/evolution-outlook-importer.c b/mail/importers/evolution-outlook-importer.c index bf05c6316d..4b78cd3b81 100644 --- a/mail/importers/evolution-outlook-importer.c +++ b/mail/importers/evolution-outlook-importer.c @@ -40,6 +40,7 @@ #include <gtk/gtkprogressbar.h> #include <bonobo/bonobo-control.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-exception.h> #include <camel/camel-folder.h> diff --git a/mail/importers/mail-importer.c b/mail/importers/mail-importer.c index 582efb761a..b9e022ac29 100644 --- a/mail/importers/mail-importer.c +++ b/mail/importers/mail-importer.c @@ -36,6 +36,7 @@ #include <gmodule.h> #include <libgnome/gnome-util.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-folder.h> #include <camel/camel-store.h> #include <camel/camel-mime-message.h> diff --git a/mail/mail-account-editor.c b/mail/mail-account-editor.c index bf5b265aab..347c78f90a 100644 --- a/mail/mail-account-editor.c +++ b/mail/mail-account-editor.c @@ -36,6 +36,7 @@ #include <gtk/gtknotebook.h> #include <gtk/gtkstock.h> +#include <libgnome/gnome-i18n.h> #include "widgets/misc/e-error.h" diff --git a/mail/mail-account-gui.c b/mail/mail-account-gui.c index 5de9dc8211..6e5328ef87 100644 --- a/mail/mail-account-gui.c +++ b/mail/mail-account-gui.c @@ -45,6 +45,7 @@ #include <gtk/gtknotebook.h> #include <gtk/gtkhbox.h> #include <gtk/gtkdialog.h> +#include <libgnome/gnome-i18n.h> #ifdef USE_GTKFILECHOOSER #include <gtk/gtkfilechooser.h> #include <gtk/gtkradiobutton.h> @@ -2442,8 +2443,6 @@ mail_account_gui_save (MailAccountGui *gui) mail_config_save_accounts (); - mail_autoreceive_setup (); - return TRUE; } diff --git a/mail/mail-component.c b/mail/mail-component.c index 6d2201937a..4ac09c0ce3 100644 --- a/mail/mail-component.c +++ b/mail/mail-component.c @@ -36,6 +36,7 @@ #include <errno.h> #include "em-popup.h" +#include "em-menu.h" #include "em-utils.h" #include "em-composer-utils.h" #include "em-format.h" @@ -75,6 +76,7 @@ #include <gal/e-table/e-tree.h> #include <gal/e-table/e-tree-memory.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-file-utils.h> #include <camel/camel-vtrash-folder.h> @@ -319,6 +321,24 @@ mc_startup(MailComponent *mc) mc_setup_local_store(mc); load_accounts(mc, mail_config_get_accounts()); vfolder_load_storage(); + +#ifdef ENABLE_MONO + if (getenv("EVOLUTION_DISABLE_MONO") == NULL) + e_plugin_register_type(e_plugin_mono_get_type()); +#endif + e_plugin_register_type(e_plugin_lib_get_type()); + e_plugin_hook_register_type(em_popup_hook_get_type()); + e_plugin_hook_register_type(em_menu_hook_get_type()); + e_plugin_hook_register_type(em_config_hook_get_type()); + + e_plugin_hook_register_type(em_format_hook_get_type()); + em_format_hook_register_type(em_format_get_type()); + em_format_hook_register_type(em_format_html_get_type()); + em_format_hook_register_type(em_format_html_display_get_type()); + + e_plugin_hook_register_type(em_event_hook_get_type()); + + e_plugin_load_plugins(); } static void @@ -857,7 +877,7 @@ mail_component_init (MailComponent *component) priv->async_event = mail_async_event_new(); priv->store_hash = g_hash_table_new (NULL, NULL); - mail_autoreceive_setup(); + mail_autoreceive_init(); offline = mail_offline_handler_new(); bonobo_object_add_interface((BonoboObject *)component, (BonoboObject *)offline); diff --git a/mail/mail-config-druid.c b/mail/mail-config-druid.c index 65e397f368..22f26b69ea 100644 --- a/mail/mail-config-druid.c +++ b/mail/mail-config-druid.c @@ -41,6 +41,7 @@ #include <libgnomeui/gnome-druid.h> #include <libgnomeui/gnome-druid-page-standard.h> #include <bonobo/bonobo-exception.h> +#include <libgnome/gnome-i18n.h> #include "mail-config-druid.h" #include "mail-config.h" diff --git a/mail/mail-config.glade b/mail/mail-config.glade index 2103e332e5..625b952991 100644 --- a/mail/mail-config.glade +++ b/mail/mail-config.glade @@ -47,6 +47,27 @@ Click "Forward" to begin. </property> <property name="spacing">0</property> <child> + <widget class="GtkLabel" id="identity_help"> + <property name="visible">True</property> + <property name="label" translatable="yes">Please enter your name and email address below. The "optional" fields below do not need to be filled in, unless you wish to include this information in email you send.</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_FILL</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> <placeholder/> </child> </widget> @@ -67,24 +88,25 @@ Click "Forward" to begin. </property> <property name="spacing">0</property> <child> - <placeholder/> + <widget class="GtkLabel" id="extra_help"> + <property name="visible">True</property> + <property name="label" translatable="yes">Please select among the following options</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_FILL</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> </child> - </widget> - </child> - </widget> - </child> - - <child> - <widget class="GnomeDruidPageStandard" id="extra_page"> - <property name="visible">True</property> - <property name="title" translatable="yes">Receiving Email</property> - - <child internal-child="vbox"> - <widget class="GtkVBox" id="druid_extra_vbox"> - <property name="border_width">16</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> <child> <placeholder/> @@ -107,6 +129,27 @@ Click "Forward" to begin. </property> <property name="spacing">0</property> <child> + <widget class="GtkLabel" id="transport_help"> + <property name="visible">True</property> + <property name="label" translatable="yes">Please enter information about the way you will send mail. If you are not sure, ask your system administrator or Internet Service Provider.</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_FILL</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> <placeholder/> </child> </widget> @@ -127,6 +170,27 @@ Click "Forward" to begin. </property> <property name="spacing">0</property> <child> + <widget class="GtkLabel" id="management_help"> + <property name="visible">True</property> + <property name="label" translatable="yes">You are almost done with the mail configuration process. The identity, incoming mail server and outgoing mail transport method which you provided will be grouped together to make an Evolution mail account. Please enter a name for this account in the space below. This name will be used for display purposes only.</property> + <property name="use_underline">False</property> + <property name="use_markup">False</property> + <property name="justify">GTK_JUSTIFY_FILL</property> + <property name="wrap">True</property> + <property name="selectable">False</property> + <property name="xalign">0.5</property> + <property name="yalign">0.5</property> + <property name="xpad">0</property> + <property name="ypad">0</property> + </widget> + <packing> + <property name="padding">0</property> + <property name="expand">False</property> + <property name="fill">True</property> + </packing> + </child> + + <child> <placeholder/> </child> </widget> @@ -626,7 +690,7 @@ For example: "Work" or "Personal"</property> <property name="yalign">0.5</property> <property name="xpad">0</property> <property name="ypad">0</property> - <property name="mnemonic_widget">sigOption</property> + <property name="mnemonic_widget">signature_dropdown</property> </widget> <packing> <property name="left_attach">0</property> @@ -645,24 +709,12 @@ For example: "Work" or "Personal"</property> <property name="spacing">6</property> <child> - <widget class="GtkOptionMenu" id="sigOption"> + <widget class="Custom" id="signature_dropdown"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget1"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget2"> - <property name="visible">True</property> - <property name="label" translatable="yes">Default</property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_dropdown_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Mon, 06 Sep 2004 01:00:22 GMT</property> </widget> <packing> <property name="padding">0</property> @@ -894,7 +946,7 @@ For example: "Work" or "Personal"</property> <property name="yalign">0.5</property> <property name="xpad">0</property> <property name="ypad">0</property> - <property name="mnemonic_widget">source_type_omenu</property> + <property name="mnemonic_widget">source_type_dropdown</property> </widget> <packing> <property name="left_attach">0</property> @@ -955,56 +1007,12 @@ For example: "Work" or "Personal"</property> </child> <child> - <widget class="GtkOptionMenu" id="source_type_omenu"> + <widget class="Custom" id="source_type_dropdown"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget3"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget4"> - <property name="visible">True</property> - <property name="label" translatable="yes">POP</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget5"> - <property name="visible">True</property> - <property name="label" translatable="yes">IMAPv4 </property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget6"> - <property name="visible">True</property> - <property name="label" translatable="yes">Standard Unix mbox</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget7"> - <property name="visible">True</property> - <property name="label" translatable="yes">Qmail maildir </property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget8"> - <property name="visible">True</property> - <property name="label" translatable="yes">None</property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_dropdown_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Thu, 29 Jul 2004 05:31:24 GMT</property> </widget> <packing> <property name="left_attach">1</property> @@ -1373,40 +1381,12 @@ For example: "Work" or "Personal"</property> </child> <child> - <widget class="GtkOptionMenu" id="source_use_ssl"> + <widget class="Custom" id="source_use_ssl"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget9"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget10"> - <property name="visible">True</property> - <property name="label" translatable="yes">Always</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget11"> - <property name="visible">True</property> - <property name="label" translatable="yes">Whenever Possible</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget12"> - <property name="visible">True</property> - <property name="label" translatable="yes">Never</property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_ssl_selector_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Tue, 03 Aug 2004 07:22:52 GMT</property> </widget> <packing> <property name="padding">0</property> @@ -1561,32 +1541,12 @@ For example: "Work" or "Personal"</property> <property name="spacing">6</property> <child> - <widget class="GtkOptionMenu" id="source_auth_omenu"> + <widget class="Custom" id="source_auth_dropdown"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget13"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget14"> - <property name="visible">True</property> - <property name="label" translatable="yes">Password</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget15"> - <property name="visible">True</property> - <property name="label" translatable="yes">Kerberos </property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_dropdown_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Thu, 29 Jul 2004 08:38:30 GMT</property> </widget> <packing> <property name="padding">0</property> @@ -1673,229 +1633,9 @@ For example: "Work" or "Personal"</property> </child> <child> - <widget class="GtkLabel" id="label32"> - <property name="visible">True</property> - <property name="label" translatable="yes">Receiving Mail</property> - <property name="use_underline">True</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_CENTER</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </widget> - <packing> - <property name="type">tab</property> - </packing> - </child> - - <child> - <widget class="GtkVBox" id="vboxExtraTableBorder"> - <property name="border_width">12</property> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">12</property> - - <child> - <widget class="GtkTable" id="extra_table"> - <property name="visible">True</property> - <property name="n_rows">1</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">18</property> - <property name="column_spacing">6</property> - - <child> - <widget class="GtkVBox" id="extra_mailcheck_frame"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">6</property> - - <child> - <widget class="GtkTable" id="extra_mailcheck_table"> - <property name="visible">True</property> - <property name="n_rows">1</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">6</property> - - <child> - <widget class="GtkTable" id="extra_table"> - <property name="visible">True</property> - <property name="n_rows">1</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">6</property> - <property name="column_spacing">0</property> - - <child> - <widget class="GtkVBox" id="extra_mailcheck_frame"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">0</property> - - <child> - <widget class="GtkLabel" id="lblMailCheck"> - <property name="visible">True</property> - <property name="label" translatable="yes"><span weight="bold">Checking for New Mail</span></property> - <property name="use_underline">False</property> - <property name="use_markup">True</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - - <child> - <widget class="GtkTable" id="extra_mailcheck_table"> - <property name="border_width">12</property> - <property name="visible">True</property> - <property name="n_rows">1</property> - <property name="n_columns">2</property> - <property name="homogeneous">False</property> - <property name="row_spacing">0</property> - <property name="column_spacing">0</property> - - <child> - <widget class="GtkHBox" id="extra_mailcheck_hbox"> - <property name="visible">True</property> - <property name="homogeneous">False</property> - <property name="spacing">4</property> - - <child> - <widget class="GtkCheckButton" id="extra_auto_check"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="label" translatable="yes">Automatically check for _new mail every</property> - <property name="use_underline">True</property> - <property name="relief">GTK_RELIEF_NORMAL</property> - <property name="focus_on_click">True</property> - <property name="active">False</property> - <property name="inconsistent">False</property> - <property name="draw_indicator">True</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - - <child> - <widget class="GtkSpinButton" id="extra_auto_check_min"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="climb_rate">1</property> - <property name="digits">0</property> - <property name="numeric">True</property> - <property name="update_policy">GTK_UPDATE_ALWAYS</property> - <property name="snap_to_ticks">False</property> - <property name="wrap">False</property> - <property name="adjustment">10 1 1440 1 10 10</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - - <child> - <widget class="GtkLabel" id="lblMinutes"> - <property name="visible">True</property> - <property name="label" translatable="yes">minutes</property> - <property name="use_underline">False</property> - <property name="use_markup">False</property> - <property name="justify">GTK_JUSTIFY_LEFT</property> - <property name="wrap">False</property> - <property name="selectable">False</property> - <property name="xalign">0.5</property> - <property name="yalign">0.5</property> - <property name="xpad">0</property> - <property name="ypad">0</property> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </widget> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - </packing> - </child> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">True</property> - <property name="fill">True</property> - </packing> - </child> - </widget> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - </packing> - </child> - </widget> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - </packing> - </child> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - </widget> - <packing> - <property name="left_attach">0</property> - <property name="right_attach">2</property> - <property name="top_attach">0</property> - <property name="bottom_attach">1</property> - </packing> - </child> - </widget> - <packing> - <property name="padding">0</property> - <property name="expand">False</property> - <property name="fill">True</property> - </packing> - </child> - </widget> - <packing> - <property name="tab_expand">False</property> - <property name="tab_fill">True</property> - </packing> - </child> - - <child> <widget class="GtkLabel" id="label33"> <property name="visible">True</property> - <property name="label" translatable="yes">Receiving Options</property> + <property name="label" translatable="yes">Receiving Email</property> <property name="use_underline">True</property> <property name="use_markup">False</property> <property name="justify">GTK_JUSTIFY_CENTER</property> @@ -1946,7 +1686,7 @@ For example: "Work" or "Personal"</property> <property name="yalign">0</property> <property name="xpad">0</property> <property name="ypad">0</property> - <property name="mnemonic_widget">transport_type_omenu</property> + <property name="mnemonic_widget">transport_type_dropdown</property> </widget> <packing> <property name="left_attach">0</property> @@ -1982,32 +1722,12 @@ For example: "Work" or "Personal"</property> </child> <child> - <widget class="GtkOptionMenu" id="transport_type_omenu"> + <widget class="Custom" id="transport_type_dropdown"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget16"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget17"> - <property name="visible">True</property> - <property name="label" translatable="yes">SMTP</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget18"> - <property name="visible">True</property> - <property name="label" translatable="yes">Sendmail</property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_dropdown_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Thu, 29 Jul 2004 05:42:00 GMT</property> </widget> <packing> <property name="left_attach">1</property> @@ -2335,40 +2055,12 @@ For example: "Work" or "Personal"</property> </child> <child> - <widget class="GtkOptionMenu" id="transport_use_ssl"> + <widget class="Custom" id="transport_use_ssl"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget19"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget20"> - <property name="visible">True</property> - <property name="label" translatable="yes">Always</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget21"> - <property name="visible">True</property> - <property name="label" translatable="yes">Whenever Possible</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget22"> - <property name="visible">True</property> - <property name="label" translatable="yes">Never</property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_ssl_selector_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Tue, 03 Aug 2004 07:23:50 GMT</property> </widget> <packing> <property name="padding">0</property> @@ -2547,7 +2239,7 @@ For example: "Work" or "Personal"</property> <property name="yalign">0.5</property> <property name="xpad">0</property> <property name="ypad">0</property> - <property name="mnemonic_widget">transport_auth_omenu</property> + <property name="mnemonic_widget">transport_auth_dropdown</property> </widget> <packing> <property name="left_attach">0</property> @@ -2612,32 +2304,12 @@ For example: "Work" or "Personal"</property> <property name="spacing">6</property> <child> - <widget class="GtkOptionMenu" id="transport_auth_omenu"> + <widget class="Custom" id="transport_auth_dropdown"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="history">0</property> - - <child internal-child="menu"> - <widget class="GtkMenu" id="convertwidget23"> - <property name="visible">True</property> - - <child> - <widget class="GtkMenuItem" id="convertwidget24"> - <property name="visible">True</property> - <property name="label" translatable="yes">Password</property> - <property name="use_underline">True</property> - </widget> - </child> - - <child> - <widget class="GtkMenuItem" id="convertwidget25"> - <property name="visible">True</property> - <property name="label" translatable="yes">Kerberos </property> - <property name="use_underline">True</property> - </widget> - </child> - </widget> - </child> + <property name="creation_function">em_account_editor_dropdown_new</property> + <property name="int1">0</property> + <property name="int2">0</property> + <property name="last_modification_time">Thu, 29 Jul 2004 08:37:13 GMT</property> </widget> <packing> <property name="padding">0</property> @@ -4352,7 +4024,7 @@ For example: "Work" or "Personal"</property> <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> <child> - <widget class="GtkNotebook" id="toplevel"> + <widget class="GtkNotebook" id="preferences_toplevel"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="show_tabs">True</property> @@ -6450,7 +6122,7 @@ For example: "Work" or "Personal"</property> <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> <child> - <widget class="GtkNotebook" id="toplevel"> + <widget class="GtkNotebook" id="composer_toplevel"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="show_tabs">True</property> diff --git a/mail/mail-dialogs.glade b/mail/mail-dialogs.glade index d0aba915ac..9f90948f7e 100644 --- a/mail/mail-dialogs.glade +++ b/mail/mail-dialogs.glade @@ -795,7 +795,7 @@ </child> </widget> -<widget class="GtkDialog" id="lic_dialog"> +<widget class="GtkDialog" id="license_dialog"> <property name="visible">True</property> <property name="title" translatable="yes">License Agreement</property> <property name="type">GTK_WINDOW_TOPLEVEL</property> @@ -909,7 +909,7 @@ <property name="spacing">0</property> <child> - <widget class="GtkLabel" id="lic_top_label"> + <widget class="GtkLabel" id="license_top_label"> <property name="visible">True</property> <property name="label" translatable="yes"> Please read carefully the license agreement displayed @@ -942,7 +942,7 @@ <property name="window_placement">GTK_CORNER_TOP_LEFT</property> <child> - <widget class="GtkTextView" id="textview1"> + <widget class="GtkTextView" id="license_textview"> <property name="width_request">500</property> <property name="height_request">400</property> <property name="visible">True</property> @@ -969,7 +969,7 @@ </child> <child> - <widget class="GtkCheckButton" id="lic_checkbutton"> + <widget class="GtkCheckButton" id="license_checkbutton"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="label" translatable="yes">Tick this to accept the license agreement</property> diff --git a/mail/mail-errors.xml b/mail/mail-errors.xml index 9e32d5aba2..44717434d3 100644 --- a/mail/mail-errors.xml +++ b/mail/mail-errors.xml @@ -311,6 +311,13 @@ You can choose to ignore this folder, overwrite or append its contents, or quit. <button label="_Append" response="GTK_RESPONSE_OK"/> </error> + <error id="no-load-license" type="error"> + <primary>Unable to read license file.</primary> + <secondary>Cannot read the license file "{0}", due to an + installation problem. You will not be able to use this provider until + you can accept its license.</secondary> + </error> + <error id="gw-accountsetup-error" type="error"> <primary><span weight="bold" size="larger">Unable to connect to the GroupWise server.</span></primary> diff --git a/mail/mail-folder-cache.c b/mail/mail-folder-cache.c index 52765b4ffd..91616dc536 100644 --- a/mail/mail-folder-cache.c +++ b/mail/mail-folder-cache.c @@ -34,6 +34,7 @@ #include <time.h> #include <libgnome/gnome-sound.h> +#include <libgnome/gnome-i18n.h> #include <bonobo/bonobo-exception.h> #include <camel/camel-store.h> #include <camel/camel-folder.h> @@ -53,6 +54,8 @@ #include "mail-config.h" #include "em-folder-tree-model.h" +#include "em-event.h" + #define w(x) #define d(x) @@ -238,6 +241,14 @@ real_flush_updates(void *o, void *event_data, void *data) if (notify_type != 0 && up->new && notify_idle_id == 0 && (now - last_notify >= 5)) notify_idle_id = g_idle_add_full (G_PRIORITY_LOW, notify_idle_cb, NULL, NULL); + if (up->uri) { + EMEvent *e = em_event_peek(); + EMEventTargetFolder *t = em_event_target_new_folder(e, up->uri, up->new?EM_EVENT_FOLDER_NEWMAIL:0); + + /* EVENT: folder.changed definition */ + e_event_emit((EEvent *)e, "folder.changed", (EEventTarget *)t); + } + free_update(up); LOCK(info_lock); @@ -357,6 +368,7 @@ update_1folder(struct _folder_info *mfi, int new, CamelFolderInfo *info) up->unread = unread; up->new = new ? 1 : 0; up->store = mfi->store_info->store; + up->uri = g_strdup(mfi->uri); camel_object_ref(up->store); e_dlist_addtail(&updates, (EDListNode *)up); flush_updates(); diff --git a/mail/mail-ops.c b/mail/mail-ops.c index d6351fe435..013eb4a184 100644 --- a/mail/mail-ops.c +++ b/mail/mail-ops.c @@ -36,6 +36,7 @@ #include <errno.h> #include <libgnome/gnome-exec.h> #include <gal/util/e-util.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-mime-filter-from.h> #include <camel/camel-stream-filter.h> diff --git a/mail/mail-send-recv.c b/mail/mail-send-recv.c index 99dbe5620b..bb3af5aa48 100644 --- a/mail/mail-send-recv.c +++ b/mail/mail-send-recv.c @@ -38,6 +38,7 @@ #include <gtk/gtkimage.h> #include <gtk/gtkbox.h> #include <libgnomeui/gnome-window-icon.h> +#include <libgnome/gnome-i18n.h> #include "e-util/e-gtk-utils.h" #include "e-util/e-account-list.h" @@ -761,8 +762,7 @@ GtkWidget *mail_send_receive (void) } struct _auto_data { - char *uri; - int keep; /* keep on server flag */ + EAccount *account; int period; /* in seconds */ int timeout_id; }; @@ -774,85 +774,100 @@ auto_timeout(void *data) { struct _auto_data *info = data; - if (camel_session_is_online(session)) - mail_receive_uri(info->uri, info->keep); + if (camel_session_is_online(session)) { + const char *uri = e_account_get_string(info->account, E_ACCOUNT_SOURCE_URL); + int keep = e_account_get_bool(info->account, E_ACCOUNT_SOURCE_KEEP_ON_SERVER); + + mail_receive_uri(uri, keep); + } return TRUE; } -static void auto_setup_set(void *key, struct _auto_data *info, GHashTable *set) +static void +auto_account_removed(EAccountList *eal, EAccount *ea, void *dummy) { - g_hash_table_insert(set, info->uri, info); + struct _auto_data *info = g_object_get_data((GObject *)ea, "mail-autoreceive"); + + g_return_if_fail(info != NULL); + + if (info->timeout_id) { + g_source_remove(info->timeout_id); + info->timeout_id = 0; + } } -static void auto_clean_set(void *key, struct _auto_data *info, GHashTable *set) +static void +auto_account_finalised(EAccount *ea, struct _auto_data *info) { - d(printf("removing auto-check for %s %p\n", info->uri, info)); - g_hash_table_remove(set, info->uri); - g_source_remove(info->timeout_id); - g_free(info->uri); + if (info->timeout_id) + g_source_remove(info->timeout_id); g_free(info); } +static void +auto_account_commit(struct _auto_data *info) +{ + int period, check; + + check = info->account->enabled + && e_account_get_bool(info->account, E_ACCOUNT_SOURCE_AUTO_CHECK) + && e_account_get_string(info->account, E_ACCOUNT_SOURCE_URL); + period = e_account_get_int(info->account, E_ACCOUNT_SOURCE_AUTO_CHECK_TIME)*60; + period = MAX(60, period); + + if (info->timeout_id + && (!check + || period != info->period)) { + g_source_remove(info->timeout_id); + info->timeout_id = 0; + } + info->period = period; + if (check && info->timeout_id == 0) + info->timeout_id = g_timeout_add(info->period*1000, auto_timeout, info); +} + +static void +auto_account_added(EAccountList *eal, EAccount *ea, void *dummy) +{ + struct _auto_data *info; + + info = g_malloc0(sizeof(*info)); + info->account = ea; + g_object_set_data_full((GObject *)ea, "mail-autoreceive", info, (GDestroyNotify)auto_account_finalised); + auto_account_commit(info); +} + +static void +auto_account_changed(EAccountList *eal, EAccount *ea, void *dummy) +{ + struct _auto_data *info = g_object_get_data((GObject *)ea, "mail-autoreceive"); + + g_return_if_fail(info != NULL); + + auto_account_commit(info); +} + /* call to setup initial, and after changes are made to the config */ /* FIXME: Need a cleanup funciton for when object is deactivated */ void -mail_autoreceive_setup (void) +mail_autoreceive_init(void) { EAccountList *accounts; - GHashTable *set_hash; EIterator *iter; + if (auto_active) + return; + accounts = mail_config_get_accounts (); - - if (auto_active == NULL) - auto_active = g_hash_table_new(g_str_hash, g_str_equal); - - set_hash = g_hash_table_new(g_str_hash, g_str_equal); - g_hash_table_foreach(auto_active, (GHFunc)auto_setup_set, set_hash); - - iter = e_list_get_iterator ((EList *) accounts); - while (e_iterator_is_valid (iter)) { - EAccountService *source; - EAccount *account; - - account = (EAccount *) e_iterator_get (iter); - source = account->source; - - if (account->enabled && source->url && source->auto_check) { - struct _auto_data *info; - - d(printf("setting up auto-receive mail for : %s %dm\n", source->url, source->auto_check_time)); - - g_hash_table_remove(set_hash, source->url); - info = g_hash_table_lookup(auto_active, source->url); - if (info) { - info->keep = source->keep_on_server; - if (info->period != source->auto_check_time*60) { - info->period = MAX(source->auto_check_time*60, 60); - g_source_remove(info->timeout_id); - info->timeout_id = g_timeout_add(info->period*1000, auto_timeout, info); - } - } else { - info = g_malloc0(sizeof(*info)); - info->uri = g_strdup(source->url); - info->keep = source->keep_on_server; - info->period = MAX(source->auto_check_time*60, 60); - info->timeout_id = g_timeout_add(info->period*1000, auto_timeout, info); - g_hash_table_insert(auto_active, info->uri, info); - /* If we do this at startup, it can cause the logon dialog to be hidden, - so lets not */ - /*mail_receive_uri(source->url, source->keep_on_server);*/ - } - } - - e_iterator_next (iter); - } - - g_object_unref (iter); - - g_hash_table_foreach(set_hash, (GHFunc)auto_clean_set, auto_active); - g_hash_table_destroy(set_hash); + auto_active = g_hash_table_new(g_str_hash, g_str_equal); + + g_signal_connect(accounts, "account-added", G_CALLBACK(auto_account_added), NULL); + g_signal_connect(accounts, "account-removed", G_CALLBACK(auto_account_removed), NULL); + g_signal_connect(accounts, "account-changed", G_CALLBACK(auto_account_changed), NULL); + + for (iter = e_list_get_iterator((EList *)accounts);e_iterator_is_valid(iter);e_iterator_next(iter)) + auto_account_added(accounts, (EAccount *)e_iterator_get(iter), NULL); } /* we setup the download info's in a hashtable, if we later need to build the gui, we insert diff --git a/mail/mail-send-recv.h b/mail/mail-send-recv.h index d97f06222e..dab199466a 100644 --- a/mail/mail-send-recv.h +++ b/mail/mail-send-recv.h @@ -36,7 +36,7 @@ GtkWidget *mail_send_receive(void); void mail_receive_uri(const char *uri, int keep); void mail_send (void); /* setup auto receive stuff */ -void mail_autoreceive_setup(void); +void mail_autoreceive_init(void); #ifdef __cplusplus } diff --git a/mail/mail-session.c b/mail/mail-session.c index 12ba0004e1..3109c47c30 100644 --- a/mail/mail-session.c +++ b/mail/mail-session.c @@ -42,6 +42,7 @@ #include <camel/camel.h> /* FIXME: this is where camel_init is defined, it shouldn't include everything else */ #include "camel/camel-filter-driver.h" +#include <camel/camel-i18n.h> #include "em-filter-context.h" #include "em-filter-rule.h" diff --git a/mail/mail-tools.c b/mail/mail-tools.c index 7cf0878a2e..59dd3613ec 100644 --- a/mail/mail-tools.c +++ b/mail/mail-tools.c @@ -36,6 +36,7 @@ #include <gconf/gconf.h> #include <gconf/gconf-client.h> +#include <libgnome/gnome-i18n.h> #include <camel/camel-vee-folder.h> #include <camel/camel-file-utils.h> diff --git a/mail/message-list.c b/mail/message-list.c index 4c0fdff1fd..c34a4ccf1e 100644 --- a/mail/message-list.c +++ b/mail/message-list.c @@ -39,6 +39,7 @@ #include <gtk/gtkmain.h> #include <gtk/gtkinvisible.h> +#include <libgnome/gnome-i18n.h> #include <gal/util/e-util.h> #include <gal/widgets/e-gui-utils.h> @@ -1678,34 +1679,48 @@ ml_drop_action(struct _drop_msg *m) } static void -ml_drop_popup_copy(GtkWidget *item, struct _drop_msg *m) +ml_drop_popup_copy(EPopup *ep, EPopupItem *item, void *data) { + struct _drop_msg *m = data; + m->action = GDK_ACTION_COPY; ml_drop_action(m); } static void -ml_drop_popup_move(GtkWidget *item, struct _drop_msg *m) +ml_drop_popup_move(EPopup *ep, EPopupItem *item, void *data) { + struct _drop_msg *m = data; + m->action = GDK_ACTION_MOVE; ml_drop_action(m); } static void -ml_drop_popup_cancel(GtkWidget *item, struct _drop_msg *m) +ml_drop_popup_cancel(EPopup *ep, EPopupItem *item, void *data) { + struct _drop_msg *m = data; + m->aborted = TRUE; mail_msg_free(&m->msg); } -static EMPopupItem ml_drop_popup_menu[] = { - { EM_POPUP_ITEM, "00.emc.02", N_("_Copy"), G_CALLBACK(ml_drop_popup_copy), NULL, "stock_folder-copy", 0 }, - { EM_POPUP_ITEM, "00.emc.03", N_("_Move"), G_CALLBACK(ml_drop_popup_move), NULL, "stock_folder-move", 0 }, - { EM_POPUP_BAR, "10.emc" }, - { EM_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), G_CALLBACK(ml_drop_popup_cancel), NULL, NULL, 0 }, +static EPopupItem ml_drop_popup_menu[] = { + { E_POPUP_ITEM, "00.emc.02", N_("_Copy"), ml_drop_popup_copy, NULL, "stock_folder-copy", 0 }, + { E_POPUP_ITEM, "00.emc.03", N_("_Move"), ml_drop_popup_move, NULL, "stock_folder-move", 0 }, + { E_POPUP_BAR, "10.emc" }, + { E_POPUP_ITEM, "99.emc.00", N_("Cancel _Drag"), ml_drop_popup_cancel, NULL, NULL, 0 }, }; static void +ml_drop_popup_free(EPopup *ep, GSList *items, void *data) +{ + g_slist_free(items); + + /* FIXME: free data if no item was selected? */ +} + +static void ml_tree_drag_data_received (ETree *tree, int row, ETreePath path, int col, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, @@ -1738,14 +1753,11 @@ ml_tree_drag_data_received (ETree *tree, int row, ETreePath path, int col, int i; emp = em_popup_new("com.ximian.mail.messagelist.popup.drop"); - for (i=0;i<sizeof(ml_drop_popup_menu)/sizeof(ml_drop_popup_menu[0]);i++) { - EMPopupItem *item = &ml_drop_popup_menu[i]; + for (i=0;i<sizeof(ml_drop_popup_menu)/sizeof(ml_drop_popup_menu[0]);i++) + menus = g_slist_append(menus, &ml_drop_popup_menu[i]); - item->activate_data = m; - menus = g_slist_append(menus, item); - } - em_popup_add_items(emp, menus, (GDestroyNotify)g_slist_free); - menu = em_popup_create_menu_once(emp, NULL, 0, 0); + e_popup_add_items((EPopup *)emp, menus, ml_drop_popup_free, m); + menu = e_popup_create_menu_once((EPopup *)emp, NULL, 0, 0); gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time()); } else { ml_drop_action(m); @@ -1848,7 +1860,7 @@ message_list_destroy(GtkObject *object) mail_async_event_destroy(message_list->async_event); message_list->async_event = NULL; } - + if (message_list->folder) { /* need to do this before removing folder, folderinfo's might not exist after */ save_tree_state(message_list); @@ -2738,7 +2750,7 @@ folder_changed (CamelObject *o, gpointer event_data, gpointer user_data) { CamelFolderChangeInfo *changes; MessageList *ml = MESSAGE_LIST (user_data); - + if (event_data) { changes = camel_folder_change_info_new(); camel_folder_change_info_cat(changes, (CamelFolderChangeInfo *)event_data); diff --git a/mail/message-tag-followup.c b/mail/message-tag-followup.c index edc015eb0b..3c456e926e 100644 --- a/mail/message-tag-followup.c +++ b/mail/message-tag-followup.c @@ -54,6 +54,7 @@ #include <gconf/gconf-client.h> #include <libgnomeui/gnome-pixmap.h> +#include <libgnome/gnome-i18n.h> #include "message-tag-followup.h" #include "mail-config.h" diff --git a/shell/ChangeLog b/shell/ChangeLog index 35d157ff3c..c04f5d0863 100644 --- a/shell/ChangeLog +++ b/shell/ChangeLog @@ -94,6 +94,10 @@ * e-shell-errors.xml.h: Add this back to CVS for translators * .cvsignore: Don't ignore e-shell-errors.xml.h +2004-09-07 Not Zed <NotZed@Ximian.com> + + * e-shell.c (e_shell_construct): disable the startup wizard. + 2004-06-24 Not Zed <NotZed@Ximian.com> ** See bug #57367. diff --git a/shell/e-shell.c b/shell/e-shell.c index 0e43ee3fd7..07c7dd9df5 100644 --- a/shell/e-shell.c +++ b/shell/e-shell.c @@ -643,10 +643,12 @@ e_shell_construct (EShell *shell, e_shell_attempt_upgrade(shell); +#if 0 if (e_shell_startup_wizard_create () == FALSE) { bonobo_object_unref (BONOBO_OBJECT (shell)); exit (0); } +#endif priv->is_initialized = TRUE; |