diff options
author | Michael Zucci <zucchi@src.gnome.org> | 2004-09-20 13:59:55 +0800 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2004-09-20 13:59:55 +0800 |
commit | ef6a3af717132e0750f226fa8a0ee0f3c98e19f0 (patch) | |
tree | f4ed25f7a247a8ccb855a8d940777e7a3d21a3e3 /e-util/e-config.c | |
parent | 96111b1f1487ca3fe454b340a73ba927cc6bfb83 (diff) | |
download | gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.gz gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.tar.zst gsoc2013-evolution-ef6a3af717132e0750f226fa8a0ee0f3c98e19f0.zip |
Merged notzed-eplugin-2-branch to head.
svn path=/trunk/; revision=27300
Diffstat (limited to 'e-util/e-config.c')
-rw-r--r-- | e-util/e-config.c | 1415 |
1 files changed, 1415 insertions, 0 deletions
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); +} |