/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/**
* gnome-component-ui.c: Client UI signal multiplexer and verb repository.
*
* Author:
* Michael Meeks (michael@ximian.com)
*
* Copyright 1999, 2001 Ximian, Inc.
*/
#include <config.h>
#include <string.h>
#include <unistd.h>
#include <bonobo/bonobo-types.h>
#include <bonobo/bonobo-ui-xml.h>
#include <bonobo/bonobo-ui-util.h>
#include <bonobo/bonobo-ui-engine.h>
#include <bonobo/bonobo-ui-component.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-ui-marshal.h>
#include <bonobo/bonobo-control.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#define PARENT_TYPE BONOBO_TYPE_OBJECT
static GObjectClass *bonobo_ui_component_parent_class;
enum {
EXEC_VERB,
UI_EVENT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
#define GET_CLASS(c) (BONOBO_UI_COMPONENT_CLASS (G_OBJECT_GET_CLASS (c)))
typedef struct {
char *id;
GClosure *closure;
} UIListener;
typedef struct {
char *cname;
GClosure *closure;
} UIVerb;
struct _BonoboUIComponentPrivate {
GHashTable *verbs;
GHashTable *listeners;
char *name;
Bonobo_UIContainer container;
int frozenness;
};
static inline BonoboUIComponent *
bonobo_ui_from_servant (PortableServer_Servant servant)
{
return BONOBO_UI_COMPONENT (bonobo_object_from_servant (servant));
}
static gboolean
verb_destroy (gpointer dummy, UIVerb *verb, gpointer dummy2)
{
if (verb) {
if (verb->closure)
g_closure_unref (verb->closure);
verb->closure = NULL;
g_free (verb->cname);
g_free (verb);
}
return TRUE;
}
static gboolean
listener_destroy (gpointer dummy, UIListener *l, gpointer dummy2)
{
if (l) {
if (l->closure)
g_closure_unref (l->closure);
l->closure = NULL;
g_free (l->id);
g_free (l);
}
return TRUE;
}
static void
ui_event (BonoboUIComponent *component,
const char *id,
Bonobo_UIComponent_EventType type,
const char *state)
{
UIListener *list;
list = g_hash_table_lookup (component->priv->listeners, id);
if (list && list->closure)
bonobo_closure_invoke (
list->closure, G_TYPE_NONE,
BONOBO_TYPE_UI_COMPONENT, component,
G_TYPE_STRING, id,
G_TYPE_INT, type,
G_TYPE_STRING, state,
G_TYPE_INVALID);
}
static void
impl_Bonobo_UIComponent_setContainer (PortableServer_Servant servant,
const Bonobo_UIContainer container,
CORBA_Environment *ev)
{
BonoboUIComponent *component = bonobo_ui_from_servant (servant);
bonobo_ui_component_set_container (component, container, ev);
}
static void
impl_Bonobo_UIComponent_unsetContainer (PortableServer_Servant servant,
CORBA_Environment *ev)
{
BonoboUIComponent *component = bonobo_ui_from_servant (servant);
bonobo_ui_component_unset_container (component, ev);
}
static CORBA_string
impl_Bonobo_UIComponent__get_name (PortableServer_Servant servant,
CORBA_Environment *ev)
{
return CORBA_string_dup ("");
}
static CORBA_char *
impl_Bonobo_UIComponent_describeVerbs (PortableServer_Servant servant,
CORBA_Environment *ev)
{
g_warning ("FIXME: Describe verbs unimplemented");
return CORBA_string_dup ("<NoUIVerbDescriptionCodeYet/>");
}
static void
impl_Bonobo_UIComponent_execVerb (PortableServer_Servant servant,
const CORBA_char *cname,
CORBA_Environment *ev)
{
BonoboUIComponent *component;
UIVerb *verb;
component = bonobo_ui_from_servant (servant);
bonobo_object_ref (BONOBO_OBJECT (component));
/* g_warning ("TESTME: Exec verb '%s'", cname);*/
verb = g_hash_table_lookup (component->priv->verbs, cname);
if (verb && verb->closure)
/* We need a funny arg order here - so for
our C closure we do odd things ! */
bonobo_closure_invoke (
verb->closure, G_TYPE_NONE,
BONOBO_TYPE_UI_COMPONENT, component,
G_TYPE_STRING, cname,
G_TYPE_INVALID);
else
g_warning ("FIXME: verb '%s' not found, emit exception", cname);
g_signal_emit (component, signals [EXEC_VERB], 0, cname);
bonobo_object_unref (BONOBO_OBJECT (component));
}
static void
impl_Bonobo_UIComponent_uiEvent (PortableServer_Servant servant,
const CORBA_char *id,
const Bonobo_UIComponent_EventType type,
const CORBA_char *state,
CORBA_Environment *ev)
{
BonoboUIComponent *component;
component = bonobo_ui_from_servant (servant);
/* g_warning ("TESTME: Event '%s' '%d' '%s'\n", path, type, state);*/
bonobo_object_ref (BONOBO_OBJECT (component));
g_signal_emit (component, signals [UI_EVENT], 0, id, type, state);
bonobo_object_unref (BONOBO_OBJECT (component));
}
static void
marshal_VOID__USER_DATA_STRING (GClosure *closure,
GValue *return_value,
guint n_param_values,
const GValue *param_values,
gpointer invocation_hint,
gpointer marshal_data)
{
typedef void (*marshal_func_VOID__USER_DATA_STRING_t) (gpointer data1,
gpointer data2,
gpointer arg_1);
register marshal_func_VOID__USER_DATA_STRING_t callback;
register GCClosure *cc = (GCClosure*) closure;
register gpointer data1, data2;
g_return_if_fail (n_param_values == 2);
if (G_CCLOSURE_SWAP_DATA (closure))
{
data1 = closure->data;
data2 = g_value_peek_pointer (param_values + 0);
}
else
{
data1 = g_value_peek_pointer (param_values + 0);
data2 = closure->data;
}
callback = (marshal_func_VOID__USER_DATA_STRING_t) (
marshal_data ? marshal_data : cc->callback);
callback (data1, data2, (char*) g_value_get_string (param_values + 1));
}
/**
* bonobo_ui_component_add_verb_full:
* @component: the component to add it to
* @cname: the programmatic name of the verb
* @fn: the callback function for invoking it
* @user_data: the associated user data for the callback
* @destroy_fn: a destroy function for the callback data
*
* Add a verb to the UI component, that can be invoked by
* the container.
**/
void
bonobo_ui_component_add_verb_full (BonoboUIComponent *component,
const char *cname,
GClosure *closure)
{
UIVerb *verb;
BonoboUIComponentPrivate *priv;
g_return_if_fail (cname != NULL);
g_return_if_fail (BONOBO_IS_UI_COMPONENT (component));
priv = component->priv;
if ((verb = g_hash_table_lookup (priv->verbs, cname))) {
g_hash_table_remove (priv->verbs, cname);
verb_destroy (NULL, verb, NULL);
}
verb = g_new (UIVerb, 1);
verb->cname = g_strdup (cname);
verb->closure = bonobo_closure_store
(closure, marshal_VOID__USER_DATA_STRING);
/* verb->cb (component, verb->user_data, cname); */
g_hash_table_insert (priv->verbs, verb->cname, verb);
}
/**
* bonobo_ui_component_add_verb:
* @component: the component to add it to
* @cname: the programmatic name of the verb
* @fn: the callback function for invoking it
* @user_data: the associated user data for the callback
*
* Add a verb to the UI component, that can be invoked by
* the container.
**/
void
bonobo_ui_component_add_verb (BonoboUIComponent *component,
const char *cname,
BonoboUIVerbFn fn,
gpointer user_data)
{
bonobo_ui_component_add_verb_full (
component, cname, g_cclosure_new (
G_CALLBACK (fn), user_data, NULL));
}
typedef struct {
gboolean by_name;
const char *name;
gboolean by_closure;
GClosure *closure;
} RemoveInfo;
static gboolean
remove_verb (gpointer key,
gpointer value,
gpointer user_data)
{
RemoveInfo *info = user_data;
UIVerb *verb = value;
if (info->by_name && info->name &&
!strcmp (verb->cname, info->name))
return verb_destroy (NULL, verb, NULL);
else if (info->by_closure &&
info->closure == verb->closure)
return verb_destroy (NULL, verb, NULL);
return FALSE;
}
/**
* bonobo_ui_component_remove_verb:
* @component: the component to add it to
* @cname: the programmatic name of the verb
*
* Remove a verb by it's unique name
**/
void
bonobo_ui_component_remove_verb (BonoboUIComponent *component,
const char *cname)
{
RemoveInfo info;
memset (&info, 0, sizeof (info));
info.by_name = TRUE;
info.name = cname;
g_hash_table_foreach_remove (component->priv->verbs, remove_verb, &info);
}
/**
* bonobo_ui_component_remove_verb_by_closure:
* @component: the component to add it to
* @fn: the function pointer
*
* remove any verb handled by @fn.
**/
void
bonobo_ui_component_remove_verb_by_closure (BonoboUIComponent *component,
GClosure *closure)
{
RemoveInfo info;
memset (&info, 0, sizeof (info));
info.by_closure = TRUE;
info.closure = closure;
g_hash_table_foreach_remove (component->priv->verbs, remove_verb, &info);
}
/**
* bonobo_ui_component_add_listener_full:
* @component: the component to add it to
* @id: the programmatic name of the id
* @fn: the callback function for invoking it
* @user_data: the associated user data for the callback
* @destroy_fn: a destroy function for the callback data
*
* Add a listener for stateful events.
**/
void
bonobo_ui_component_add_listener_full (BonoboUIComponent *component,
const char *id,
GClosure *closure)
{
UIListener *list;
BonoboUIComponentPrivate *priv;
g_return_if_fail (closure != NULL);
g_return_if_fail (BONOBO_IS_UI_COMPONENT (component));
priv = component->priv;
if ((list = g_hash_table_lookup (priv->listeners, id))) {
g_hash_table_remove (priv->listeners, id);
listener_destroy (NULL, list, NULL);
}
list = g_new (UIListener, 1);
list->id = g_strdup (id);
list->closure = bonobo_closure_store
(closure, bonobo_ui_marshal_VOID__STRING_INT_STRING);
g_hash_table_insert (priv->listeners, list->id, list);
}
/**
* bonobo_ui_component_add_listener:
* @component: the component to add it to
* @id: the programmatic name of the id
* @fn: the callback function for invoking it
* @user_data: the associated user data for the callback
*
* Add a listener for stateful events.
**/
void
bonobo_ui_component_add_listener (BonoboUIComponent *component,
const char *id,
BonoboUIListenerFn fn,
gpointer user_data)
{
bonobo_ui_component_add_listener_full (
component, id, g_cclosure_new (G_CALLBACK (fn), user_data, NULL));
}
static gboolean
remove_listener (gpointer key,
gpointer value,
gpointer user_data)
{
RemoveInfo *info = user_data;
UIListener *listener = value;
if (info->by_name && info->name &&
!strcmp (listener->id, info->name))
return listener_destroy (NULL, listener, NULL);
else if (info->by_closure &&
info->closure == listener->closure)
return listener_destroy (NULL, listener, NULL);
return FALSE;
}
/**
* bonobo_ui_component_remove_listener:
* @component: the component to add it to
* @cname: the programmatic name of the id
*
* Remove any listener by its unique id
**/
void
bonobo_ui_component_remove_listener (BonoboUIComponent *component,
const char *cname)
{
RemoveInfo info;
memset (&info, 0, sizeof (info));
info.by_name = TRUE;
info.name = cname;
g_hash_table_foreach_remove (component->priv->listeners, remove_listener, &info);
}
/**
* bonobo_ui_component_remove_by_closure:
* @component: the component to add it to
* @fn: the function pointer
*
* Remove any listener with associated function @fn
**/
void
bonobo_ui_component_remove_listener_by_closure (BonoboUIComponent *component,
GClosure *closure)
{
RemoveInfo info;
memset (&info, 0, sizeof (info));
info.by_closure = TRUE;
info.closure = closure;
g_hash_table_foreach_remove (component->priv->listeners, remove_listener, &info);
}
static void
bonobo_ui_component_finalize (GObject *object)
{
BonoboUIComponent *comp = (BonoboUIComponent *) object;
BonoboUIComponentPrivate *priv = comp->priv;
if (priv) {
g_hash_table_foreach_remove (
priv->verbs, (GHRFunc) verb_destroy, NULL);
g_hash_table_destroy (priv->verbs);
priv->verbs = NULL;
g_hash_table_foreach_remove (
priv->listeners,
(GHRFunc) listener_destroy, NULL);
g_hash_table_destroy (priv->listeners);
priv->listeners = NULL;
g_free (priv->name);
g_free (priv);
}
comp->priv = NULL;
bonobo_ui_component_parent_class->finalize (object);
}
/**
* bonobo_ui_component_construct:
* @ui_component: the UI component itself
* @name: the name of the UI component
*
* Construct the UI component with name @name
*
* Return value: a constructed UI component or NULL on error
**/
BonoboUIComponent *
bonobo_ui_component_construct (BonoboUIComponent *ui_component,
const char *name)
{
g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (ui_component), NULL);
ui_component->priv->name = g_strdup (name);
return ui_component;
}
/**
* bonobo_ui_component_new:
* @name: the name of the UI component
*
* Create a new UI component with the specified name
*
* Return value: a new UI component
**/
BonoboUIComponent *
bonobo_ui_component_new (const char *name)
{
BonoboUIComponent *component;
component = g_object_new (BONOBO_TYPE_UI_COMPONENT, NULL);
if (!component)
return NULL;
return BONOBO_UI_COMPONENT (
bonobo_ui_component_construct (
component, name));
}
/**
* bonobo_ui_component_new_default:
* @void:
*
* Create a UI component with a unique default name
* constructed from various available system properties.
*
* Return value: a new UI component
**/
BonoboUIComponent *
bonobo_ui_component_new_default (void)
{
char *name;
BonoboUIComponent *component;
static int idx = 0;
static int pid = 0;
if (!pid)
pid = getpid ();
name = g_strdup_printf ("%d-%d", pid, idx++);
component = bonobo_ui_component_new (name);
g_free (name);
return component;
}
/**
* bonobo_ui_component_set_name:
* @component: the UI component
* @name: the new name
*
* Set the @name of the UI @component
**/
void
bonobo_ui_component_set_name (BonoboUIComponent *component,
const char *name)
{
g_return_if_fail (name != NULL);
g_return_if_fail (BONOBO_IS_UI_COMPONENT (component));
g_free (component->priv->name);
component->priv->name = g_strdup (name);
}
/**
* bonobo_ui_component_get_name:
* @component: the UI component
*
* Return value: the name of the UI @component
**/
const char *
bonobo_ui_component_get_name (BonoboUIComponent *component)
{
g_return_val_if_fail (BONOBO_IS_UI_COMPONENT (component), NULL);
return component->priv->name;
}
/**
* bonobo_ui_component_set:
* @component: the component
* @path: the path to set
* @xml: the xml to set
* @opt_ev: the (optional) CORBA exception environment
*
* Set the @xml fragment into the remote #BonoboUIContainer's tree
* attached to @component at the specified @path
*
* If you see blank menu items ( or just separators ) it's
* likely that you should be using #bonobo_ui_component_set_translate
* which substantialy deprecates this routine.
**/
void
bonobo_ui_component_set (BonoboUIComponent *component,
const char *path,
const char *xml,
CORBA_Environment *opt_ev)
{
g_return_if_fail (BONOBO_IS_UI_COMPONENT (component));
GET_CLASS (component)->xml_set (component, path, xml, opt_ev);
}
static void
impl_xml_set (BonoboUIComponent *component,
const char *path,
const char *xml,
CORBA_Environment *ev)
{
CORBA_Environment *real_ev, tmp_ev;
Bonobo_UIContainer container;
char *name;
container = component->priv->container;
g_return_if_fail (container != CORBA_OBJECT_NIL);
if (xml [0] == '\0')
retur