1370 lines
35 KiB
C
1370 lines
35 KiB
C
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*
|
|
* Authors:
|
|
* Michael Zucchi <notzed@ximian.com>
|
|
*
|
|
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "e-config.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
|
|
#define E_CONFIG_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE \
|
|
((obj), E_TYPE_CONFIG, EConfigPrivate))
|
|
|
|
#define d(x)
|
|
|
|
typedef GtkWidget *
|
|
(*EConfigItemSectionFactoryFunc)
|
|
(EConfig *ec,
|
|
EConfigItem *item,
|
|
GtkWidget *parent,
|
|
GtkWidget *old,
|
|
gint position,
|
|
gpointer data,
|
|
GtkWidget **real_frame);
|
|
|
|
struct _EConfigFactory {
|
|
gchar *id;
|
|
EConfigFactoryFunc func;
|
|
gpointer user_data;
|
|
};
|
|
|
|
struct _menu_node {
|
|
GSList *menu;
|
|
EConfigItemsFunc free;
|
|
gpointer data;
|
|
};
|
|
|
|
struct _widget_node {
|
|
EConfig *config;
|
|
|
|
struct _menu_node *context;
|
|
EConfigItem *item;
|
|
GtkWidget *widget; /* widget created by the factory, if any */
|
|
GtkWidget *frame; /* if created by us */
|
|
GtkWidget *real_frame; /* used for sections and section tables, this is the real GtkFrame (whereas "frame" above is the internal vbox/table) */
|
|
|
|
guint empty:1; /* set if empty (i.e. hidden) */
|
|
};
|
|
|
|
struct _check_node {
|
|
gchar *pageid;
|
|
EConfigCheckFunc func;
|
|
gpointer data;
|
|
};
|
|
|
|
struct _finish_page_node {
|
|
gchar *pageid;
|
|
gboolean is_finish;
|
|
gint orig_type;
|
|
};
|
|
|
|
struct _EConfigPrivate {
|
|
GList *menus;
|
|
GList *widgets;
|
|
GList *checks;
|
|
};
|
|
|
|
static GtkWidget *
|
|
config_hook_section_factory (EConfig *config,
|
|
EConfigItem *item,
|
|
GtkWidget *parent,
|
|
GtkWidget *old,
|
|
gint position,
|
|
gpointer data,
|
|
GtkWidget **real_frame);
|
|
|
|
enum {
|
|
ABORT,
|
|
COMMIT,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL];
|
|
|
|
G_DEFINE_TYPE (
|
|
EConfig,
|
|
e_config,
|
|
G_TYPE_OBJECT)
|
|
|
|
static void
|
|
check_node_free (struct _check_node *node)
|
|
{
|
|
g_free (node->pageid);
|
|
|
|
g_slice_free (struct _check_node, node);
|
|
}
|
|
|
|
static void
|
|
config_finalize (GObject *object)
|
|
{
|
|
EConfigPrivate *priv;
|
|
GList *link;
|
|
|
|
priv = E_CONFIG_GET_PRIVATE (object);
|
|
|
|
d (printf ("finalising EConfig %p\n", object));
|
|
|
|
g_free (E_CONFIG (object)->id);
|
|
|
|
link = priv->menus;
|
|
while (link != NULL) {
|
|
struct _menu_node *node = link->data;
|
|
|
|
if (node->free)
|
|
node->free (E_CONFIG (object), node->menu, node->data);
|
|
|
|
g_free (node);
|
|
|
|
link = g_list_delete_link (link, link);
|
|
}
|
|
|
|
link = priv->widgets;
|
|
while (link != NULL) {
|
|
struct _widget_node *node = link->data;
|
|
|
|
/* disconnect the ec_widget_destroyed function from the widget */
|
|
if (node->widget)
|
|
g_signal_handlers_disconnect_matched (
|
|
node->widget, G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL, NULL, node);
|
|
|
|
g_free (node);
|
|
|
|
link = g_list_delete_link (link, link);
|
|
}
|
|
|
|
g_list_free_full (priv->checks, (GDestroyNotify) check_node_free);
|
|
|
|
/* Chain up to parent's finalize() method. */
|
|
G_OBJECT_CLASS (e_config_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
config_target_free (EConfig *config,
|
|
EConfigTarget *target)
|
|
{
|
|
g_free (target);
|
|
g_object_unref (config);
|
|
}
|
|
|
|
static void
|
|
config_set_target (EConfig *config,
|
|
EConfigTarget *target)
|
|
{
|
|
if (config->target != NULL)
|
|
e_config_target_free (config, target);
|
|
|
|
config->target = target;
|
|
}
|
|
|
|
static void
|
|
e_config_class_init (EConfigClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
g_type_class_add_private (class, sizeof (EConfigPrivate));
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->finalize = config_finalize;
|
|
|
|
class->set_target = config_set_target;
|
|
class->target_free = config_target_free;
|
|
|
|
signals[ABORT] = g_signal_new (
|
|
"abort",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (EConfigClass, abort),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
|
|
signals[COMMIT] = g_signal_new (
|
|
"commit",
|
|
G_OBJECT_CLASS_TYPE (object_class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (EConfigClass, commit),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
e_config_init (EConfig *config)
|
|
{
|
|
config->priv = E_CONFIG_GET_PRIVATE (config);
|
|
}
|
|
|
|
/**
|
|
* e_config_construct:
|
|
* @config: The instance to initialise.
|
|
* @id: The name of the configuration window this manager drives.
|
|
*
|
|
* Used by implementing classes to initialise base parameters.
|
|
*
|
|
* Return value: @config is returned.
|
|
**/
|
|
EConfig *
|
|
e_config_construct (EConfig *config,
|
|
const gchar *id)
|
|
{
|
|
config->id = g_strdup (id);
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* e_config_add_items:
|
|
* @config: An initialised implementing instance of EConfig.
|
|
* @items: A list of EConfigItem's to add to the configuration manager.
|
|
* @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 freefunc,
|
|
gpointer data)
|
|
{
|
|
struct _menu_node *node;
|
|
|
|
node = g_malloc (sizeof (*node));
|
|
node->menu = items;
|
|
node->free = freefunc;
|
|
node->data = data;
|
|
|
|
ec->priv->menus = g_list_append (ec->priv->menus, node);
|
|
}
|
|
|
|
/**
|
|
* e_config_add_page_check:
|
|
* @config: Initialised implemeting instance of EConfig.
|
|
* @pageid: pageid to check.
|
|
* @func: 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 assistant to continue or the notebook to close.
|
|
**/
|
|
void
|
|
e_config_add_page_check (EConfig *ec,
|
|
const gchar *pageid,
|
|
EConfigCheckFunc func,
|
|
gpointer data)
|
|
{
|
|
struct _check_node *cn;
|
|
|
|
cn = g_slice_new0 (struct _check_node);
|
|
cn->pageid = g_strdup (pageid);
|
|
cn->func = func;
|
|
cn->data = data;
|
|
|
|
ec->priv->checks = g_list_append (ec->priv->checks, cn);
|
|
}
|
|
|
|
static void
|
|
ec_add_static_items (EConfig *config)
|
|
{
|
|
EConfigClass *class;
|
|
GList *link;
|
|
|
|
class = E_CONFIG_GET_CLASS (config);
|
|
for (link = class->factories; link != NULL; link = link->next) {
|
|
EConfigFactory *factory = link->data;
|
|
|
|
if (factory->id == NULL || strcmp (factory->id, config->id) == 0)
|
|
factory->func (config, factory->user_data);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
ep_cmp (gconstpointer ap,
|
|
gconstpointer bp)
|
|
{
|
|
struct _widget_node *a = *((gpointer *) ap);
|
|
struct _widget_node *b = *((gpointer *) bp);
|
|
|
|
return strcmp (a->item->path, b->item->path);
|
|
}
|
|
|
|
static void
|
|
ec_widget_destroyed (GtkWidget *widget,
|
|
struct _widget_node *node)
|
|
{
|
|
/* Use our own function instead of gtk_widget_destroyed()
|
|
* so it's easier to trap EConfig widgets in a debugger. */
|
|
|
|
node->widget = NULL;
|
|
}
|
|
|
|
static void
|
|
ec_rebuild (EConfig *config)
|
|
{
|
|
EConfigPrivate *p = config->priv;
|
|
struct _widget_node *sectionnode = NULL, *pagenode = NULL;
|
|
GtkWidget *book = NULL, *page = NULL, *section = NULL, *root = NULL;
|
|
gint pageno = 0, sectionno = 0, itemno = 0;
|
|
gint n_visible_widgets = 0;
|
|
GList *link;
|
|
|
|
d (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 */
|
|
|
|
/* because rebuild destroys pages, and destroying active page causes crashes */
|
|
for (link = p->widgets; link != NULL; link = g_list_next (link)) {
|
|
struct _widget_node *wn = link->data;
|
|
struct _EConfigItem *item = wn->item;
|
|
const gchar *translated_label = NULL;
|
|
GtkWidget *w;
|
|
|
|
d (printf (" '%s'\n", item->path));
|
|
|
|
if (item->label != NULL)
|
|
translated_label = gettext (item->label);
|
|
|
|
/* If the last section doesn't contain any visible widgets, hide it */
|
|
if (sectionnode != NULL
|
|
&& sectionnode->frame != NULL
|
|
&& (item->type == E_CONFIG_PAGE
|
|
|| item->type == E_CONFIG_SECTION
|
|
|| item->type == E_CONFIG_SECTION_TABLE)) {
|
|
if ((sectionnode->empty = (itemno == 0 || n_visible_widgets == 0))) {
|
|
if (sectionnode->real_frame)
|
|
gtk_widget_hide (sectionnode->real_frame);
|
|
|
|
if (sectionnode->frame)
|
|
gtk_widget_hide (sectionnode->frame);
|
|
|
|
sectionno--;
|
|
} else {
|
|
if (sectionnode->real_frame)
|
|
gtk_widget_show (sectionnode->real_frame);
|
|
|
|
if (sectionnode->frame)
|
|
gtk_widget_show (sectionnode->frame);
|
|
}
|
|
|
|
d (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
|
|
&& pagenode->frame != NULL
|
|
&& item->type == E_CONFIG_PAGE) {
|
|
if ((pagenode->empty = sectionno == 0)) {
|
|
gtk_widget_hide (pagenode->frame);
|
|
pageno--;
|
|
} else
|
|
gtk_widget_show (pagenode->frame);
|
|
d (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:
|
|
/* This 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 redefined at: %s", item->path);
|
|
break;
|
|
}
|
|
|
|
if (wn->widget == NULL) {
|
|
if (item->factory) {
|
|
root = item->factory (
|
|
config, item, NULL, wn->widget,
|
|
0, wn->context->data);
|
|
} else {
|
|
root = gtk_notebook_new ();
|
|
gtk_widget_show (root);
|
|
}
|
|
|
|
config->widget = root;
|
|
wn->widget = root;
|
|
} else {
|
|
root = wn->widget;
|
|
}
|
|
|
|
book = root;
|
|
|
|
page = NULL;
|
|
pagenode = NULL;
|
|
section = NULL;
|
|
sectionnode = NULL;
|
|
pageno = 0;
|
|
sectionno = 0;
|
|
break;
|
|
case E_CONFIG_PAGE:
|
|
/* The page is a VBox, stored in the notebook. */
|
|
sectionno = 0;
|
|
if (root == NULL) {
|
|
g_warning ("EConfig page defined before container widget: %s", item->path);
|
|
break;
|
|
}
|
|
|
|
if (item->factory) {
|
|
page = item->factory (
|
|
config, item, root, wn->frame,
|
|
pageno, wn->context->data);
|
|
wn->frame = page;
|
|
if (page)
|
|
gtk_notebook_reorder_child ((GtkNotebook *) book, page, pageno);
|
|
if (page)
|
|
sectionno = 1;
|
|
} else if (wn->widget == NULL) {
|
|
w = gtk_label_new_with_mnemonic (translated_label);
|
|
gtk_widget_show (w);
|
|
page = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
|
gtk_container_set_border_width ((GtkContainer *) page, 12);
|
|
gtk_widget_show (page);
|
|
gtk_notebook_insert_page ((GtkNotebook *) book, page, w, pageno);
|
|
gtk_container_child_set (GTK_CONTAINER (book), page, "tab-fill", FALSE, "tab-expand", FALSE, NULL);
|
|
wn->frame = page;
|
|
} else
|
|
page = wn->widget;
|
|
|
|
d (printf ("page %d:%s widget %p\n", pageno, item->path, page));
|
|
|
|
if (wn->widget && wn->widget != page) {
|
|
d (printf ("destroy old widget for page '%s' (%p)\n", item->path, wn->widget));
|
|
gtk_widget_destroy (wn->widget);
|
|
}
|
|
|
|
pageno++;
|
|
pagenode = wn;
|
|
section = NULL;
|
|
sectionnode = NULL;
|
|
wn->widget = page;
|
|
if (page)
|
|
g_signal_connect (
|
|
page, "destroy",
|
|
G_CALLBACK (ec_widget_destroyed), wn);
|
|
break;
|
|
case E_CONFIG_SECTION:
|
|
case E_CONFIG_SECTION_TABLE:
|
|
/* The section factory is always called with
|
|
* the parent vbox object. Even for assistant pages. */
|
|
if (page == NULL) {
|
|
/*g_warning("EConfig section '%s' has no parent page", item->path);*/
|
|
section = NULL;
|
|
wn->widget = NULL;
|
|
wn->frame = NULL;
|
|
goto nopage;
|
|
}
|
|
|
|
itemno = 0;
|
|
n_visible_widgets = 0;
|
|
|
|
d (printf ("Building section %s - '%s' - %s factory\n", item->path, item->label, item->factory ? "with" : "without"));
|
|
|
|
if (item->factory) {
|
|
/* For sections, we pass an extra argument to the usual EConfigItemFactoryFunc.
|
|
* If this is an automatically-generated section, that extra argument (real_frame from
|
|
* EConfigItemSectionFactoryFunc) will contain the actual GtkFrame upon returning.
|
|
*/
|
|
EConfigItemSectionFactoryFunc factory = (EConfigItemSectionFactoryFunc) item->factory;
|
|
|
|
section = factory (
|
|
config, item, page, wn->widget, 0,
|
|
wn->context->data, &wn->real_frame);
|
|
wn->frame = section;
|
|
if (section)
|
|
itemno = 1;
|
|
|
|
if (factory != config_hook_section_factory) {
|
|
/* This means there is a section that came from a user-specified factory,
|
|
* so we don't know what is inside the section. In that case, we increment
|
|
* n_visible_widgets so that the section will not get hidden later (we don't know
|
|
* if the section is empty or not, so we cannot decide to hide it).
|
|
*
|
|
* For automatically-generated sections, we use a special config_hook_section_factory() -
|
|
* see config_hook_construct_item().
|
|
*/
|
|
n_visible_widgets++;
|
|
d (printf (" n_visible_widgets++ because there is a section factory -> frame=%p\n", section));
|
|
}
|
|
|
|
if (section
|
|
&& ((item->type == E_CONFIG_SECTION && !GTK_IS_BOX (section))
|
|
|| (item->type == E_CONFIG_SECTION_TABLE && !GTK_IS_TABLE (section))))
|
|
g_warning ("EConfig section type is wrong");
|
|
} else {
|
|
GtkWidget *frame;
|
|
GtkWidget *label = NULL;
|
|
|
|
if (wn->frame) {
|
|
d (printf ("Item %s, clearing generated section widget\n", wn->item->path));
|
|
gtk_widget_destroy (wn->frame);
|
|
wn->widget = NULL;
|
|
wn->frame = NULL;
|
|
}
|
|
|
|
if (translated_label != NULL) {
|
|
gchar *txt = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>", translated_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_box_new (GTK_ORIENTATION_VERTICAL, 6);
|
|
else {
|
|
section = gtk_table_new (1, 1, FALSE);
|
|
gtk_table_set_col_spacings ((GtkTable *) section, 6);
|
|
gtk_table_set_row_spacings ((GtkTable *) section, 6);
|
|
}
|
|
|
|
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;
|
|
}
|
|
nopage:
|
|
if (wn->widget && wn->widget != section) {
|
|
d (printf ("destroy old widget for section '%s'\n", item->path));
|
|
gtk_widget_destroy (wn->widget);
|
|
}
|
|
|
|
d (printf ("Item %s, setting section widget\n", wn->item->path));
|
|
|
|
sectionno++;
|
|
wn->widget = section;
|
|
if (section)
|
|
g_signal_connect (
|
|
section, "destroy",
|
|
G_CALLBACK (ec_widget_destroyed), wn);
|
|
sectionnode = wn;
|
|
break;
|
|
case E_CONFIG_ITEM:
|
|
case E_CONFIG_ITEM_TABLE:
|
|
/* generated sections never retain their widgets on a rebuild */
|
|
if (sectionnode && sectionnode->item->factory == NULL)
|
|
wn->widget = NULL;
|
|
|
|
/* 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) {
|
|
wn->widget = NULL;
|
|
wn->frame = 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 (
|
|
config, item, section, wn->widget,
|
|
0, wn->context->data);
|
|
|
|
if (wn->widget && wn->widget != w) {
|
|
d (printf ("destroy old widget for item '%s'\n", item->path));
|
|
gtk_widget_destroy (wn->widget);
|
|
}
|
|
|
|
wn->widget = w;
|
|
if (w) {
|
|
g_signal_connect (
|
|
w, "destroy",
|
|
G_CALLBACK (ec_widget_destroyed), wn);
|
|
itemno++;
|
|
|
|
if (gtk_widget_get_visible (w))
|
|
n_visible_widgets++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the last section doesn't contain any visible widgets, hide it */
|
|
if (sectionnode != NULL && sectionnode->frame != NULL) {
|
|
d (printf ("Section %s - %d visible widgets (frame=%p)\n", sectionnode->item->path, n_visible_widgets, sectionnode->frame));
|
|
if ((sectionnode->empty = (itemno == 0 || n_visible_widgets == 0))) {
|
|
if (sectionnode->real_frame)
|
|
gtk_widget_hide (sectionnode->real_frame);
|
|
|
|
if (sectionnode->frame)
|
|
gtk_widget_hide (sectionnode->frame);
|
|
|
|
sectionno--;
|
|
} else {
|
|
if (sectionnode->real_frame)
|
|
gtk_widget_show (sectionnode->real_frame);
|
|
|
|
if (sectionnode->frame)
|
|
gtk_widget_show (sectionnode->frame);
|
|
}
|
|
d (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 && pagenode->frame != NULL) {
|
|
if ((pagenode->empty = sectionno == 0)) {
|
|
gtk_widget_hide (pagenode->frame);
|
|
pageno--;
|
|
} else
|
|
gtk_widget_show (pagenode->frame);
|
|
d (printf ("%s page '%s' [section=%d]\n", pagenode->empty?"hiding":"showing", pagenode->item->path, pageno));
|
|
}
|
|
|
|
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:
|
|
* @config: An initialised EConfig.
|
|
* @target: A target allocated from @config.
|
|
*
|
|
* 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 *config,
|
|
EConfigTarget *target)
|
|
{
|
|
if (config->target != target)
|
|
E_CONFIG_GET_CLASS (config)->set_target (config, 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:
|
|
* @config: An initialised EConfig object.
|
|
*
|
|
* Create the #GtkNotebook described by @config.
|
|
*
|
|
* This object will be self-driving, but will not close itself once
|
|
* complete.
|
|
*
|
|
* Unless reffed otherwise, the management object @config will be
|
|
* finalized when the widget is.
|
|
*
|
|
* Return value: The widget, also available in @config.widget
|
|
**/
|
|
GtkWidget *
|
|
e_config_create_widget (EConfig *config)
|
|
{
|
|
EConfigPrivate *p = config->priv;
|
|
GPtrArray *items = g_ptr_array_new ();
|
|
GList *link;
|
|
GSList *l;
|
|
gint i;
|
|
|
|
g_return_val_if_fail (config->target != NULL, NULL);
|
|
|
|
ec_add_static_items (config);
|
|
|
|
/* FIXME: need to override old ones with new names */
|
|
link = p->menus;
|
|
while (link != NULL) {
|
|
struct _menu_node *mnode = link->data;
|
|
|
|
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 = config;
|
|
g_ptr_array_add (items, wn);
|
|
}
|
|
|
|
link = g_list_next (link);
|
|
}
|
|
|
|
qsort (items->pdata, items->len, sizeof (items->pdata[0]), ep_cmp);
|
|
|
|
for (i = 0; i < items->len; i++)
|
|
p->widgets = g_list_append (p->widgets, items->pdata[i]);
|
|
|
|
g_ptr_array_free (items, TRUE);
|
|
ec_rebuild (config);
|
|
|
|
/* auto-unref it */
|
|
g_signal_connect (
|
|
config->widget, "destroy",
|
|
G_CALLBACK (ec_widget_destroy), config);
|
|
|
|
/* FIXME: for some reason ec_rebuild puts the widget on page 1, this is just to override that */
|
|
gtk_notebook_set_current_page ((GtkNotebook *) config->widget, 0);
|
|
|
|
return config->widget;
|
|
}
|
|
|
|
static void
|
|
ec_call_page_check (EConfig *config)
|
|
{
|
|
if (config->window) {
|
|
if (e_config_page_check (config, NULL)) {
|
|
gtk_dialog_set_response_sensitive ((GtkDialog *) config->window, GTK_RESPONSE_OK, TRUE);
|
|
} else {
|
|
gtk_dialog_set_response_sensitive ((GtkDialog *) config->window, GTK_RESPONSE_OK, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ec_idle_handler_for_rebuild (gpointer data)
|
|
{
|
|
EConfig *config = (EConfig *) data;
|
|
|
|
ec_rebuild (config);
|
|
ec_call_page_check (config);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* e_config_target_changed:
|
|
* @config: an #EConfig
|
|
* @how: an enum value indicating how the target has changed
|
|
*
|
|
* 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 Assistant next/back buttons and the Apply
|
|
* button for the Notebook mode.
|
|
**/
|
|
void
|
|
e_config_target_changed (EConfig *config,
|
|
e_config_target_change_t how)
|
|
{
|
|
if (how == E_CONFIG_TARGET_CHANGED_REBUILD) {
|
|
g_idle_add (ec_idle_handler_for_rebuild, config);
|
|
} else {
|
|
ec_call_page_check (config);
|
|
}
|
|
|
|
/* virtual method/signal? */
|
|
}
|
|
|
|
/**
|
|
* e_config_abort:
|
|
* @config: an #EConfig
|
|
*
|
|
* Signify that the stateful configuration changes must be discarded
|
|
* to all listeners. This is used by self-driven assistant or notebook, or
|
|
* may be used by code using the widget directly.
|
|
**/
|
|
void
|
|
e_config_abort (EConfig *config)
|
|
{
|
|
g_return_if_fail (E_IS_CONFIG (config));
|
|
|
|
g_signal_emit (config, signals[ABORT], 0);
|
|
}
|
|
|
|
/**
|
|
* e_config_commit:
|
|
* @config: an #EConfig
|
|
*
|
|
* Signify that the stateful configuration changes should be saved.
|
|
* This is used by the self-driven assistant or notebook, or may be used
|
|
* by code driving the widget directly.
|
|
**/
|
|
void
|
|
e_config_commit (EConfig *config)
|
|
{
|
|
g_return_if_fail (E_IS_CONFIG (config));
|
|
|
|
g_signal_emit (config, signals[COMMIT], 0);
|
|
}
|
|
|
|
/**
|
|
* e_config_page_check:
|
|
* @config: an #EConfig
|
|
* @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 *config,
|
|
const gchar *pageid)
|
|
{
|
|
GList *link;
|
|
|
|
link = config->priv->checks;
|
|
|
|
while (link != NULL) {
|
|
struct _check_node *node = link->data;
|
|
|
|
if ((pageid == NULL
|
|
|| node->pageid == NULL
|
|
|| strcmp (node->pageid, pageid) == 0)
|
|
&& !node->func (config, pageid, node->data)) {
|
|
return FALSE;
|
|
}
|
|
|
|
link = g_list_next (link);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* ********************************************************************** */
|
|
|
|
/**
|
|
* 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.
|
|
* @user_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 gchar *id,
|
|
EConfigFactoryFunc func,
|
|
gpointer user_data)
|
|
{
|
|
EConfigFactory *factory;
|
|
|
|
g_return_val_if_fail (E_IS_CONFIG_CLASS (klass), NULL);
|
|
g_return_val_if_fail (func != NULL, NULL);
|
|
|
|
factory = g_slice_new0 (EConfigFactory);
|
|
factory->id = g_strdup (id);
|
|
factory->func = func;
|
|
factory->user_data = user_data;
|
|
|
|
klass->factories = g_list_append (klass->factories, factory);
|
|
|
|
return factory;
|
|
}
|
|
|
|
/**
|
|
* e_config_target_new:
|
|
* @config: an #EConfig
|
|
* @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.
|
|
**/
|
|
gpointer
|
|
e_config_target_new (EConfig *config,
|
|
gint type,
|
|
gsize size)
|
|
{
|
|
EConfigTarget *target;
|
|
|
|
if (size < sizeof (EConfigTarget)) {
|
|
g_warning ("Size is less than size of EConfigTarget\n");
|
|
size = sizeof (EConfigTarget);
|
|
}
|
|
|
|
target = g_malloc0 (size);
|
|
target->config = g_object_ref (config);
|
|
target->type = type;
|
|
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* e_config_target_free:
|
|
* @config: an #EConfig
|
|
* @target: the target to free
|
|
*
|
|
* Free a target. The implementing class can override this method to
|
|
* free custom targets.
|
|
**/
|
|
void
|
|
e_config_target_free (EConfig *config,
|
|
gpointer target)
|
|
{
|
|
E_CONFIG_GET_CLASS (config)->target_free (
|
|
config, (EConfigTarget *) target);
|
|
}
|
|
|
|
/* ********************************************************************** */
|
|
|
|
/* Config menu plugin handler */
|
|
|
|
/*
|
|
* <e-plugin
|
|
* class="org.gnome.mail.plugin.config:1.0"
|
|
* id="org.gnome.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="org.gnome.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>
|
|
* </e-plugin>
|
|
*/
|
|
|
|
static const EPluginHookTargetKey config_hook_item_types[] = {
|
|
{ "book", E_CONFIG_BOOK },
|
|
|
|
{ "page", E_CONFIG_PAGE },
|
|
{ "section", E_CONFIG_SECTION },
|
|
{ "section_table", E_CONFIG_SECTION_TABLE },
|
|
{ "item", E_CONFIG_ITEM },
|
|
{ "item_table", E_CONFIG_ITEM_TABLE },
|
|
{ NULL },
|
|
};
|
|
|
|
G_DEFINE_TYPE (
|
|
EConfigHook,
|
|
e_config_hook,
|
|
E_TYPE_PLUGIN_HOOK)
|
|
|
|
static void
|
|
config_hook_commit (EConfig *ec,
|
|
EConfigHookGroup *group)
|
|
{
|
|
if (group->commit && group->hook->hook.plugin->enabled)
|
|
e_plugin_invoke (group->hook->hook.plugin, group->commit, ec->target);
|
|
}
|
|
|
|
static void
|
|
config_hook_abort (EConfig *ec,
|
|
EConfigHookGroup *group)
|
|
{
|
|
if (group->abort && group->hook->hook.plugin->enabled)
|
|
e_plugin_invoke (group->hook->hook.plugin, group->abort, ec->target);
|
|
}
|
|
|
|
static gboolean
|
|
config_hook_check (EConfig *ec,
|
|
const gchar *pageid,
|
|
gpointer data)
|
|
{
|
|
EConfigHookGroup *group = data;
|
|
EConfigHookPageCheckData hdata;
|
|
|
|
if (!group->hook->hook.plugin->enabled)
|
|
return TRUE;
|
|
|
|
hdata.config = ec;
|
|
hdata.target = ec->target;
|
|
hdata.pageid = pageid ? pageid:"";
|
|
|
|
return GPOINTER_TO_INT (e_plugin_invoke (group->hook->hook.plugin, group->check, &hdata));
|
|
}
|
|
|
|
static void
|
|
config_hook_factory (EConfig *config,
|
|
gpointer data)
|
|
{
|
|
EConfigHookGroup *group = data;
|
|
|
|
d (printf ("config factory called %s\n", group->id ? group->id:"all menus"));
|
|
|
|
if (config->target->type != group->target_type
|
|
|| !group->hook->hook.plugin->enabled)
|
|
return;
|
|
|
|
if (group->items) {
|
|
e_config_add_items (config, group->items, NULL, group);
|
|
g_signal_connect (
|
|
config, "abort",
|
|
G_CALLBACK (config_hook_abort), group);
|
|
g_signal_connect (
|
|
config, "commit",
|
|
G_CALLBACK (config_hook_commit), group);
|
|
}
|
|
|
|
if (group->check)
|
|
e_config_add_page_check (config, NULL, config_hook_check, group);
|
|
}
|
|
|
|
static void
|
|
config_hook_free_item (struct _EConfigItem *item)
|
|
{
|
|
g_free (item->path);
|
|
g_free (item->label);
|
|
g_free (item->user_data);
|
|
g_free (item);
|
|
}
|
|
|
|
static void
|
|
config_hook_free_group (EConfigHookGroup *group)
|
|
{
|
|
g_slist_foreach (group->items, (GFunc) config_hook_free_item, NULL);
|
|
g_slist_free (group->items);
|
|
|
|
g_free (group->id);
|
|
g_free (group);
|
|
}
|
|
|
|
static GtkWidget *
|
|
config_hook_widget_factory (EConfig *config,
|
|
EConfigItem *item,
|
|
GtkWidget *parent,
|
|
GtkWidget *old,
|
|
gint position,
|
|
gpointer data)
|
|
{
|
|
EConfigHookGroup *group = data;
|
|
EConfigHookItemFactoryData factory_data;
|
|
EPlugin *plugin;
|
|
|
|
factory_data.config = config;
|
|
factory_data.item = item;
|
|
factory_data.target = config->target;
|
|
factory_data.parent = parent;
|
|
factory_data.old = old;
|
|
factory_data.position = position;
|
|
|
|
plugin = group->hook->hook.plugin;
|
|
return e_plugin_invoke (plugin, item->user_data, &factory_data);
|
|
}
|
|
|
|
static GtkWidget *
|
|
config_hook_section_factory (EConfig *config,
|
|
EConfigItem *item,
|
|
GtkWidget *parent,
|
|
GtkWidget *old,
|
|
gint position,
|
|
gpointer data,
|
|
GtkWidget **real_frame)
|
|
{
|
|
EConfigHookGroup *group = data;
|
|
GtkWidget *label = NULL;
|
|
GtkWidget *widget;
|
|
EPlugin *plugin;
|
|
|
|
if (item->label != NULL) {
|
|
const gchar *translated;
|
|
gchar *markup;
|
|
|
|
translated = gettext (item->label);
|
|
markup = g_markup_printf_escaped ("<b>%s</b>", translated);
|
|
|
|
label = gtk_label_new (markup);
|
|
gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
|
|
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
|
|
gtk_widget_show (label);
|
|
|
|
g_free (markup);
|
|
}
|
|
|
|
widget = gtk_frame_new (NULL);
|
|
gtk_frame_set_label_widget (GTK_FRAME (widget), label);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_NONE);
|
|
gtk_box_pack_start (GTK_BOX (parent), widget, FALSE, FALSE, 0);
|
|
|
|
*real_frame = widget;
|
|
|
|
/* This is why we have a custom factory for sections.
|
|
* When the plugin is disabled the frame is invisible. */
|
|
plugin = group->hook->hook.plugin;
|
|
g_object_bind_property (
|
|
plugin, "enabled",
|
|
widget, "visible",
|
|
G_BINDING_SYNC_CREATE);
|
|
|
|
parent = widget;
|
|
|
|
widget = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
|
|
gtk_alignment_set_padding (GTK_ALIGNMENT (widget), 6, 0, 12, 0);
|
|
gtk_container_add (GTK_CONTAINER (parent), widget);
|
|
gtk_widget_show (widget);
|
|
|
|
parent = widget;
|
|
|
|
switch (item->type) {
|
|
case E_CONFIG_SECTION:
|
|
widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
|
|
break;
|
|
|
|
case E_CONFIG_SECTION_TABLE:
|
|
widget = gtk_table_new (1, 1, FALSE);
|
|
gtk_table_set_col_spacings (GTK_TABLE (widget), 6);
|
|
gtk_table_set_row_spacings (GTK_TABLE (widget), 6);
|
|
break;
|
|
|
|
default:
|
|
g_return_val_if_reached (NULL);
|
|
}
|
|
|
|
gtk_container_add (GTK_CONTAINER (parent), widget);
|
|
gtk_widget_show (widget);
|
|
|
|
return widget;
|
|
}
|
|
|
|
static struct _EConfigItem *
|
|
config_hook_construct_item (EPluginHook *eph,
|
|
EConfigHookGroup *menu,
|
|
xmlNodePtr root,
|
|
EConfigHookTargetMap *map)
|
|
{
|
|
struct _EConfigItem *item;
|
|
|
|
d (printf (" loading config item\n"));
|
|
item = g_malloc0 (sizeof (*item));
|
|
if ((item->type = e_plugin_hook_id (root, config_hook_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 = config_hook_widget_factory;
|
|
else if (item->type == E_CONFIG_SECTION)
|
|
item->factory = (EConfigItemFactoryFunc) config_hook_section_factory;
|
|
else if (item->type == E_CONFIG_SECTION_TABLE)
|
|
item->factory = (EConfigItemFactoryFunc) config_hook_section_factory;
|
|
|
|
d (printf (" path=%s label=%s factory=%s\n", item->path, item->label, (gchar *) item->user_data));
|
|
|
|
return item;
|
|
error:
|
|
d (printf ("error!\n"));
|
|
config_hook_free_item (item);
|
|
return NULL;
|
|
}
|
|
|
|
static EConfigHookGroup *
|
|
config_hook_construct_menu (EPluginHook *eph,
|
|
xmlNodePtr root)
|
|
{
|
|
EConfigHookGroup *menu;
|
|
xmlNodePtr node;
|
|
EConfigHookTargetMap *map;
|
|
EConfigHookClass *class;
|
|
gchar *tmp;
|
|
|
|
class = E_CONFIG_HOOK_GET_CLASS (eph);
|
|
|
|
d (printf (" loading menu\n"));
|
|
menu = g_malloc0 (sizeof (*menu));
|
|
|
|
tmp = (gchar *) xmlGetProp (root, (const guchar *)"target");
|
|
if (tmp == NULL)
|
|
goto error;
|
|
map = g_hash_table_lookup (class->target_map, tmp);
|
|
xmlFree (tmp);
|
|
if (map == NULL)
|
|
goto error;
|
|
|
|
menu->target_type = map->id;
|
|
menu->id = e_plugin_xml_prop (root, "id");
|
|
if (menu->id == NULL) {
|
|
g_warning (
|
|
"Plugin '%s' missing 'id' field in group for '%s'\n",
|
|
eph->plugin->name,
|
|
E_PLUGIN_HOOK_CLASS (class)->id);
|
|
goto error;
|
|
}
|
|
menu->check = e_plugin_xml_prop (root, "check");
|
|
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 ((gchar *) node->name, "item")) {
|
|
struct _EConfigItem *item;
|
|
|
|
item = config_hook_construct_item (eph, menu, node, map);
|
|
if (item)
|
|
menu->items = g_slist_append (menu->items, item);
|
|
}
|
|
node = node->next;
|
|
}
|
|
|
|
return menu;
|
|
error:
|
|
config_hook_free_group (menu);
|
|
return NULL;
|
|
}
|
|
|
|
static gint
|
|
config_hook_construct (EPluginHook *eph,
|
|
EPlugin *ep,
|
|
xmlNodePtr root)
|
|
{
|
|
xmlNodePtr node;
|
|
EConfigClass *class;
|
|
EConfigHook *config_hook;
|
|
|
|
config_hook = (EConfigHook *) eph;
|
|
|
|
d (printf ("loading config hook\n"));
|
|
|
|
if (((EPluginHookClass *) e_config_hook_parent_class)->construct (eph, ep, root) == -1)
|
|
return -1;
|
|
|
|
class = E_CONFIG_HOOK_GET_CLASS (eph)->config_class;
|
|
|
|
node = root->children;
|
|
while (node) {
|
|
if (strcmp ((gchar *) node->name, "group") == 0) {
|
|
EConfigHookGroup *group;
|
|
|
|
group = config_hook_construct_menu (eph, node);
|
|
if (group) {
|
|
e_config_class_add_factory (class, group->id, config_hook_factory, group);
|
|
config_hook->groups = g_slist_append (config_hook->groups, group);
|
|
}
|
|
}
|
|
node = node->next;
|
|
}
|
|
|
|
eph->plugin = ep;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
config_hook_finalize (GObject *object)
|
|
{
|
|
EConfigHook *config_hook = (EConfigHook *) object;
|
|
|
|
g_slist_free_full (
|
|
config_hook->groups,
|
|
(GDestroyNotify) config_hook_free_group);
|
|
|
|
/* Chain up to parent's finalize() method. */
|
|
G_OBJECT_CLASS (e_config_hook_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
e_config_hook_class_init (EConfigHookClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
EPluginHookClass *plugin_hook_class;
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->finalize = config_hook_finalize;
|
|
|
|
plugin_hook_class = E_PLUGIN_HOOK_CLASS (class);
|
|
plugin_hook_class->construct = config_hook_construct;
|
|
|
|
/* this is actually an abstract implementation but list it anyway */
|
|
plugin_hook_class->id = "org.gnome.evolution.config:1.0";
|
|
|
|
class->target_map = g_hash_table_new (g_str_hash, g_str_equal);
|
|
class->config_class = g_type_class_ref (e_config_get_type ());
|
|
}
|
|
|
|
static void
|
|
e_config_hook_init (EConfigHook *hook)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* e_config_hook_class_add_target_map:
|
|
*
|
|
* @hook_class: 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 *hook_class,
|
|
const EConfigHookTargetMap *map)
|
|
{
|
|
g_hash_table_insert (
|
|
hook_class->target_map,
|
|
(gpointer) map->type, (gpointer) map);
|
|
}
|