1652 lines
44 KiB
C
1652 lines
44 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 d(x)
|
|
|
|
struct _EConfigFactory {
|
|
struct _EConfigFactory *next, *prev;
|
|
|
|
gchar *id;
|
|
EConfigFactoryFunc factory;
|
|
gpointer factory_data;
|
|
};
|
|
|
|
struct _menu_node {
|
|
struct _menu_node *next, *prev;
|
|
|
|
GSList *menu;
|
|
EConfigItemsFunc free;
|
|
EConfigItemsFunc abort;
|
|
EConfigItemsFunc commit;
|
|
gpointer data;
|
|
};
|
|
|
|
struct _widget_node {
|
|
struct _widget_node *next, *prev;
|
|
|
|
EConfig *config;
|
|
|
|
struct _menu_node *context;
|
|
EConfigItem *item;
|
|
GtkWidget *widget; /* widget created by the factory, if any */
|
|
GtkWidget *frame; /* if created by us */
|
|
|
|
guint empty:1; /* set if empty (i.e. hidden) */
|
|
};
|
|
|
|
struct _check_node {
|
|
struct _check_node *next, *prev;
|
|
|
|
gchar *pageid;
|
|
EConfigCheckFunc check;
|
|
gpointer data;
|
|
};
|
|
|
|
struct _finish_page_node {
|
|
struct _finish_page_node *next, *prev;
|
|
|
|
gchar *pageid;
|
|
gboolean is_finish;
|
|
gint orig_type;
|
|
};
|
|
|
|
struct _EConfigPrivate {
|
|
EDList menus;
|
|
EDList widgets;
|
|
EDList checks;
|
|
EDList finish_pages;
|
|
};
|
|
|
|
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);
|
|
e_dlist_init(&p->finish_pages);
|
|
}
|
|
|
|
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;
|
|
struct _finish_page_node *fp;
|
|
|
|
d(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)) ) {
|
|
/* disconnect the gtk_widget_destroyed function from the widget */
|
|
if (wn->widget)
|
|
g_signal_handlers_disconnect_matched (wn->widget, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, &wn->widget);
|
|
|
|
g_free(wn);
|
|
}
|
|
|
|
while ( (cn = (struct _check_node *)e_dlist_remhead(&p->checks)) ) {
|
|
g_free(cn->pageid);
|
|
g_free(cn);
|
|
}
|
|
|
|
while ( (fp = (struct _finish_page_node *) e_dlist_remhead (&p->finish_pages)) ) {
|
|
g_free (fp->pageid);
|
|
g_free (fp);
|
|
}
|
|
|
|
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)
|
|
{
|
|
d(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_ASSISTANT.
|
|
* @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, gint type, const gchar *id)
|
|
{
|
|
g_return_val_if_fail (type == E_CONFIG_BOOK || type == E_CONFIG_ASSISTANT, NULL);
|
|
|
|
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, gpointer 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 assistant to continue or the notebook to close.
|
|
**/
|
|
void
|
|
e_config_add_page_check(EConfig *ec, const gchar *pageid, EConfigCheckFunc check, gpointer 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 struct _finish_page_node *
|
|
find_page_finish (EConfig *ec, const gchar *pageid)
|
|
{
|
|
struct _finish_page_node *fp;
|
|
|
|
for (fp = (struct _finish_page_node *) ec->priv->finish_pages.head; fp->next; fp = fp->next) {
|
|
if (g_str_equal (fp->pageid, pageid))
|
|
return fp;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* e_config_set_page_is_finish:
|
|
* @ec: Initialised implementing instance of EConfig.
|
|
* @pageid: pageid to change the value on.
|
|
* @can_finish: whether the pageid can finish immediately or not.
|
|
*
|
|
* With is_finish set on the pageid the page is treated as the last page in an assistant.
|
|
**/
|
|
void
|
|
e_config_set_page_is_finish (EConfig *ec, const gchar *pageid, gboolean is_finish)
|
|
{
|
|
struct _finish_page_node *fp;
|
|
|
|
fp = find_page_finish (ec, pageid);
|
|
|
|
if (is_finish) {
|
|
if (!fp) {
|
|
fp = g_malloc0 (sizeof (*fp));
|
|
fp->pageid = g_strdup (pageid);
|
|
e_dlist_addtail (&ec->priv->finish_pages, (EDListNode *)fp);
|
|
}
|
|
|
|
fp->is_finish = TRUE;
|
|
} else {
|
|
if (fp)
|
|
fp->is_finish = FALSE;
|
|
}
|
|
}
|
|
|
|
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 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 struct _widget_node *
|
|
ec_assistant_find_page (EConfig *ec, GtkWidget *page, gint *page_index)
|
|
{
|
|
struct _widget_node *wn;
|
|
|
|
g_return_val_if_fail (ec != NULL, NULL);
|
|
g_return_val_if_fail (GTK_IS_ASSISTANT (ec->widget), NULL);
|
|
g_return_val_if_fail (page != NULL, NULL);
|
|
|
|
for (wn = (struct _widget_node *)ec->priv->widgets.head; wn->next; wn = wn->next) {
|
|
if (wn->frame == page
|
|
&& (wn->item->type == E_CONFIG_PAGE
|
|
|| wn->item->type == E_CONFIG_PAGE_START
|
|
|| wn->item->type == E_CONFIG_PAGE_FINISH))
|
|
break;
|
|
}
|
|
|
|
if (wn->frame != page)
|
|
wn = NULL;
|
|
|
|
if (page_index) {
|
|
if (wn) {
|
|
GtkAssistant *assistant = GTK_ASSISTANT (ec->widget);
|
|
gint index, count = gtk_assistant_get_n_pages (assistant);
|
|
|
|
for (index = 0; index < count; index++) {
|
|
if (gtk_assistant_get_nth_page (assistant, index) == page)
|
|
break;
|
|
}
|
|
|
|
if (index == count)
|
|
index = -1;
|
|
*page_index = index;
|
|
} else {
|
|
*page_index = -1;
|
|
}
|
|
}
|
|
|
|
return wn;
|
|
}
|
|
|
|
static void
|
|
ec_assistant_check_current (EConfig *ec)
|
|
{
|
|
struct _widget_node *wn;
|
|
struct _finish_page_node *fp;
|
|
GtkAssistant *assistant;
|
|
GtkWidget *page;
|
|
gint page_no;
|
|
|
|
g_return_if_fail (GTK_IS_ASSISTANT (ec->widget));
|
|
|
|
assistant = GTK_ASSISTANT (ec->widget);
|
|
page_no = gtk_assistant_get_current_page (assistant);
|
|
|
|
/* no page selected yet */
|
|
if (page_no == -1)
|
|
return;
|
|
|
|
page = gtk_assistant_get_nth_page (assistant, page_no);
|
|
g_return_if_fail (page != NULL);
|
|
|
|
wn = ec_assistant_find_page (ec, page, NULL);
|
|
g_return_if_fail (wn != NULL);
|
|
|
|
/* this should come first, as the check function can change the finish state of the page */
|
|
gtk_assistant_set_page_complete (assistant, page, e_config_page_check (ec, wn->item->path));
|
|
|
|
fp = find_page_finish (ec, wn->item->path);
|
|
if (fp) {
|
|
GtkAssistantPageType pt = gtk_assistant_get_page_type (assistant, page);
|
|
|
|
if (fp->is_finish && pt != GTK_ASSISTANT_PAGE_CONFIRM) {
|
|
if (fp->orig_type == GTK_ASSISTANT_PAGE_CONTENT)
|
|
fp->orig_type = pt;
|
|
gtk_assistant_set_page_type (assistant, page, GTK_ASSISTANT_PAGE_CONFIRM);
|
|
} else if (!fp->is_finish && pt != fp->orig_type) {
|
|
gtk_assistant_set_page_type (assistant, page, fp->orig_type);
|
|
}
|
|
}
|
|
|
|
gtk_assistant_update_buttons_state (assistant);
|
|
}
|
|
|
|
static gint
|
|
ec_assistant_forward (gint current_page, gpointer user_data)
|
|
{
|
|
EConfig *ec = user_data;
|
|
struct _widget_node *wn;
|
|
gint next_page = current_page + 1;
|
|
|
|
d(printf("next page from '%d'\n", current_page));
|
|
|
|
wn = ec_assistant_find_page (ec, gtk_assistant_get_nth_page (GTK_ASSISTANT (ec->widget), current_page), NULL);
|
|
|
|
if (wn && wn->next) {
|
|
for (wn = wn->next; wn->next; wn = wn->next) {
|
|
if (!wn->empty && wn->frame != NULL
|
|
&& (wn->item->type == E_CONFIG_PAGE
|
|
|| wn->item->type == E_CONFIG_PAGE_START
|
|
|| wn->item->type == E_CONFIG_PAGE_FINISH))
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (wn && wn->next) {
|
|
ec_assistant_find_page (ec, wn->frame, &next_page);
|
|
d(printf(" is %s (%d)\n",wn->item->path, next_page));
|
|
}
|
|
|
|
return next_page;
|
|
}
|
|
|
|
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, *assistant = NULL;
|
|
gint pageno = 0, sectionno = 0, itemno = 0;
|
|
struct _widget_node *last_active_page = NULL;
|
|
gboolean is_assistant;
|
|
|
|
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 */
|
|
is_assistant = emp->widget && GTK_IS_ASSISTANT (emp->widget);
|
|
if (is_assistant) {
|
|
gint page_index = gtk_assistant_get_current_page (GTK_ASSISTANT (emp->widget));
|
|
if (page_index != -1)
|
|
last_active_page = ec_assistant_find_page (emp, gtk_assistant_get_nth_page (GTK_ASSISTANT (emp->widget), page_index), NULL);
|
|
gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), 0);
|
|
}
|
|
|
|
for (wn = (struct _widget_node *)p->widgets.head;wn->next;wn=wn->next) {
|
|
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 anything, hide it */
|
|
if (sectionnode != NULL
|
|
&& sectionnode->frame != 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);
|
|
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_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);
|
|
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:
|
|
case E_CONFIG_ASSISTANT:
|
|
/* Only one of BOOK or ASSISTANT 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/assistant redefined at: %s", item->path);
|
|
break;
|
|
}
|
|
|
|
if (wn->widget == NULL) {
|
|
if (item->type != emp->type) {
|
|
g_warning("EConfig book/assistant 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 = gtk_notebook_new();
|
|
gtk_widget_show (root);
|
|
} else if (item->type == E_CONFIG_ASSISTANT) {
|
|
root = gtk_assistant_new ();
|
|
} else
|
|
abort();
|
|
|
|
if (item->type == E_CONFIG_ASSISTANT) {
|
|
g_signal_connect_swapped (
|
|
root, "apply",
|
|
G_CALLBACK (e_config_commit), emp);
|
|
g_signal_connect_swapped (
|
|
root, "cancel",
|
|
G_CALLBACK (e_config_abort), emp);
|
|
g_signal_connect (
|
|
root, "cancel",
|
|
G_CALLBACK (gtk_widget_destroy), emp);
|
|
g_signal_connect (
|
|
root, "close",
|
|
G_CALLBACK (gtk_widget_destroy), NULL);
|
|
g_signal_connect_swapped (
|
|
root, "prepare",
|
|
G_CALLBACK (ec_assistant_check_current), emp);
|
|
gtk_assistant_set_forward_page_func (
|
|
GTK_ASSISTANT (root),
|
|
ec_assistant_forward, emp, NULL);
|
|
}
|
|
|
|
emp->widget = root;
|
|
wn->widget = root;
|
|
} else {
|
|
root = wn->widget;
|
|
}
|
|
|
|
if (item->type == E_CONFIG_BOOK)
|
|
book = root;
|
|
else
|
|
assistant = 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_ASSISTANT) {
|
|
g_warning("EConfig assistant start/finish pages can't be used on E_CONFIG_BOOKs");
|
|
break;
|
|
}
|
|
|
|
if (item->type == E_CONFIG_PAGE_FINISH || wn->widget == NULL) {
|
|
/* always rebuild finish page, to have it as the last page */
|
|
if (item->factory) {
|
|
page = item->factory(emp, item, root, wn->frame, wn->context->data);
|
|
} else {
|
|
page = gtk_vbox_new (FALSE, 0);
|
|
gtk_container_set_border_width (GTK_CONTAINER (page), 12);
|
|
if (pagenode) {
|
|
/* put after */
|
|
gint index = -1;
|
|
ec_assistant_find_page (emp, pagenode->frame, &index);
|
|
gtk_assistant_insert_page (GTK_ASSISTANT (assistant), page, index + 1);
|
|
} else {
|
|
gtk_assistant_prepend_page (GTK_ASSISTANT (assistant), page);
|
|
}
|
|
|
|
gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), page, item->type == E_CONFIG_PAGE_START ? GTK_ASSISTANT_PAGE_INTRO : GTK_ASSISTANT_PAGE_CONFIRM);
|
|
gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), page, translated_label);
|
|
gtk_widget_show_all (page);
|
|
}
|
|
|
|
if (wn->widget != NULL && wn->widget != page) {
|
|
gtk_widget_destroy (wn->widget);
|
|
}
|
|
|
|
wn->frame = page;
|
|
wn->widget = page;
|
|
|
|
if (page) {
|
|
const gchar *empty_xpm_img[] = {
|
|
"75 1 2 1",
|
|
" c None",
|
|
". c #FFFFFF",
|
|
" "};
|
|
|
|
/* left side place with a blue background on a start and finish page */
|
|
GdkPixbuf *spacer = gdk_pixbuf_new_from_xpm_data (empty_xpm_img);
|
|
|
|
gtk_assistant_set_page_side_image (GTK_ASSISTANT (assistant), page, spacer);
|
|
|
|
g_object_unref (spacer);
|
|
}
|
|
}
|
|
|
|
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: {
|
|
/* CONFIG_PAGEs depend on the config type.
|
|
E_CONFIG_BOOK:
|
|
The page is a VBox, stored in the notebook.
|
|
E_CONFIG_ASSISTANT
|
|
The page is a VBox, stored in the GtkAssistant,
|
|
any sections automatically added 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_ASSISTANT) {
|
|
wn->frame = page;
|
|
} else {
|
|
wn->frame = page;
|
|
if (page)
|
|
gtk_notebook_reorder_child((GtkNotebook *)book, page, pageno);
|
|
}
|
|
if (page)
|
|
sectionno = 1;
|
|
} else if (wn->widget == NULL) {
|
|
if (emp->type == E_CONFIG_ASSISTANT) {
|
|
page = gtk_vbox_new (FALSE, 0);
|
|
gtk_container_set_border_width (GTK_CONTAINER (page), 12);
|
|
if (pagenode) {
|
|
/* put after */
|
|
gint index = -1;
|
|
ec_assistant_find_page (emp, pagenode->frame, &index);
|
|
gtk_assistant_insert_page (GTK_ASSISTANT (assistant), page, index + 1);
|
|
} else {
|
|
gtk_assistant_prepend_page (GTK_ASSISTANT (assistant), page);
|
|
}
|
|
gtk_assistant_set_page_type (GTK_ASSISTANT (assistant), page, GTK_ASSISTANT_PAGE_CONTENT);
|
|
gtk_assistant_set_page_title (GTK_ASSISTANT (assistant), page, translated_label);
|
|
gtk_widget_show_all (page);
|
|
|
|
wn->frame = page;
|
|
} else {
|
|
w = gtk_label_new_with_mnemonic (translated_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;
|
|
|
|
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(gtk_widget_destroyed), &wn->widget);
|
|
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;
|
|
if (item->factory) {
|
|
section = item->factory(emp, item, page, wn->widget, wn->context->data);
|
|
wn->frame = section;
|
|
if (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("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_vbox_new(FALSE, 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(gtk_widget_destroyed), &wn->widget);
|
|
sectionnode = wn;
|
|
break;
|
|
case E_CONFIG_ITEM:
|
|
case E_CONFIG_ITEM_TABLE:
|
|
/* generated sections never retain their widgets on a rebuild */
|
|
if (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(emp, item, section, wn->widget, wn->context->data);
|
|
|
|
d(printf("item %d:%s widget %p\n", itemno, item->path, w));
|
|
|
|
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(gtk_widget_destroyed), &wn->widget);
|
|
itemno++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If the last section doesn't contain anything, hide it */
|
|
if (sectionnode != NULL && sectionnode->frame != NULL) {
|
|
if ( (sectionnode->empty = itemno == 0) ) {
|
|
gtk_widget_hide(sectionnode->frame);
|
|
sectionno--;
|
|
} else
|
|
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);
|
|
}
|
|
}
|
|
|
|
if (is_assistant && last_active_page) {
|
|
gint page_index = -1;
|
|
|
|
ec_assistant_find_page (emp, last_active_page->frame, &page_index);
|
|
gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), page_index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 GtkAssistant for the E_CONFIG_ASSISTANT
|
|
* 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;*/
|
|
gint i;
|
|
|
|
g_return_val_if_fail (emp->target != NULL, 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);
|
|
else {
|
|
gtk_window_set_position (GTK_WINDOW (emp->widget), GTK_WIN_POS_CENTER);
|
|
gtk_widget_show (emp->widget);
|
|
}
|
|
|
|
return emp->widget;
|
|
}
|
|
|
|
static void
|
|
ec_dialog_response(GtkWidget *d, gint 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_ASSISTANT, then this will be a toplevel GtkWindow containing
|
|
* a GtkAssistant. If it is @E_CONFIG_BOOK then it will be a GtkDialog
|
|
* containing a Notebook.
|
|
*
|
|
* 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, GtkWindow *parent, const gchar *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_DIALOG_NO_SEPARATOR,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OK, GTK_RESPONSE_OK,
|
|
NULL);
|
|
g_signal_connect(w, "response", G_CALLBACK(ec_dialog_response), emp);
|
|
|
|
gtk_widget_ensure_style (w);
|
|
gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (w))), 0);
|
|
gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_action_area (GTK_DIALOG (w))), 12);
|
|
|
|
gtk_box_pack_start((GtkBox *)gtk_dialog_get_content_area (((GtkDialog *)w)), emp->widget, TRUE, TRUE, 0);
|
|
} else {
|
|
/* response is handled directly by the assistant stuff */
|
|
w = emp->widget;
|
|
gtk_window_set_title ((GtkWindow *)w, title);
|
|
}
|
|
|
|
emp->window = w;
|
|
gtk_widget_show(w);
|
|
|
|
return w;
|
|
}
|
|
|
|
static void
|
|
ec_call_page_check (EConfig *emp)
|
|
{
|
|
if (emp->type == E_CONFIG_ASSISTANT) {
|
|
ec_assistant_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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ec_idle_handler_for_rebuild (gpointer data)
|
|
{
|
|
EConfig *emp = (EConfig*) data;
|
|
|
|
ec_rebuild (emp);
|
|
ec_call_page_check (emp);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* 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 Assistant 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) {
|
|
g_idle_add (ec_idle_handler_for_rebuild, emp);
|
|
} else {
|
|
ec_call_page_check (emp);
|
|
}
|
|
|
|
/* 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 assistant 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 assistant 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 gchar *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 GtkVBox object inside the assistant.
|
|
**/
|
|
GtkWidget *e_config_page_get(EConfig *ec, const gchar *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 gchar *e_config_page_next(EConfig *ec, const gchar *pageid)
|
|
{
|
|
struct _widget_node *wn;
|
|
gint 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 gchar *e_config_page_prev(EConfig *ec, const gchar *pageid)
|
|
{
|
|
struct _widget_node *wn;
|
|
gint 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 gchar *id, EConfigFactoryFunc func, gpointer 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.
|
|
**/
|
|
gpointer e_config_target_new(EConfig *ep, gint type, gsize size)
|
|
{
|
|
EConfigTarget *t;
|
|
|
|
if (size < sizeof(EConfigTarget)) {
|
|
g_warning ("Size is less than size of EConfigTarget\n");
|
|
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, gpointer o)
|
|
{
|
|
EConfigTarget *t = o;
|
|
|
|
((EConfigClass *)G_OBJECT_GET_CLASS(ep))->target_free(ep, t);
|
|
}
|
|
|
|
/* ********************************************************************** */
|
|
|
|
/* 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>
|
|
</extension>
|
|
|
|
*/
|
|
|
|
static gpointer emph_parent_class;
|
|
#define emph ((EConfigHook *)eph)
|
|
|
|
static const EPluginHookTargetKey ech_item_types[] = {
|
|
{ "book", E_CONFIG_BOOK },
|
|
{ "assistant", E_CONFIG_ASSISTANT },
|
|
|
|
{ "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 },
|
|
{ NULL },
|
|
};
|
|
|
|
static void
|
|
ech_commit(EConfig *ec, GSList *items, gpointer data)
|
|
{
|
|
struct _EConfigHookGroup *group = data;
|
|
|
|
if (group->commit && group->hook->hook.plugin->enabled)
|
|
e_plugin_invoke(group->hook->hook.plugin, group->commit, ec->target);
|
|
}
|
|
|
|
static void
|
|
ech_abort(EConfig *ec, GSList *items, gpointer data)
|
|
{
|
|
struct _EConfigHookGroup *group = data;
|
|
|
|
if (group->abort && group->hook->hook.plugin->enabled)
|
|
e_plugin_invoke(group->hook->hook.plugin, group->abort, ec->target);
|
|
}
|
|
|
|
static gboolean
|
|
ech_check(EConfig *ec, const gchar *pageid, gpointer data)
|
|
{
|
|
struct _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
|
|
ech_config_factory(EConfig *emp, gpointer data)
|
|
{
|
|
struct _EConfigHookGroup *group = data;
|
|
|
|
d(printf("config factory called %s\n", group->id?group->id:"all menus"));
|
|
|
|
if (emp->target->type != group->target_type
|
|
|| !group->hook->hook.plugin->enabled)
|
|
return;
|
|
|
|
if (group->items)
|
|
e_config_add_items(emp, group->items, ech_commit, ech_abort, NULL, group);
|
|
|
|
if (group->check)
|
|
e_config_add_page_check(emp, NULL, ech_check, 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 GtkWidget *
|
|
ech_config_widget_factory(EConfig *ec, EConfigItem *item, GtkWidget *parent, GtkWidget *old, gpointer data)
|
|
{
|
|
struct _EConfigHookGroup *group = data;
|
|
|
|
if (group->hook->hook.plugin->enabled) {
|
|
EConfigHookItemFactoryData hdata;
|
|
|
|
hdata.config = ec;
|
|
hdata.item = item;
|
|
hdata.target = ec->target;
|
|
hdata.parent = parent;
|
|
hdata.old = old;
|
|
|
|
return (GtkWidget *)e_plugin_invoke(group->hook->hook.plugin, (gchar *)item->user_data, &hdata);
|
|
} else
|
|
return NULL;
|
|
}
|
|
|
|
static struct _EConfigItem *
|
|
emph_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, 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;
|
|
|
|
d(printf(" path=%s label=%s factory=%s\n", item->path, item->label, (gchar *)item->user_data));
|
|
|
|
return item;
|
|
error:
|
|
d(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);
|
|
gchar *tmp;
|
|
|
|
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(klass->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,
|
|
((EPluginHookClass *)G_OBJECT_GET_CLASS(eph))->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 = 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 gint
|
|
emph_construct(EPluginHook *eph, EPlugin *ep, xmlNodePtr root)
|
|
{
|
|
xmlNodePtr node;
|
|
EConfigClass *klass;
|
|
|
|
d(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((gchar *)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 = "org.gnome.evolution.config:1.0";
|
|
|
|
d(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, (gpointer)map->type, (gpointer)map);
|
|
}
|