/*
* 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
*
*
* Authors:
* Michael Zucchi
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "e-config.h"
#include
#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 check;
gpointer data;
};
struct _finish_page_node {
gchar *pageid;
gboolean is_finish;
gint orig_type;
};
struct _EConfigPrivate {
GList *menus;
GList *widgets;
GList *checks;
GList *finish_pages;
};
static GtkWidget *
ech_config_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
config_finalize (GObject *object)
{
EConfig *emp = (EConfig *) object;
EConfigPrivate *p = emp->priv;
GList *link;
d(printf("finalising EConfig %p\n", object));
g_free (emp->id);
link = p->menus;
while (link != NULL) {
struct _menu_node *node = link->data;
if (node->free)
node->free (emp, node->menu, node->data);
g_free (node);
link = g_list_delete_link (link, link);
}
link = p->widgets;
while (link != NULL) {
struct _widget_node *node = link->data;
/* disconnect the gtk_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->widget);
g_free (node);
link = g_list_delete_link (link, link);
}
link = p->checks;
while (link != NULL) {
struct _check_node *node = link->data;
g_free (node->pageid);
g_free (node);
link = g_list_delete_link (link, link);
}
link = p->finish_pages;
while (link != NULL) {
struct _finish_page_node *node = link->data;
g_free (node->pageid);
g_free (node);
link = g_list_delete_link (link, link);
}
/* 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 = G_TYPE_INSTANCE_GET_PRIVATE (
config, E_TYPE_CONFIG, EConfigPrivate);
}
/**
* 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.
* @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:
* @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;
ec->priv->checks = g_list_append (ec->priv->checks, cn);
}
static struct _finish_page_node *
find_page_finish (EConfig *config, const gchar *pageid)
{
GList *link;
link = config->priv->finish_pages;
while (link != NULL) {
struct _finish_page_node *node = link->data;
if (g_str_equal (node->pageid, pageid))
return node;
link = g_list_next (link);
}
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);
ec->priv->finish_pages = g_list_append (
ec->priv->finish_pages, fp);
}
fp->is_finish = TRUE;
} else {
if (fp)
fp->is_finish = FALSE;
}
}
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 GList *
ec_assistant_find_page (EConfig *ec, GtkWidget *page, gint *page_index)
{
struct _widget_node *node = NULL;
GList *link;
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);
/* Assume failure, then if we do fail we can just return. */
if (page_index != NULL)
*page_index = -1;
/* Find the page widget in our sorted widget node list. */
for (link = ec->priv->widgets; link != NULL; link = link->next) {
node = link->data;
if (node->frame != page)
continue;
if (node->item->type == E_CONFIG_PAGE)
break;
if (node->item->type == E_CONFIG_PAGE_START)
break;
if (node->item->type == E_CONFIG_PAGE_FINISH)
break;
if (node->item->type == E_CONFIG_PAGE_PROGRESS)
break;
}
/* FAIL: The widget is not in our list. */
if (link == NULL)
return NULL;
/* Find the corresponding GtkAssistant page index. */
if (page_index) {
GtkAssistant *assistant;
GtkWidget *nth_page;
gint ii, n_pages;
assistant = GTK_ASSISTANT (ec->widget);
n_pages = gtk_assistant_get_n_pages (assistant);
for (ii = 0; ii < n_pages; ii++) {
nth_page = gtk_assistant_get_nth_page (assistant, ii);
if (page == nth_page) {
*page_index = ii;
break;
}
}
g_warn_if_fail (ii < n_pages);
}
return link;
}
static void
ec_assistant_check_current (EConfig *ec)
{
struct _widget_node *wn;
struct _finish_page_node *fp;
GtkAssistant *assistant;
GtkWidget *page;
GList *link;
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);
link = ec_assistant_find_page (ec, page, NULL);
g_return_if_fail (link != NULL);
wn = link->data;
/* 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)
{
GtkAssistant *assistant;
EConfig *ec = user_data;
struct _widget_node *node;
GtkWidget *page_widget;
GList *link = NULL;
gint next_page;
/* As far as we're concerned, the GtkAssistant is just an unordered
* collection of pages. Our sorted list of widget nodes determines
* the next page. */
assistant = GTK_ASSISTANT (ec->widget);
page_widget = gtk_assistant_get_nth_page (assistant, current_page);
link = ec_assistant_find_page (ec, page_widget, NULL);
g_return_val_if_fail (link != NULL, -1);
node = (struct _widget_node *) link->data;
/* If we're already on a FINISH page then we're done. */
if (node->item->type == E_CONFIG_PAGE_FINISH)
return -1;
/* Find the next E_CONFIG_PAGE* type node. */
for (link = link->next; link != NULL; link = link->next) {
node = (struct _widget_node *) link->data;
if (node->empty || node->frame == NULL)
continue;
if (node->item->type == E_CONFIG_PAGE)
break;
if (node->item->type == E_CONFIG_PAGE_START)
break;
if (node->item->type == E_CONFIG_PAGE_FINISH)
break;
if (node->item->type == E_CONFIG_PAGE_PROGRESS)
break;
}
/* Find the corresponding GtkAssistant page number. */
if (link != NULL) {
node = (struct _widget_node *) link->data;
ec_assistant_find_page (ec, node->frame, &next_page);
} else
next_page = -1;
return next_page;
}
static void
ec_rebuild (EConfig *emp)
{
EConfigPrivate *p = emp->priv;
struct _widget_node *sectionnode = NULL, *pagenode = NULL;
GtkWidget *book = NULL, *page = NULL, *section = NULL, *root = NULL, *assistant = NULL;
gint pageno = 0, sectionno = 0, itemno = 0;
gint n_visible_widgets = 0;
GList *last_active_link = NULL;
gboolean is_assistant;
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 */
is_assistant = GTK_IS_ASSISTANT (emp->widget);
if (is_assistant) {
GtkAssistant *assistant;
gint page_index;
assistant = GTK_ASSISTANT (emp->widget);
page_index = gtk_assistant_get_current_page (assistant);
if (page_index != -1) {
GtkWidget *nth_page;
nth_page = gtk_assistant_get_nth_page (
GTK_ASSISTANT (emp->widget), page_index);
last_active_link = ec_assistant_find_page (
emp, nth_page, NULL);
}
gtk_assistant_set_current_page (GTK_ASSISTANT (emp->widget), 0);
}
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_PAGE_START
|| item->type == E_CONFIG_PAGE_FINISH
|| item->type == E_CONFIG_PAGE_PROGRESS
|| 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
|| item->type == E_CONFIG_PAGE_START
|| item->type == E_CONFIG_PAGE_FINISH
|| item->type == E_CONFIG_PAGE_PROGRESS)) {
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,
0, 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 (wn->widget == NULL) {
if (item->factory) {
page = item->factory (
emp, item, root, wn->frame,
pageno, 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:
case E_CONFIG_PAGE_PROGRESS:
/* 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->type == E_CONFIG_PAGE_PROGRESS &&
emp->type != E_CONFIG_ASSISTANT) {
g_warning("EConfig assistant progress pages can't be used on E_CONFIG_BOOKs");
break;
}
if (item->factory) {
page = item->factory (
emp, item, root, wn->frame,
pageno, 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, item->type == E_CONFIG_PAGE ? GTK_ASSISTANT_PAGE_CONTENT : GTK_ASSISTANT_PAGE_PROGRESS);
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);
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(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;
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 (
emp, item, page, wn->widget, 0,
wn->context->data, &wn->real_frame);
wn->frame = section;
if (section)
itemno = 1;
if (factory != ech_config_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 ech_config_section_factory() -
* see emph_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("%s", 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,
0, wn->context->data);
d(printf("item %d:%s widget %p\n", itemno, item->path, w));
d(printf (" item %s: (%s - %s)\n",
item->path,
g_type_name_from_instance ((GTypeInstance *) w),
gtk_widget_get_visible (w) ? "visible" : "invisible"));
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++;
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);
}
}
if (is_assistant && last_active_link != NULL) {
GtkAssistant *assistant;
struct _widget_node *wn;
gint page_index = -1;
wn = last_active_link->data;
assistant = GTK_ASSISTANT (emp->widget);
ec_assistant_find_page (emp, wn->frame, &page_index);
gtk_assistant_set_current_page (assistant, 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
* finalized when the widget is.
*
* Return value: The widget, also available in @emp.widget
**/
GtkWidget *
e_config_create_widget (EConfig *emp)
{
EConfigPrivate *p = emp->priv;
GPtrArray *items = g_ptr_array_new ();
GList *link;
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 */
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 = emp;
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;ilen;i++)
p->widgets = g_list_append (p->widgets, items->pdata[i]);
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
* finalized 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 *window;
e_config_create_widget (emp);
if (emp->type == E_CONFIG_BOOK) {
GtkWidget *content_area;
window = gtk_dialog_new_with_buttons (
title, parent,
GTK_DIALOG_DESTROY_WITH_PARENT,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_APPLY, GTK_RESPONSE_OK,
NULL);
g_signal_connect (
window, "response",
G_CALLBACK (ec_dialog_response), emp);
gtk_container_set_border_width (GTK_CONTAINER (window), 5);
gtk_container_set_border_width (GTK_CONTAINER (emp->widget), 5);
content_area =
gtk_dialog_get_content_area (GTK_DIALOG (window));
gtk_box_pack_start (
GTK_BOX (content_area), emp->widget, TRUE, TRUE, 0);
} else {
/* response is handled directly by the assistant stuff */
window = emp->widget;
gtk_window_set_title (GTK_WINDOW (window), title);
}
emp->window = window;
gtk_widget_show (window);
return window;
}
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: 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 *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:
* @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:
* @ec: 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->check (config, pageid, node->data)) {
return FALSE;
}
link = g_list_next (link);
}
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)
{
GList *link;
link = ec->priv->widgets;
while (link != NULL) {
struct _widget_node *wn = link->data;
if (!wn->empty
&& (wn->item->type == E_CONFIG_PAGE
|| wn->item->type == E_CONFIG_PAGE_START
|| wn->item->type == E_CONFIG_PAGE_FINISH
|| wn->item->type == E_CONFIG_PAGE_PROGRESS)
&& !strcmp (wn->item->path, pageid))
return wn->frame;
link = g_list_next (link);
}
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)
{
GList *link;
gint found;
link = g_list_first (ec->priv->widgets);
found = pageid == NULL ? 1:0;
while (link != NULL) {
struct _widget_node *wn = link->data;
if (!wn->empty
&& (wn->item->type == E_CONFIG_PAGE
|| wn->item->type == E_CONFIG_PAGE_START
|| wn->item->type == E_CONFIG_PAGE_FINISH
|| wn->item->type == E_CONFIG_PAGE_PROGRESS)) {
if (found)
return wn->item->path;
else if (strcmp (wn->item->path, pageid) == 0)
found = 1;
}
link = g_list_next (link);
}
return NULL;
}
/**
* e_config_page_next:
* @ec: an #EConfig
* @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)
{
GList *link;
gint found;
link = g_list_last (ec->priv->widgets);
found = pageid == NULL ? 1:0;
while (link != NULL) {
struct _widget_node *wn = link->data;
if (!wn->empty
&& (wn->item->type == E_CONFIG_PAGE
|| wn->item->type == E_CONFIG_PAGE_START
|| wn->item->type == E_CONFIG_PAGE_FINISH
|| wn->item->type == E_CONFIG_PAGE_PROGRESS)) {
if (found)
return wn->item->path;
else if (strcmp (wn->item->path, pageid) == 0)
found = 1;
}
link = g_list_previous (link);
}
return NULL;
}
/* ********************************************************************** */
/**
* e_config_class_add_factory:
* @class: 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 *class,
const gchar *id,
EConfigFactoryFunc func,
gpointer user_data)
{
EConfigFactory *factory;
g_return_val_if_fail (E_IS_CONFIG_CLASS (class), 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;
class->factories = g_list_append (class->factories, factory);
return factory;
}
/**
* e_config_class_remove_factory:
* @factory: an #EConfigFactory
*
* Removes a config factory.
**/
void
e_config_class_remove_factory (EConfigClass *class,
EConfigFactory *factory)
{
g_return_if_fail (E_IS_CONFIG_CLASS (class));
g_return_if_fail (factory != NULL);
class->factories = g_list_remove (class->factories, factory);
g_free (factory->id);
g_slice_free (EConfigFactory, factory);
}
/**
* 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 */
/*