Documentos compuestos

A la hora de crear documentos compuestos utilizando Bonobo, Bonobo Embeddable, Empotrables Bonobo, es la clase de componentes Bonobo a utilizar para lograr una integración óptima entre las distintas partes de un documentos: gráficos, celdas de una hoja de cálculo, registros de una base de datos o cualquier otro tipo de contenidos que nos podamos utilizar dentro de un documento.

A diferencia de los controles Bonobo que son mucho más autónomos en su funcionamiento, los empotrables de Bonobo deben integrarse visualmente dentro del documento sis apreciarse que son objetos diferentes.

La motivación que nos ha llevado a escribir este capítulo del libro ha sido el poder utilizar las gráficas empotrables que nos devuelve Guppi.

A lo largo de todo el documento hablaremos de contenedores, empotrables y controles. Todos estos conceptos se refieren a la arquitectura de Bonobo por lo que evitaremos poner la coletilla Bonobo en todos ellos.

Los componentes empotrables Bonobo nos abren toda la potencia de la integración visual, de persistencia y de impresión de diferentes aplicaciones, desarrolladas incluso en diferentes lenguajes de programación. La potencia de esta plataforma veremos que es enorme y tras leer este completo artículo, se estará en disposición de comenzar a programar utilizando Bonobo. ¿Preparado para el viaje?

Introducción a los Documentos Compuestos

Una de las motivaciones principales a la hora de crear la arquitectura de componentes Bonobo fue facilitar el desarrollo de aplicaciones centradas en la creación de documentos.

Para el usuario final de la aplicación, lo que ofrece el sistema es un entorno de creación de documentos. Dichos documentos pueden incluir contenidos de muy diferentes tipos como imágenes, hojas de cálculo, bases de datos, texto, sonido o incluso vídeos, pero el tratamiento de todos los contenidos para el usuario final deberá de ser el mismo: se selecciona un tipo de contenido, se inserta en el documento de trabajo, y este contenido se integra visualmente dentro del documento como una parte más del mismo.

A la hora de trabajar con un determinado objeto, el usuario pulsará sobre él para activarlo, lo que provocará normalmente que la barra de herramientas de la aplicación de creación de documentos se especialice para tratar ese tipo determinado de objetos.

Pongamos un ejemplo que es posible se haga realidad en el futuro. Estamos trabajando dentro de AbiWord, el procesador de textos de GNOME, e introducimos un nuevo empotrable que es un conjunto de celdas de Gnumeric. Estas celdas aparecerán como una parte integrada dentro del documento. Al hacer "click" sobre ellas, la barra de herramientas (toolbar) de AbiWord será sustituida por la de Gnumeric y el usuario podrá utilizar esta barra de herramientas para obtener la funcionalidad específica que proporciona la herramienta Gnumeric.

Un ejemplo ya real es la forma en la que Gnumeric integra gráficos dentro de las hojas de cálculo utilizando empotrables Guppi. En la siguiente imagen podemos ver una captura de pantalla de como se integra la gráfica dentro de la hoja de cálculo.

A lo largo de las secciones siguientes del documento vamos a explicar como interacciona el contenedor (AbiWord en el primer ejemplo y Gnumeric en el segundo) con los componentes empotrados que residen dentro de él.

Figura 5. Gnumeric con Guppi empotrado

Gnumeric con Guppi empotrado

Interfaces de comunicación entre contenedor y empotrable

En la siguiente figura podemos observar las interfaces de comunicación que se establecen entre el contenedor y el empotrable. Toda la interacción entre ambos se realiza a través de estas interfaces, parte de las IDL de Bonobo.

Figura 6. Comunicación contenedor y empotrable

Comunicación contenedor y empotrable

Cada empotrable (Bonobo::Embeddable) que se visualiza dentro del contenedor necesita tener un sitio en el contenedor. Este sitio es el "Bonobo::ClientSite". Un empotrable se puede visualizar en diferentes vistas dentro del mismo contenedor. Todas las vistas mostrarán el mismo empotrable, pero pueden estar situadas en diferentes localizaciones del documento. Para dar soporte a esta funcionalidad, cada empotrable se visualiza dentro del contenedor a través de una vista (Bonobo::View) que está asociada a un lugar de visualización (Bonobo::Frame) dentro del sitio reservado en el contenedor para el empotrable (Bonobo::ClientSite).

Es este un buen ejemplo de la separación entre el modelo de datos, del que hay una copia única en el empotrable, y el modelo de visualización, del que puede haber múltiples copias, con visualizaciones diferentes. Pero los datos se comparten.

Documentación y ejemplos de documentos compuestos

Ha llegado el momento de ver como se programa un empotrable y como se inserta dentro de un contenedor. Podemos intentar realizar un análisis de como se realiza dentro de Gnumeric o bien, ver los ejemplos que acompañan a Bonobo.

Vamos a seguir este segundo camino inicialmente, intentando utilizar como empotrable a Guppi. Como contenedor nos valdrá uno génerico.

Lo primero es bajarnos las fuentes de bonobo, la implementación en C de las interfaces IDL de Bonobo, ya que es en ellas donde vienen los ejemplos. En muchas ocasiones, los paquetes que instalamos ya no tienen los ejemplos que acompañan a las fuentes.

    acs@infra:~/debian-src$ apt-get source bonobo
Reading Package Lists... Done
Building Dependency Tree... Done
Need to get 1560kB of source archives.
0% [Connecting to red-carpet.ximian.com (129.250.184.229)]
Fetched 1560kB in 5m24s (4809B/s)
dpkg-source: extracting bonobo in bonobo-1.0.19
      

Ya dentro de las fuentes, lo mejor suele ser irse a las demos si es que existen, o buscar programas de test que también suelen ser una buena fuente de ejemplos. En el caso de Bonobo tenemos un directorio con ejemplos (samples) e incluso uno para ejemplos de documentos compuestos (compound-doc).

Es conveniente compilar todas las fuentes, aunque no hay que instalarlas porque se supone que ya tenemos Bonobo en nuestra máquina, para poder tener un entorno de desarrollo en el que poder modificar tranquilamente los ejemplos y no tener problemas a la hora de compilarlos.

Junto con los ejemplos de Bonobo en este caso tenemos también la cada vez más completa documentación del API de Bonobo, cuya versión más actualizada he localizado en las páginas de Michael Meeks. En concreto de esta API ahora nos interesa la parte referente a Documentos Compuestos Bonobo. Se divide esta sección en tres grandes grupos: interfaces del modelo, de la vista e impresión.

Interfaces del modelo (datos)

Aquí se agrupa la interfaz del empotrable BonoboEmbeddable, la del contenedor BonoboClientSite y BononoItemContainer. Sobre esta última decir que esta siendo sustituida por una versión basada en monikers llamada BonoboItemHandler. Esta interfaz es la parte visible de una implementación simple para contenedores de documentos compuestos.

Interfaces de la vista

Impresión

El primer ejemplo de un contenedor

Nos vamos a las fuentes de bonobo y en concreto, al directorio "samples/compound-doc".

En este directorio nos encontramos con un empotrable que es un componente de dibujo (paint-component-simple.c), un ejemplo de uso de BonoboCanvasItem que nos permite crear empotrables a partir de GnomeCanvas, un directorio "container" con un ejemplo de un contenedor y un directorio "bonobo-hello" con un empotrable que muestre el típico "Hello World" pero dentro de un empotrable que reside en un contenedor. ¿Listo para comenzar la aventura?

Como siempre, empezaremos con el método "main" e iremos siguiendo el flujo de ejecución del programa. Ya en anteriores apartados hemos dibujado el análisis conceptual y más orientado a objetos de los documentos compuestos, por lo que el enfoque de seguir el flujo de ejecución del programa nos dará un nuevo punto de vista que facilitará la compresión.

El método "main" se encuentra en "container.c":

	  int
main (int argc, char **argv)
{
	setup_data_t init_data;
	CORBA_Environment ev;
	CORBA_ORB orb;

	free (malloc (8));

 	CORBA_exception_init (&ev);
	gnome_init_with_popt_table ("container", VERSION,
				    argc, argv, oaf_popt_options, 0, &ctx);

	orb = oaf_init (argc, argv);

	if (bonobo_init (orb, NULL, NULL) == FALSE)
		g_error (_("Could not initialize Bonobo!\n"));
	

Hasta el momento, tan sólo hemos inicializado CORBA y Bonobo, algo necesario en cualquier aplicación que haga uso de Bonob.


	  init_data.app = sample_app_create ();
	

En esta función es donde es previsible que esté la parte de arranque de toda la aplicación, y será la que pasaremos a analizar después de terminar "main".


	  if (ctx)
		init_data.startup_files = (const char**) poptGetArgs (ctx);
	else
		init_data.startup_files = NULL;
	

Aquí lo único que hacemos es ver si se han pasado argumentos por la línea de comandos, en cuyo caso los procesamos.


	  gtk_idle_add ((GtkFunction) final_setup, &init_data);
	  

Como siempre, lanzamos la aplicación de forma retardada para darnos tiempo a entrar en el bucle principal de bonobo. El arranque de la aplicación


	  bonobo_main ();
	

Nos vamos al bucle principal de bonobo a la espera de eventos.


	  if (ctx)
		poptFreeContext (ctx);

	return 0;
}
	

Cuando salgamos del bucle principal de bonobo por alguna condición de terminación, liberamos el contexto y salimos retornando "0" indicando ejecución correcta.

Bien, vamos a centrarnos ahora en los métodos que realmente hacen el trabajo: sample_app_create y final_setup.

sample_app_create

	   static SampleApp *
sample_app_create (void)
{
	SampleApp *app = g_new0 (SampleApp, 1);
	  

Este método lo que nos devuelve es un SampleApp el cual esta definido dentro de "container.h" y su estructura es:

typedef struct _SampleApp        SampleApp;
struct _SampleApp {
	BonoboItemContainer *container;
	BonoboUIContainer   *ui_container;

	BonoboViewFrame     *curr_view;
	GList               *components;

	GtkWidget           *app;
	GtkWidget           *box;
	GtkWidget           *fileselection;
};
	  

Es importante tener en mente esta estructura ya que incluye todos los parámetros que utilizaremos para gestionar los empotrables que vayamos incorporando al contenedor. Sigamos con el método "sample_app_create".

	GtkWidget *app_widget;

	/* Create widgets */
	app_widget = app->app = bonobo_window_new ("sample-container",
						   _("Sample Bonobo container"));

Esta llamada es la que crea el contenedor Bonobo real. Vemos que basta con hacer una llamada para tener una GtkWindow pero con todo el soporte de Bonobo detrás, por lo que el uso de contenedores Bonobo es bastante sencillo.

	app->box = gtk_vbox_new (FALSE, 10);

	gtk_signal_connect (GTK_OBJECT (app_widget), "delete_event",
			    (GtkSignalFunc) delete_cb, app);

	/* Do the packing stuff */
	bonobo_window_set_contents (BONOBO_WINDOW (app->app), app->box);
	gtk_widget_set_usize (app_widget, 400, 600);
	  

Asociamos a los contenidos de la BonoboWindow el GtkWidget box, un contenedor vertical de Gtk en el que vamos a ir poniendo todos los empotrables que vayan incorporando al contenedor. Este es un contenedor simple de Gtk y no tiene nada de Bonobo.


	app->container = bonobo_item_container_new ();

	app->ui_container = bonobo_ui_container_new ();
	bonobo_ui_container_set_win (app->ui_container, BONOBO_WINDOW (app->app));
	  

De nuevo, otra de las partes claves para tener control sobre todos los empotrables que vayan metiendo en el contenedor Bonobo. En este caso creamos un BonoboUIContainer, que como ya veremos, es el que nos va a permitir controlar detalles de la interfaz gráfica del contenedor Bonobo en función de los empotrables, como la gestión de la barra de herramientas.

	   /* Crear menu bar */
	sample_app_fill_menu (app);

	gtk_widget_show_all (app_widget);

	return app;
}
	  

La interfaz gráfica la completamos con un menú desde el que poder cargar los componentes y una barra de herramientas con menús. Esto es en su mayoría programación normal Gtk. Vemos una captura de pantalla para ver el resultado:

Figura 7. GUI del contenedor Bonobo

GUI del contenedor Bonobo

Pero aquí también se incluye el código que permite cambiar la barra de herramientas y menús en función de que componente empotrable este activo. Vamos pues a buscar este código ya que es muy importante para la programación Bonobo. Esta función está implementada en "container-menu":

void
sample_app_fill_menu (SampleApp *app)
{
	Bonobo_UIContainer corba_container;
	BonoboUIComponent *uic;

	uic = bonobo_ui_component_new ("sample");
	  

Creamos un nuevo componente de UI de Bonobo, el cual va a ser nuestra barra de menú. Esta barra se va a ir cambiando en función de que componente empotrable tengamos activado.

	corba_container = BONOBO_OBJREF (app->ui_container);
	bonobo_ui_component_set_container (uic, corba_container);
	  

Asociamos el menú Bonobo al contenedor Bonobo donde vamos a ir incorporando los empotrables.

	bonobo_ui_component_set_translate (uic, "/", ui_commands, NULL);
	bonobo_ui_component_set_translate (uic, "/", ui_data, NULL);
	  

De la API de Bonobo, que viene incluída junto con las fuentes que nos hemos bajado, y que está perfectamente organizada y completada en casi todas sus partes, vemos que estas funciones que son parte de BonoboUIComponent son las responsables de que podamos traducir los contenidos de los menús. No vamos a entrar aquí en más detalles aunque en la API aparece como se debe de utilizar.


	bonobo_ui_component_add_verb_list_with_data (uic, sample_app_verbs, app);
}
	  

Esta última llamada añade el contenido de los menús. Estos menús son:

	   static BonoboUIVerb sample_app_verbs[] = {
	BONOBO_UI_VERB ("AddEmbeddable", verb_AddEmbeddable_cb),
	BONOBO_UI_VERB ("FileOpen", verb_FileLoad_cb),
	BONOBO_UI_VERB ("FileSaveAs", verb_FileSaveAs_cb),
	BONOBO_UI_VERB ("PrintPreview", verb_PrintPreview_cb),
	BONOBO_UI_VERB ("XmlDump", verb_XmlDump_cb),
	BONOBO_UI_VERB ("FileExit", verb_FileExit_cb),
	BONOBO_UI_VERB ("HelpAbout", verb_HelpAbout_cb),
	BONOBO_UI_VERB_END
};
	  

Vemos que al aplicación se controla en gran medida desde estos menús, que provocan callbacks a funciones según la opción que hayamos seleccionado.

Volvemos de nuevo al método "main" de "container.c" para finalizar con su inicialización, algo que se hace dentro de "final_setup".

final_setup

Este método realiza cosas interesantes en el caso de que tengamos activadas las pruebas de Monikers, algo que de momento no vamos a realizar, por lo que podemos ignorar este método.

Añadir empotrables al contenedor

Después de tener ya un contenedor Bonobo totalmente preparado para recibir empotrables e incorporarlos a sus contenidos, vamos a ver como se hace esto. Del menú del contenedor lo podemos deducir de forma sencilla ya que hay un verbo:

	BONOBO_UI_VERB ("AddEmbeddable", verb_AddEmbeddable_cb)
      

Es decir, que cuando pulsemos sobre "AddEmbeddable" se va a ejecutar el método de callback "verb_AddEmbeddable_cb". Veamos que es lo que hace este método:

static void
verb_AddEmbeddable_cb (BonoboUIComponent *uic, gpointer user_data, const char *cname)
{
	SampleApp *inst = user_data;
	char *required_interfaces [2] =
	    { "IDL:Bonobo/Embeddable:1.0", NULL };
      

Aquí indicamos la interfaz IDL requerida por aquellos componentes Bonobo que podemos empotrar en el contenedor. En este caso, sólo aquellos que implementen la interfaz de empotrables, claro.

	char *obj_id;

	/* Ask the user to select a component. */
	obj_id = bonobo_selector_select_id (
		_("Select an embeddable Bonobo component to add"),
		(const gchar **) required_interfaces);
      

Esta es una función muy útil de Bonobo que nos abre un dialogo con el usuario final para que seleccione que tipo de empotrable quiere añadir. Esta función localiza todos los empotrables disponibles por lo que si añadimos nuevos empotrables al sistema, los podremos luego cargar utilizando esta función. En realidad, y pensando en el usuario final, lo suyo sería que no se especificara el empotrable si no el objeto que se quiere insertar en el documento de trabajo. En función del tipo de objeto, se consultaría que empotrable lo puede visualizar, y se cargaría el objeto a través de él. Veamos que aspecto tiene este dialogo:

Figura 8. Selección de componente Bonobo empotrable

Selección de componente Bonobo empotrable

Esta función nos devuelve un identificador único para los objetos asociados a este empotrable de forma que podremos utilizarlo a partir de ahora como sinónimo de este tipo de objetos Bonobo.

	if (!obj_id)
		return;

	/* Activarlo. */
	sample_app_add_component (inst, obj_id);
      

Vamos con la inserción del empotrable, uno de los procesos claves dentro de esta sección, y para ello, nos toca analizar a fondo esta función.

static SampleClientSite *
sample_app_add_embeddable (SampleApp          *app,
			   BonoboObjectClient *server,
			   char               *object_id)
{

De momento ya vemos por aquí la aparición en escena de los ClientSite, en concreto el parámetro de retorno de esta función es SampleClientSite que si analizamos su estructura, que aparece dentro de "component.h":

struct _SampleClientSite {
	BonoboClientSite parent;

	SampleApp *app;
	gchar     *obj_id;

	GtkWidget *widget;
	GtkWidget *views_hbox;
	GtkWidget *frame;
};
      

Ya iremos viendo el uso que le damos a los diferentes campos del ClientSite asociado a un componente, en este caso, a componentes Bonobo empotrables. Sigamos con "sample_app_add_embeddable":

	SampleClientSite *site;

	/*
	 * El ClientSite es el punto de contacto del lado del contenedor
	 * para un Embeddable.  Luego hay una correspondencia uno-a-uno
	 * entre BonoboClientSites y BonoboEmbeddables.
	 */
	site = sample_client_site_new (app->container, app,
				       server, object_id);
      

Acabamos de crear el ClientSite en el contenedor por el que se comunicarán el contenedor y el empotrable. Veamos los detalles de esta creación:

	SampleClientSite *
sample_client_site_new (BonoboItemContainer *container,
			SampleApp           *app,
			BonoboObjectClient  *embeddable,
			const char          *embeddable_id)
{
	SampleClientSite *site;

	g_return_val_if_fail (app != NULL, NULL);
	g_return_val_if_fail (embeddable_id != NULL, NULL);
	g_return_val_if_fail (BONOBO_IS_OBJECT_CLIENT (embeddable), NULL);
	g_return_val_if_fail (BONOBO_IS_ITEM_CONTAINER (container), NULL);
      

Tratamiento de los parámetros que recibimos para asegurarnos que son todos correctos.

	site = gtk_type_new (sample_client_site_get_type ());

	site = SAMPLE_CLIENT_SITE (bonobo_client_site_construct (
		BONOBO_CLIENT_SITE (site), container));
      

La clase SampleClientSite hereda de BonoboClientSite y para construir un objeto de esta clase, utilizamos la llamada de bonobo "bonobo_client_site_construct", asociando el BonoboClientSite creado al contenedor Bonobo en el que van a residir todos los empotrables y que ya analizamos en el anterior apartado.

	if (site) {
		bonobo_client_site_bind_embeddable (BONOBO_CLIENT_SITE (site),
						    embeddable);

Sin duda, la llamada más importante de esta función, cuando se asocia el BonoboClientSite al Embeddable. A partir de este momento se abren las comunicaciones entre el contenedor y el empotrable y podemos comenzar a visualizar el empotrable, crear nuevas vistas ...

	        bonobo_object_unref (BONOBO_OBJECT (embeddable));

El control de referencias a objetos Bonobo es muy importante si no queremos tener agujeros de memoria, aunque en este caso no entiendo porqué tenemos que quitar una referencia al embeddable. Quizá sea debido a que al enlazar el empotrable y el contenedor, el contenedor indica una nueva referencia dentro de él al empotrable. La referencia que teníamos al empotrable fuera del contenedor ya no nos sirve para nada y no la vamos a utilizar, por lo que informamos el objeto empotrable que quite una referencia entre las que tiene.


	site->app = app;
		g_free (site->obj_id);
		site->obj_id = g_strdup (embeddable_id);

		site_create_widgets (site);

De nuevo, una llamada a una función que como vamos a ver, ya sólo trata con Gtk para crear una interfaz gráfica en la que insertar el nuevo empotrable, en realidad, para poder ir insertando las vistas que vayamos creando del empotrable. Aunque veremos que aún en este método, hay algo de Bonobo también:

static void
site_create_widgets (SampleClientSite *site)
{
	GtkWidget *frame;
	GtkWidget *vbox, *hbox;
	GtkWidget *new_view_button, *del_view_button;
	GtkWidget *del_comp_button, *fill_comp_button;

	g_return_if_fail (site != NULL);

	/* Display widgets */
	frame = site->frame = gtk_frame_new (site->obj_id);
	vbox = gtk_vbox_new (FALSE, 10);
	hbox = gtk_hbox_new (TRUE, 5);
	new_view_button = gtk_button_new_with_label ("New view");
	del_view_button = gtk_button_new_with_label ("Remove view");
	del_comp_button = gtk_button_new_with_label ("Remove component");
	/* The views of the component */
	site->views_hbox = gtk_hbox_new (FALSE, 2);
	gtk_signal_connect (GTK_OBJECT (new_view_button), "clicked",
			    GTK_SIGNAL_FUNC (add_frame_cb), site);
	gtk_signal_connect (GTK_OBJECT (del_view_button), "clicked",
			    GTK_SIGNAL_FUNC (del_frame_cb), site);
	gtk_signal_connect (GTK_OBJECT (del_comp_button), "clicked",
 			    GTK_SIGNAL_FUNC (del_cb), site);

	gtk_container_add (GTK_CONTAINER (hbox), new_view_button);
	gtk_container_add (GTK_CONTAINER (hbox), del_view_button);
	gtk_container_add (GTK_CONTAINER (hbox), del_comp_button);
      

Hasta el momento hemos creado un nuevo GtkFrame, frame, en que vamos a ir añadiendo las vistas del empotrable. Para poder ir gestionando las vistas y el empotrable, añadimos tres botones con las funcionalidades necesarias. Lo más importante de esta parte son las funciones que se llaman en caso de pulsar cada uno de los botones, funcionalidad que analizaremos en los siguientes apartados.

	if (bonobo_object_client_has_interface (
		bonobo_client_site_get_embeddable (BONOBO_CLIENT_SITE (site)),
		"IDL:Bonobo/PersistStream:1.0", NULL)) {

En el caso de que el componente empotrable implemente la interfaz de persistencia, podemos cargar los datos del empotrable desde un "stream". Si volvemos a pensar en el usuario final, este querrá introducir en su documento de trabajo un objeto, que podrían ser los datos persistentes de un componente empotrable. Los pasos que daríamos serían pues localizar el empotrable que es capaz de cargar dicho objeto y, utilizando la interfaz de persistencia de ese empotrable, cargar los contenidos del objeto dentro del empotrable.


	fill_comp_button =
		    gtk_button_new_with_label ("Fill with stream");
		gtk_container_add (GTK_CONTAINER (hbox), fill_comp_button);

		gtk_signal_connect (GTK_OBJECT (fill_comp_button),
				    "clicked", GTK_SIGNAL_FUNC (fill_cb),
				    site);
	}

Creamos un botón que nos permite cargar los datos persistentes del componente empotrable. Lo más importante de esta parte es de nuevo la función a la que se llama "fill_cb", que cubriremos en los próximos apartados.



	gtk_container_add (GTK_CONTAINER (vbox),  site->views_hbox);
	gtk_container_add (GTK_CONTAINER (vbox),  hbox);
	gtk_container_add (GTK_CONTAINER (frame), vbox);
}
      

Hemos terminado de construir el widget asociado al empotrable y volvemos el método de creación de un nuevo SampleClientSite.

	}

	return site;
}
      

Bueno, visto como se crea el SampleClientSite, continuemos con el método ""sample_app_add_embeddable".

	app->components = g_list_append (app->components, site);
      

Mantenemos un listado con todos los componentes empotrables que hemos ido añadiendo ya que si luego hay que salir de la aplicación, tenemos que tener cuidado de irlos liberando todos.

	gtk_box_pack_start (GTK_BOX (app->box),
			    sample_client_site_get_widget (site),
			    FALSE, FALSE, 0);
      

Vemos lo sencillo que es añadir el nuevo empotrable al contendor GTK que está viendo el usuario final. Aunque esta sencillez es gracias al método "sample_client_site_get_widget" que es parte de la clase "SampleClientSite", implementada en component.c":

	GtkWidget *
sample_client_site_get_widget (SampleClientSite *site)
{
	g_return_val_if_fail (site != NULL, NULL);

	return site->frame;
}
      

Esta función es realmente sencilla ya que dentro del constructor de SampleClientSite ya habíamos hecho todo el trabajo de construcción del GUI, por lo que aquí sólo nos queda el acceder a él. Volvemos al método "sample_app_add_embeddable" para terminar de añadir el empotrable al contenedor principal.

	sample_client_site_add_frame (site);
      

Llegamos a uno de los métodos que más carga de Bonobo tienen ya que tiene que crear una nueva vista del empotrable, lo que significa crear un View dentro del empotrable y un ViewFrame dentro del contenedor. Vamos a analizar el método con cuidado:

void
sample_client_site_add_frame (SampleClientSite *site)
{
	BonoboViewFrame *view_frame;
	GtkWidget       *view_widget;

	/*
	 * Creamos la View remota del empotrable y el ViewFrame local
	 * del contenedor. Esto también estable el BonoboUIHandler para
         * este ViewFrame. De esta forma, el componente empotrado puede
	 * tener acceso a nuestro servidor UIHandler de forma que pueda
         * mezclar sus menús y barra de herramientas cuando es activado
	 */
	view_frame = bonobo_client_site_new_view (BONOBO_CLIENT_SITE (site),
		BONOBO_OBJREF (site->app->ui_container));

	/*
	 * Empotrar la view frame en la aplicación
	 */
	view_widget = bonobo_view_frame_get_wrapper (view_frame);
	gtk_box_pack_start (GTK_BOX (site->views_hbox), view_widget,
			    FALSE, FALSE, 5);

	/*
	 * La señal "user_activate" se emitirá cuando el usuario
         * haga doble click en la "cubierta" de la vista. Esta
         * cubierta es una ventana transparente que está
         * encima del componente y captura cualquier evento
         * (teclado, ratón) que se produce sobre la cubierta.
         * Cuando el usuario hace doble click sobre la cubierta
         * el contenedor (nosotros en este momento) puede decidir
         * activar el componente
	 */
	gtk_signal_connect (GTK_OBJECT (view_frame), "user_activate",
			    GTK_SIGNAL_FUNC (activate_request_cb),
			    site);

	/*
	 * La activación "In-place" de un componente es un proceso
	 * en dos pasos. Después de que el usuario haga doble click
         * en el componente, nuestro callback de la señal
         * (component_user_activate_request_cb()) pide al componente
	 * que se active (ver  bonobo_view_frame_view_activate()).
	 * El componente puede entonces elegir aceptar o rechazar
         * la activación. Cuando un componente empotrado nos informa
         * de su decisión de pasar a estado activo, la señal "activated"
         * se emite desde el view frame. En ese momento quitamos la
	 * cubierta del componente para que pueda recibir eventos.
	 */
	gtk_signal_connect (GTK_OBJECT (view_frame), "activated",
			    GTK_SIGNAL_FUNC (view_activated_cb),
			    site);

	/*
	 * La señal "user_context" se emite cuando el usuario pulsa el
	 * botón derecho en la cubierta. Lo usamos para mostrar un menu verb
	 */
	gtk_signal_connect (GTK_OBJECT (view_frame), "user_context",
			    GTK_SIGNAL_FUNC (component_user_context_cb),
			    site);

	/*
	 * Mostrar el component,
	 */
	gtk_widget_show_all (view_widget);
}
      

Por fin hemos terminado de añadir el empotrable a nuestro contenedor, proceso en el que hemos aprendido mucho de empotrables, algo que vamos a ir asentando en las próximas secciones de este capítulo.

	gtk_widget_show_all (GTK_WIDGET (app->box));

	return site;
}
      

Para terminar el método "verb_AddEmbeddable_cb":


	g_free (obj_id);
}
      

Y ojo, no nos olvidemos de no ir dejando memoria sin liberar ;-)

Ya hemos visto todo el código del contenedor necesario para incluir componentes empotrables, asi como el código necesario para introducir dentro del contenedor un componente Bonobo empotrable. Ahora vamos a intentar ir resaltando las partes más importantes para terminar mostrando como crear los empotrables, los componentes que finalmente se empotran en Bonobo.

Vistas de un Empotrable

En la siguiente figura podemos ver al contenedor Bonobo con varias vistas de un mismo componente empotrable que, como se puede apreciar, tienen todas las vistas el mismo contenido.

Figura 9. Varias vistas de un mismo empotrado

Varias vistas de un mismo empotrado

Vamos a ir viendo como se logra esta funcionalidad tan potente con Bonobo.

Las interfaces ClientSite y Embeddable

Como ya hemos visto, para poder introducir un empotrable dentro de un contenedor, algo que se realizaba dentro de la función "sample_app_add_embeddable", lo primero que necesitamos es reservar dentro del contenedor un sitio para el empotrable. Este sitio se crea con un "BonoboClientSite", en nuestro caso SampleClientSite que tiene más información, y la llamada que nos ha permitido crear un "BonoboClientSite" asociado a un contenedor ha sido:

	site = SAMPLE_CLIENT_SITE (bonobo_client_site_construct (
		BONOBO_CLIENT_SITE (site), container));
      

Ya tenemos un "site" (BonoboClientSite) preparado para recibir un componente empotrable. Vamos a decirle cual es, algo que hacemos con la llamada:

	bonobo_client_site_bind_embeddable (BONOBO_CLIENT_SITE (site),
						    embeddable);
      

De esta forma hemos puesto en comunicación el contenedor con el empotrable, pero esta comunicación aún no ha provocado la visualización del empotrable dentro del contenedor. Aún es necesario que creemos las vistas del empotrable dentro del contenedor.

Las interfaces ViewFrame y View

Una vez que tenemos asociados un contenedor y un empotrable, podemos insertar tantas vistas (View) del empotrable en el contenedor como queremos. Todas las vistas tendrán los mismos contenidos de origen, aquellos que tenga el empotrable. Para que el contenedor sea capaz de gestionar todas estas vistas, cada una de las "View" de un empotrable es necesaria que esté asociada a un "ViewFrame" dentro del contenedor. Estos "ViewFrame" se crean asociados a un ClientSite, como podemos ver en el siguiente código del ejemplo:

	  BonoboViewFrame *view_frame;
	  view_frame = bonobo_client_site_new_view (BONOBO_CLIENT_SITE (site),
		BONOBO_OBJREF (site->app->ui_container));
	

La llamada, junto con el "BonoboClientSite", incluye un Bonobo_UIContainer, que como luego veremos, es el que permite integrar los menús y la barra de herramientas del empotrable con el del contenedor. Esta llamada de forma automática crea una nueva vista (View) del empotrable remoto, y utilizando el ViewFrame, nosotros vamos a poder decidir en que parte de la GUI se muestra el ViewFrame. Para ello, utilizamos la siguiente llamada que nos permite obtener el widget asociado al ViewFrame, que en realidad, es el widget asociado a la vista del empotrable que queremos visualizar.

	  GtkWidget       *view_widget;
	  view_widget = bonobo_view_frame_get_wrapper (view_frame);
	

Ahora ya tenemos un widget Gtk el cual colocamos dentro del GUI utilizando las técnicas habituales de diseño de GUI con Gtk.

	 gtk_box_pack_start (GTK_BOX (site->views_hbox), view_widget,
			    FALSE, FALSE, 5);
	 gtk_widget_show_all (view_widget);
	

donde el contenedor "site->views_hbox" en un contenedor que coloca los widgets en horizontal, y que muestra todas las vistas que vayamos añadiendo. Este contenedor a su vez se ha incluído dentro del contenedor principal.

Es muy importante el tratamiento de las señales que se hace de este widget. Los componentes empotrados están desactivados por defecto. Ello significa que el usuario trabaja únicamente con el documento global o con alguno de los componentes empotrados. Según lo que esté activo, tendremos por ejemplo una barra de menú o una barra de herramientas. Veremos con detalle la importancia de estas señales a la hora de activar y desactivar los componentes empotrables.

Integración de menús y barras de herramientas

Pongamos en el papel de nuestro usuario final. Tiene abierto un documento de trabajo en el que tiene empotrados varios componentes. Por lo general, al usuario lo que le importa es como se visualizan esos componentes y como salen colocados dentro del documento global. Pero el usuario también quiere poder interactuar con dichos componentes, por ejemplo, para modificar los datos de una gráfica o para modificar una imágen insertada en el documento. Quizá incluso quiera modificar la hoja de cálculo al recibir nuevos datos sobre las ventas del último cuatrimestre.

La idea es que todos estos cambios los pueda hacer el usuario sin necesidad de salir el documento. Para ello, el componente debe de poder presentar una interfaz al usuario capaz de permitirle trabajar y modificar el componente. Ello se logra gracias a la capacidad de los contenedores Bonobo de integrar la barra de menús y de herramientas de los empotrados dentro del contenedor principal. En la siguiente captura podemos ver varios componentes empotrados dentro del contenedor, pero ninguno de ellos está activado aún. Podemos observar la barra de menús que tenemos disponible en este caso.

Figura 10. Barra de menús sin componente empotrado activo

Barra de menús sin componente empotrado activo

Cuando un usuario activa un componente empotrado, haciendo doble click sobre él normalmente, una de las acciones que lleva a cabo Bonobo es la de cambiar los menús y la barra de herramientas por aquellos que diga el componente. De esta forma el usuario podrá interactuar con el componente y cambiarlo. Cuando finalice, podrá volver a activar el documento global, recuperando los menús y barra de herramientas originales. Podemos observar como cuando tenemos activo un empotrable de dibujo, nos aparece una nueva opción en la barra de menús para seleccionar el color de trazado.

Figura 11. Barra de menús con componente empotrado activo

Barra de menús con componente empotrado activo

La gestión de los menús y la barra de herramientas se lleva a través de un BonoboUIContainer, elemento que hemos utilizado a la hora de crear nuestro contenedor. Dentro de nuestra clase SampleApp, que representa a la aplicación globalmente, junto con el contenedor, la vista actual y algunos widgets, tenemos un BonoboUIContainer, que es el que utilizamos a la hora de crear los menús y barra de herramientas.

	struct _SampleApp {
	BonoboItemContainer *container;
	BonoboUIContainer   *ui_container;

	BonoboViewFrame     *curr_view;
	GList               *components;

	GtkWidget           *app;
	GtkWidget           *box;
	GtkWidget           *fileselection;
};
      

Este BonoboUIContainer se crea justo después de crear el contenedor principal:

	app->container = bonobo_item_container_new ();

	app->ui_container = bonobo_ui_container_new ();
	bonobo_ui_container_set_win (app->ui_container, BONOBO_WINDOW (app->app));
      

Pero de momento, tenemos el BonoboUIContainer vacío, sin los datos de los menús y de la barra de herramientas. Esta última no existe en nuestro caso, por lo que nos vamos a centrar en los menús. Dentro del método "sample_app_fill_menu" es donde se toman los datos del menú y se llena. Remitimos al lector a la descripción que se dió en el análisis del código del ejemplo para entender como se definen los contenidos de los menús.

Activación de la vista de un Empotrable

Como ya hemos visto, uno de los momentos más importantes es cuando se activa un empotrable. En ese momento, el usuario tiene que percibir que dicho empotrable ha pasado a estar activo, se deben de sustituir los menús y barras de herramientas por las del empotrable, y este debe de comenzar a recibir los eventos que el usuario provoque sobre él. Podemos ver en la siguiente figura como se muestra que una vista está activada, al aparecer alrededor de ella un borde más grueso.

Figura 12. Barra de menús con componente empotrado activo

Barra de menús con componente empotrado activo

Cuando una vistas de un empotrable está desactivada, una capa cubre la zona que ocupa dicha vistas. Esa capa captura todos los eventos que se produzcan en dicha zona. En nuestro caso tratamos dos tipos de eventos. La activación del empotrable al hacer un doble click, o la presentación de un menú si se pulsa el botón derecho sobre el empotrable. Centremos únicamente en la activación:

gtk_signal_connect (GTK_OBJECT (view_frame), "user_activate",
			    GTK_SIGNAL_FUNC (activate_request_cb),
			    site);
      

Vemos que en realidad, la señal se captura utilizando el ViewFrame, el cual es el representante de la vista del empotrable dentro del contenedor. Cuando el usuario provoca "user_activate", que en este caso es hacer doble-click, el componente pasa a ser activado dentro de la función de callback "activate_request_cb". Esta función es parte de "component.c".

	static void
activate_request_cb (BonoboViewFrame  *view_frame,
                     SampleClientSite *site)
{
        SampleApp *app;

        g_return_if_fail (site != NULL);
        g_return_if_fail (site->app != NULL);

        app = site->app;

        if (app->curr_view) {
                bonobo_view_frame_view_deactivate (app->curr_view);
                if (app->curr_view)
                        bonobo_view_frame_set_covered (app->curr_view, FALSE);
        }

        /*
	 * Se activa la View sobre la que el usuario ha hecho click.
         * Esto provoca una petición a la View empotrada para que se active.
	 * Si la View acepta la activación, se lo notifica al ViewFrame
	 * el cual llama a view_activated_cb callback.
         *
         * No descubrimos aquí la View porque puede rechazar la activación.
         * Esperamos a descubrir la vista cuando esta acepta la activación.
         */
        bonobo_view_frame_view_activate (view_frame);
}

Bueno, pues a ver el tratamiento que se hace cuando la vista a aceptado ser activada.

	static void
view_activated_cb (BonoboViewFrame  *view_frame,
		   gboolean          activated,
		   SampleClientSite *site)
{
	SampleApp *app = site->app;

	if (activated) {
		/*
	         * Si la View que pide ser activada es la actual
                 * informamos del hecho y regresamos.
		 */
		if (app->curr_view) {
			g_warning (¡"La View a activar ya está activa!");
			return;
		}

		/*
		 * En otro caso, descubrimos la vista para que pueda recibir
		 * eventos, y la ponemos como la View activa.
		 */
		bonobo_view_frame_set_covered (view_frame, FALSE);
		app->curr_view = view_frame;
	} else {
		/*
                 * Si la View pide ser desactivada, obligar siempre.
	         * Podemos haberla desactivado ya (ver user_activation_request_cb),
		 * If the View is asking to be deactivated, always
	         * pero no ocurre nada malo haciéndolo de nuevo.
                 * Siempre está la posibilidad que la vista pida ser
                 * desactivada sin que se lo haya pedido el usuario por
                 * lo que cubrimos la vista siempre aquí.
		 */
		bonobo_view_frame_set_covered (view_frame, TRUE);

		if (view_frame == app->curr_view)
			app->curr_view = NULL;
	}
}
      

De los comentarios del código podemos ver la secuencia de acontecimientos que se producen para poder activar la vista, así como las llamadas que se han de ir haciendo para cubrir y descubrir los componentes empotrables.

Impresión del contenedor

La impresión del contenedor para el usuario final es la impresión de todo el documento. Ello implica que el contenedor debe de saber como ir imprimiendo cada uno de los empotrados que tiene incluídos dentro de él. Y para llevar a cabo esta tarea, la única forma genérica de lograrlo para cualquier componente empotrable es que estos implementen una interfaz genérica de impresión, que es efectivamente lo que hacen. Implementan la interfaz "IDL:Bonobo/Print". El contenedor puede entonces ir barriendo todos los empotrados y decirles: tu te tienes que imprimir dentro de un contexto de impresión (la página de impresión), en esta posición y con este tamaño. Veamos como efectivamente se sigue este mecanismo.

La localización del código de impresión es sencilla. La impresión se llama desde un menú del contenedor, tal y como podemos ver en la siguiente imagen.

Figura 13. Impresión del contenedor Bonobo

Impresión del contenedor Bonobo

Por lo tanto, nos vamos a la definición de los menús dentro del contenedor, "container-menu.h", y vemos que la selección de este menú termina provocando la llamada a "verb_PrintPreview_cb". Veamos que hace este método:

	static void
verb_PrintPreview_cb (BonoboUIComponent *uic, gpointer user_data, const char *cname)
{
	SampleApp *app = user_data;

	sample_app_print_preview (app);
}
      

lo que nos lleva al método "sample_app_print_preview" que ya está dentro del fichero "container-print.h/c", que las acciones que lleva a cabo son:

	void
sample_app_print_preview (SampleApp *app)
{
	GList *l;
	double ypos = 0.0;
	GnomePrintMaster *pm;
	GnomePrintContext *ctx;
	GnomePrintMasterPreview *pv;

	pm = gnome_print_master_new ();
	ctx = gnome_print_master_get_context (pm);

	for (l = app->components; l; l = l->next) {
		BonoboClientSite *site = l->data;

		object_print (bonobo_client_site_get_embeddable (site),
			      ctx, 0.0, ypos, 320.0, 320.0);
		ypos += 320.0;
	}

	gnome_print_showpage (ctx);
	gnome_print_context_close (ctx);
	gnome_print_master_close (pm);

	pv = gnome_print_master_preview_new (pm, "Component demo");
	gtk_widget_show  (GTK_WIDGET (pv));
	gtk_object_unref (GTK_OBJECT (pm));
}
      

No nos vamos a parar en este momento a describir el sistema de impresión de GNOME, pero vemos cláramente el bucle en el que se van imprimiendo todos los empotrables con la llamada a "object_print".

Aquí debe ir una captura de pantalla *FIXME*

Persistencia del contenedor

Al igual que la con la impresión, la persistencia de un contenedor, es decir, el almacenar en un fichero o base de datos o cualquier sistema de almacenamiento persistente a nuestro contenedor Bonobo, incluye que se vayan guardando de forma persistente el estado de cada uno de los empotrables que existan, y para ello de nuevo, todos los empotrables han de implementar la interfaz "IDL:Bonobo/PersistStream".

Creación de un empotrable

Hasta el momento nos hemos centrado en el contenedor de empotrables, aunque ya hemos visto gran parte de la interacción que se produce entre empotrable y contenedor. Vamos ahora a ver un ejemplo de un empotrable. Y para ello nada mejor que coger el empotrable que pone "¡Hola Mundo!". Nos permitirá centrarnos en lo que se necesita para hacer un empotrable sin tener que despistarnos con la funcionalidad propia del empotrable. Este ejemplo va a ser especialmente interesante ya que vamos a mostrar un empotrable que implementa las interfaces:

  • IDL:Bonobo/Unknown:1.0 e IDL:Bonobo/Embeddable:1.0, como debe hacer todo componente empotrable.

  • IDL:Bonobo/PersistStream:1.0 que nos permite almacenar de forma persistente el empotrable. Esta interfaz, o Bonobo/Persist, debe de ser implementada en general por todos los empotrables ya que, cuando queramos guardar un documento, debemos de poder ir guardando todos los componentes empotrados que incluye, y la única forma de hacer esto es a través de la interfaz Bonobo/PersistStream o Bonobo/Persist. Esta interfaz permite guardar y obtener el estado de un componente utilizando "streams" (flujos) de datos.

  • IDL:Bonobo/Persist:1.0 nos permite almacenar el estado de un empotrable a un medio persistente (base de datos, fichero ...).

  • IDL:Bonobo/Print:1.0 que nos permite imprimir el empotrable y que al igual que las interfaces de persistencia, debe de ser implementado por los empotrables en general, ya que si queremos poder imprimir un documento completo, debemos de poder imprimir los empotrables, algo que se hace desde este interfaz.

La forma de obtener un empotrable "Hello" es utilizando la factoría de este tipo de empotrables. Esta factoría se haya en el fichero "bonobo-hello.c".

#include "config.h"
#include <bonobo.h>

#include "hello-embeddable.h"

static BonoboObject*
hello_embeddable_factory (BonoboGenericFactory *f, gpointer data)
{
        HelloBonoboEmbeddable *embeddable;

        embeddable = gtk_type_new (HELLO_BONOBO_EMBEDDABLE_TYPE);

        g_return_val_if_fail(embeddable != NULL, NULL);

        embeddable = hello_bonobo_embeddable_construct (embeddable);

        return BONOBO_OBJECT (embeddable);
}

BONOBO_OAF_FACTORY ("OAFIID:Bonobo_Sample_Hello_EmbeddableFactory",
                    "bonobo hello", VERSION,
                    hello_embeddable_factory,
                    NULL)
      

Como es tan común que el programa principal de un empotrable sea una factoría que llama a un método que devuelve un nuevo empotrable, ya existe hasta un MACRO que automatiza todo este código, BONOBO_OAF_FACTORY, y al que le pasamos el identificador de la factoría y el método a llamar para obtener nuevos componentes empotrables.

El método "hello_embeddable_factory" nos devuelve el empotrable recién creado. El proceso de creación lo vamos a ver en los siguientes apartados. Vamos a comenzar viendo la definición de la clase "HelloBonoboEmbeddable".

struct _HelloBonoboEmbeddable {
	BonoboEmbeddable embeddable;
	char            *text;
};
typedef struct {
	BonoboEmbeddableClass parent_class;
} HelloBonoboEmbeddableClass;
      

Vemos que es una clase muy sencilla, que hereda de BonoboEmbeddable y que sólo añade un campo extra, "text", que quizá se utilice para definir el mensaje a visualizar. Las funciones que exporta esta clase son:

GtkType hello_bonobo_embeddable_get_type  (void);

HelloBonoboEmbeddable *
        hello_bonobo_embeddable_construct (HelloBonoboEmbeddable *embeddable);
void    hello_bonobo_embeddable_set_text  (HelloBonoboEmbeddable *embeddable,
					   char                  *text);
      

Con ellas podemos crear nuevos empotrables y construirlos, y modificar el texto que visualiza un empotrable. Todo muy sencillo para que nada nos distraiga del objetivo de aprender a programar componentes Bonobo empotrables.

Vamos con la implementación de estos métodos:

static void
hello_bonobo_embeddable_class_init (BonoboEmbeddableClass *klass)
{
	GtkObjectClass *object_class = (GtkObjectClass *) klass;

	hello_bonobo_embeddable_parent_class =
		gtk_type_class (bonobo_embeddable_get_type ());

	object_class->destroy = hello_bonobo_embeddable_destroy;
}

Este es el constructor de la parte del objeto común a todos los objetos de la clase (métodos y variables estáticos, en terminología Java), que se resume en crear un nuevo objeto de la clase padre, BonoboEmbeddable.

	static void
hello_bonobo_embeddable_init (BonoboObject *object)
{
	HelloBonoboEmbeddable *embeddable = HELLO_BONOBO_EMBEDDABLE (object);

	embeddable->text = g_strdup ("Hello World");
}
      

Y aquí tenemos la parte específica por objeto del constructor, en el que vemos que se inicializa la cadena de caracteres a mostrar.

BONOBO_X_TYPE_FUNC (HelloBonoboEmbeddable,
		      bonobo_embeddable_get_type (),
		      hello_bonobo_embeddable);
      

La construcción de este tipo de clases es tan común que se han creado macros para automatizar la labor de creación del tipo (clase). De esta forma, se logra que sea un poco menos laboriosa la creación de nuevas clases, algo pesado a nivel de sintaxis y más después de ver como en lenguajes orientados a objetos como Java o C++, todo esto no es necesario.

Una vez que se creaba un objeto nuevo HelloBonoboEmbeddable dentro del método de la factoría, vimos que se llamaba al método "hello_bonobo_embeddable_construct" el cual es el que termina de construir el empotrable. Vamos con este método:

	HelloBonoboEmbeddable *
hello_bonobo_embeddable_construct (HelloBonoboEmbeddable *embeddable)
{
	BonoboPersistStream *stream;
	BonoboPrint         *print;

	g_return_val_if_fail (HELLO_BONOBO_IS_EMBEDDABLE (embeddable), NULL);

	bonobo_embeddable_construct (BONOBO_EMBEDDABLE (embeddable),
				     hello_bonobo_view_factory, NULL);
      

De la descripción de la API leemos que esta rutina se encarga de construir un servidor CORBA Bonobo::Embeddable y activarlo, de forma que cuando se pidan nuevas vistas del empotrable por ejemplo, se redirijan las peticiones al método adecuado.


	/* Registrar la interfaz Bonobo::PersistStream*/
	stream = bonobo_persist_stream_new (hello_object_pstream_load,
					    hello_object_pstream_save,
					    hello_object_pstream_get_max_size,
					    hello_object_pstream_get_types,
					    embeddable);
      

Comenzamos a ver lo que significa implementar la interfaz de persistencia, que consiste básicamente en especificar como cargar y guardar la persistencia de empotrable, en concreto, los métodos a ser llamados cuando el contenedor pida por la interfaz Bonobo::PersistStream que se guarde o se obtenga el estado del componente empotrado.

	if (!stream) {
		bonobo_object_unref (BONOBO_OBJECT (embeddable));
		return NULL;
	}

Si no logramos crear el objeto de persistencia, eliminamos la referencia que teníamos al empotrable (si nadie más lo está utilizando se destruiría) y devolvemos NULL.


	bonobo_object_add_interface (BONOBO_OBJECT (embeddable),
				     BONOBO_OBJECT (stream));
      

Hemos logrado crear el objeto que implementa la interfaz de persistencia del empotrable, por lo que agregamos la interfaz de este objeto de persistencia a las que soporta el empotrable. Aquí se refleja cláramente la idea de que la funcionalidad de un objeto Bonobo se va logrando por agregación y no por otros mecanismos como la herencia. Esta es una de las claves del modelo de componentes OLE2, y se basa en la idea de que en los sistemas distribuidos en los que la evolución y la flexibilidad son claves para las tecnologías, la agregación es un mecanismo mucho más flexible que la herencia. Por ejemplo, en tiempo de ejecución, las interfaces de los objetos se pueden cambiar por agregación sin tener que tocar para nada la implementación de un objeto. O un caso aún más atractivo: podemos ir cambiando las interfaces de los objetos pero manteniendo también las interfaces antiguas. De esta forma, un mismo objeto puede ir evolucionando pero manteniendo compatibilidad 100% con sus interfaces pasadas. De esta forma, se pueden por ejemplo actualizar sistemas de una forma mucho más gradual.

	/* Registrar la interfaz the Bonobo::Print */
	print = bonobo_print_new (hello_object_print, embeddable);
      

Aquí es donde creamos el objeto que se encargará de las labores de impresión del componente empotrable.

	if (!print) {
		bonobo_object_unref (BONOBO_OBJECT (embeddable));
		return NULL;
	}
      

Si no logramos crear el objeto de impresión, eliminamos la referencia que teníamos al empotrable (si nadie más lo está utilizando se destruiría) y devolvemos NULL.


	bonobo_object_add_interface (BONOBO_OBJECT (embeddable),
				     BONOBO_OBJECT (print));
      

Si el objeto de impresión se ha creado de forma correcta, añadimos la interfaz de impresión entre las soportadas por nuestro empotrable. Y con ello ya tenemos lista nuestro nuevo empotrable para ser utilizado.

	return embeddable;
}
      

Devolvemos nuestro nuevo empotrable para que puede ser utilizado dentro de la aplicación.

Persistencia de un Empotrable

La persistencia del empotrable la creamos en:

      /* Registrar la interfaz Bonobo::PersistStream*/
	stream = bonobo_persist_stream_new (hello_object_pstream_load,
					    hello_object_pstream_save,
					    hello_object_pstream_get_max_size,
					    hello_object_pstream_get_types,
					    embeddable);
      

La implementación de todos estos método se realiza dentro de "hello-object-io.h/c"

Impresión de un Empotrable

Como hemos visto, la impresión de un empotrable se logra creando el objeto:

	/* Registrar la interfaz the Bonobo::Print */
	print = bonobo_print_new (hello_object_print, embeddable);
      

Por lo tanto, vamos a irnos a "hello-object-print.h/c" para ver como se implementa esta interfaz de impresión, que en nuestro caso tan sólo nos mostrará el texto asociado a la variable "text" de nuestro empotrable. Sólo hay una función pública de esta clase:

	void hello_object_print (GnomePrintContext         *ctx,
			 double                     width,
			 double                     height,
			 const Bonobo_PrintScissor *scissor,
			 gpointer                   user_data);
      

que lo que hará es pintar dentro del "GnomePrintContext", en la posición indica por "width" y "height" el texto a ser impreso. Vamos a ver la implementación de este método:

	void
hello_object_print (GnomePrintContext         *ctx,
		    double                     width,
		    double                     height,
		    const Bonobo_PrintScissor *scissor,
		    gpointer                   user_data)
{
	HelloBonoboEmbeddable *embeddable = user_data;
	GnomeFont             *font;
	double                 w, w2, h;
	const char            *str, *descr;

	str = embeddable->text ? embeddable->text : "No hay texto";
      

Si no hay ningún texto, vamos a imprimir "No hay texto".

	descr = "Valor:";

	gnome_print_setlinewidth (ctx, 2);
	font = gnome_font_new ("Helvetica", 12.0);
      

Elegimos el tipo de fuente y su tamaño.

	g_return_if_fail (font != NULL);
	gnome_print_setrgbcolor (ctx, 0.0, 0.0, 0.0);
	gnome_print_setfont (ctx, font);
      

Ponemos el color y el tipo de fuente al contexto de impresión actual.

	w = gnome_font_get_width_string (font, descr);
	w2 = gnome_font_get_width_string (font, str);
	h =
	    gnome_font_get_ascender (font) +
	    gnome_font_get_descender (font);
      

Obtenemos la anchura de las cadenas a imprimir así como la altura.


	gnome_print_moveto (ctx, (width / 2) - (w / 2), (height / 2) + h * 2);
	gnome_print_show (ctx, descr);
      

Nos situamos y pintamos la cadena "Valor:".

	gnome_print_moveto (ctx, (width / 2) - (w2 / 2), height / 2 - h);
	gnome_print_show (ctx, str);
      

Nos situamos y pintamos la cadena que resida en "str".

	gtk_object_unref (GTK_OBJECT (font));
}
      

Liberamos el objeto fuente que ya no vamos a utilizar más.

Como vemos, la impresión es bastante sencilla en el concepto. Los detalles en concreto de como se lleva a cabo la impresión pueden ser más o menos complejos, pero siempre la metodología es la misma.