| La arquitectura de componentes Bonobo: Una introducción práctica | ||
|---|---|---|
| Prev | Chapter 1. La arquitectura de componentes Bonobo | Next |
Gnome-db es la arquitectura para acceso a fuentes de datos en GNOME. Aún no ha sido liberada oficialmente aunque ya le queda muy poco ya que se va a realizar a la vez que GNOME 2.0, a principios de este Otoño. Entre las parte más destacadas encontramos una interfaz gráfica muy cuidada con aplicaciones gráficas como gda-fe, desde donde se pueden acceder a casi todas las parte de la arquitectura desde una interfaz gráfica.
Gnome-db, al igual que Gnumeric, Evolution, Nautilius, Sodipodi y muchos otros, forma parte de una nueva generación de proyectos software que han abrazado la arquitectura de componentes de Bonobo y que llevan explotando los beneficios de esta arquitectura varios meses. El uso de Bonobo en la actualidad tiene el incoveniente de que está aún sufriendo cambios muy importantes lo que puede provocar que los proyectos que lo están utilizando tengan que ser readaptados de forma continua. Para evitarlo en Gnome-db, se han creado unas librerías que aislan al desarrollador de estos cambios en Bonobo, facilitando también su uso. Son estas librerías las que vamos a analizar así como para qué se utilizan.
Uno de los primeros pasos que se tomó fue crear una librería para la creación de contenedores Bonobo. Gracias a ella la creación de un contenedor Bonobo es tan sencillo como crear un objeto. Para poder seguir adelante vamos a necesitar el código de gnome-db que, de nuevo, obtenemos del CVS de GNOME con las ordenes:
export CVSROOT=':pserver:anonymous@anoncvs.gnome.org:/cvs/gnome'
cvs login
cvs -z3 checkout gnome-db
Una vez que lo tengamos nos vamos al directorio gnome-db y, dentro de él, al directorio gda-components, donde se encuentra los objetos GTK que representan a los contenedores, controles y componentes de gnome-db.
En este caso nos interesa el fichero gnome-db-container.c y el gnome-db-container.h en los que se define el objeto GTK GnomeDbContainer. Este objeto se encarga de abstraer la complejidad de un BonoboContainer, que es la implementación de la interfaz Bonobo::Container. La definición de objetos utilizando el modelo de GTK es un poco más compleja que en lenguajes como C++ o Java pro lo que vamos a evitar entrar en muchos detalles en este sentido. Resumiendo, tenemos a BonoboContainer que es la implementación en C de la interfaz Bonobo::Container, y tenemos a GnomeDbContainer, que hereda de BonoboContainer y la amplía y abstrae para facilitar la vida del desarrollador de Gnome-db. Vemos que tiene esta clase.
struct _GnomeDbContainer
{
BonoboObject bonobo_object;
BonoboContainer* bonobo_container;
BonoboPersistFile* persist_file;
BonoboUIHandler* uih;
/* private */
GtkWidget* toplevel;
GtkWidget* app_bar;
GList* inserted_objects;
GnomeDbContainerGetObjectFunc get_object_func;
};Como era de esperar un GnomeDbContainer es un BonoboContaier al que se le han añadido los objetos que permiten grabar en un medio persistente el estado del contenedor, BonoboPersistFile, y la gestión de la barra de herramientas y de menús, BonoboUIHandler. Recordemos que cuando se inserta en un contenedor un componente, este puede sustituir la barra de herramientas (toolbar) y la de menús (menubar) por los suyos propios.
Si queremos crear un contenedor Bonobo dentro de Gnome-db nada más sencillo. Por ejemplo, dentro del directorio gda-rolodex de gnome-db, está una aplicación que demuestra el uso de gnome-db y en concreto utiliza GnomeDbContainer para crear la ventana principal de la aplicación. Dentro del fichero gda-rolodex-main.c aparece el código:
glb_container = gnome_db_container_new_glade(GNOME_APP(gnome_app), GTK_MENU_BAR(menubar), GTK_TOOLBAR(toolbar), GNOME_APPBAR(gnome_app_bar), NULL);
Con esta llamada se crea un contenedor GnomeDbContainer, un contenedor Bonobo, y será en este contenedor donde se irán insertando los componentes que forman la aplicación.
Las parte fundamentales de la creación de este contenedor se realizan por un lado en el método init de la clase donde se crea el BonoboContainer de la clase GnomeDbContainer. Este método se puede interpretar como un constructor de la clase.
static void
gnome_db_container_init (GnomeDbContainer *container)
{
container->bonobo_container = BONOBO_CONTAINER(bonobo_container_new());
persist_file_save_cb,
(gpointer) container);Dentro del método gnome_db_container_new() es donde realmente se crea en GnomeDbContainer, se le dice que su ventana principal es un widget aplicación Gnome (gnome_app_new()) y se crea el UIHandler encargado de la gestión de barras de menús y herramientas. El código más relevante de este método es:
/**
* gnome_db_container_new
*/
GnomeDbContainer *
gnome_db_container_new (const gchar *name,
GnomeUIInfo *menus[],
GnomeUIInfo *toolbar[],
GnomeDbContainerGetObjectFunc func)
{
GnomeDbContainer* container;
BonoboUIHandlerMenuItem* menu_list;
BonoboUIHandlerToolbarItem* toolbar_list;
/* Creación del objeto y por lo tanto, del BonoboContainer */
container = gtk_type_new(gnome_db_container_get_type());
/* La ventana principal es un widget de tipo aplicación Gnome */
container->toplevel = gnome_app_new(name, name);
gtk_signal_connect(GTK_OBJECT(container->toplevel),
"delete_event",
GTK_SIGNAL_FUNC(close_cb),
(gpointer) container);
/* Creación del UIHandler del contenedor */
container->uih = bonobo_ui_handler_new();Para insertar un componente dentro del contenedor utilizamos la función:
void gnome_db_container_insert_object (GnomeDbContainer *container, const gchar *id)
La implementación de esta función revela toda la teoría que hemos detallado en apartados anteriores. Lo primero que se hace es intentar localizar al componente a través del identificador "id" que se pasa a la función. Para ello se utilizan dos sistemas:
if (!strncmp(id, "moniker_url:", 12))
object_server = bonobo_object_activate(id, 0);
else
object_server = bonobo_object_activate_with_goad_id(NULL, id, 0, NULL);
Si lo que se nos pasa para identificar al componente no es un "moniker" llamamos
directamente a la función bonobo_object_activate() que se encarga de activar
dicho objeto. Este objeto es un objeto CORBA, por lo que habrá que activar su
servidor asociado y creara una instancia del mismo a través de las factorías.
Para profundizar en este proceso de activación recomendamos al lector acudir a las
referencias del artículo.
En el caso de que el identificador sea un "moniker" hay que activar el objeto con
la llamada bonobo_object_activate_with_goad_id(). Hay que tener cuidado con el uso
de GOAD, el primitivo sistema de activación de objetos CORBA de GNOME. Se ha quedado
obsoleto y está siendo sustituido por una arquitectura conocida como OAF (Object
Activation Framework) que, entre otras ventajas, elimina la dependencia de las X que
tenía GOAD, permitiendo que las aplicaciones sin interfaz gráfica no tengan que
enlazar con librerías gráficas. Por ello en un futuro cercano esta llamada será
sustituida por "bonobo_object_activate_with_oaf_id()".Si hemos logrado localizar el componente y activarlo lo siguientes que necesitamos es crear un ClientSite dentro del contenedor para él:
if (object_server)
{
client_site = bonobo_client_site_new(container->bonobo_container);
bonobo_container_add(container->bonobo_container, BONOBO_OBJECT(client_site));
bonobo_client_site_bind_embeddable(client_site, object_server);Como se puede ver en el código, se crea un ClientSite nuevo, se añade al contenedor y se asocia al componente que acabamos de crear. Ahora ya solo nos queda añadir una vista para que se pueda visualizar el componente. Vamos con ellos:
view = g_new0(COMPONENTS_ContainerView, 1);
view->view_frame = NULL;
view_frame = bonobo_client_site_new_view(client_site, container->uih->top_level_uih);
view->container = container;Se crea una View para el componente y un ViewFrame asociado al ClientSite que acabamos de crear. Al crear el ViewFrame tenemos que pasarle un UIHandler, que es el objeto a través del cual se gestiona el cambio de las barras de herramientas de menús de la ventana principal. Es importante que el componente no este activo hasta que el usuario lo solicite. Esto ocurre cuando el usuario de la aplicación pulsa sobre el componente, lo que provoca la emisión de señales que activan la vista. Estas señales se asocian con los métodos que las tratan aquí:
gtk_signal_connect(GTK_OBJECT(view_frame),
"user_activate",
GTK_SIGNAL_FUNC(user_activation_request_cb),
(gpointer) view);
gtk_signal_connect(GTK_OBJECT(view_frame),
"activated",
GTK_SIGNAL_FUNC(view_activated_cb),
(gpointer) view);Cuando el usuario pulsa sobre el componente se llama a la función "view_activated_cb" que se encarga de la vista activa pase a ser el componente.
La creación de un control es mucho más sencilla que la de un contenedor. De hecho, a partir de un widget la creación de un control es tan sencilla como llamar a una función:
control = bonobo_control_new(widget);
La complejidad de los controles viene por la gestión de sus propiedades y por la definción de la factoría de creación de controles. Los controles se obtiene a través de peticiones a sus factorías, es decir, que hay unos servidores CORBA que son los encargados de la creación de cada tipo de controles. Debido a la extensión que nos llevaría detallar la gestión de propiedades y factorías de los controles nos vemos obligados a omitirla, aunque el lector siempre podrá acudir al código de Gnome-db y consultarlo allí directamente.
En el módulo gda-mgr se utiliza en muchos lugares los controles por lo que puede ser un buen lugar para comenzar a investigar.
Como ya hemos comentado los componentes Bonobo son aquellos elementos de Bonobo que no tienen porque tener necesariamente una interfaz gráfica. Se puede insertar en contenedores y ser utilizados desde allí, pero para el usuario de la aplicación serán invisibles. Al igual que los controles, su creación se realiza a partir de factorías. El método de creación de un control es:
/**
* gnome_db_component_new
*/
GtkObject *
gnome_db_component_new (const gchar *id)
{
GnomeDbComponent* comp;
gchar* factory_id;
g_return_val_if_fail(id != NULL, NULL);
comp = GNOME_DB_COMPONENT(gtk_type_new(gnome_db_component_get_type()));
factory_id = g_strdup_printf("component-factory:%s", id);
comp->factory = bonobo_generic_factory_new(factory_id, component_factory, NULL);
return comp;
}Cuando se crea un componente se le asocia una factoría de componentes a partir de la cual poder crear nuevas instancias del mismo.