Controles Bonobo

Este tipo de componentes, los controles, son los más usados, pues, aparte de ser de los más fáciles de implementar, ofrecen lo que antes comentábamos: importar la interfaz gráfica de una aplicación en otra. Esto puede observarse en la siguiente captura de pantalla,

Figura 1. GnomeDbBrowser en Glade

GnomeDbBrowser en Glade

donde aparece Glade, el programa de diseño de interfaces gráficas del proyecto GNOME, con un ventana en la que se ha insertado el componente DBBrowser de GNOME-DB, que aparece, esta vez dentro de la aplicación gnomedb-fe (parte de GNOME-DB), en la siguiente captura de pantalla:

Figura 2. GnomeDbBrowser en el front-end de GNOME-DB

GnomeDbBrowser en el front-end de GNOME-DB

Como puede verse en este ejemplo "visual", la interfaz gráfica de un programa es exportada para que cualquier otra aplicación Bonobo pueda fácilmente incluirla. Como se comentaba anteriormente, esto (exportar la interfaz gráfica) es algo que las aplicaciones GNOME de 2ª generación están empezando a hacer, lo que va a posibilitar el desarrollo de aplicaciones a gran escala, pues el tener un sistema de componentes potente como es Bonobo va a permitir a los desarrolladores el centrarse en la implementación de sus componentes espcíficos, que luego interactuarán a las mil maravillas con el resto de aplicaciones Bonobo.

Ejemplos de uso de estos controles podrían ser, por ejemplo, un navegador de internet, que exporte su visualizador HTML, que luego podría ser incluido en cualquier aplicación, dotándola así de acceso a Internet con sólo unas pocas líneas de código:

      BonoboWidget* browser;
      Gtkwidget* window;

      window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      browser = bonobo_widget_new_control("OAFIID:GNOME_Web_Browser");
      gtk_container_add(GTK_CONTAINER(window), browser);
      gtk_widget_show_all(window);
    

Podriamos definir los controles como "widgets" convertidos en componentes, es decir, que son simples "widgets" que un determinado proceso exporta para ser reutilizados por otras aplicaciones Bonobo. Esta idea puede parecer estúpida, pero no lo es, pues esto nos abre unas posibilidades muy grandes, a la hora de hacer aplicaciones de gran envergadura. Un buen ejemplo es Evolution, el gestor de correo y herramienta de trabajo en grupo que está siendo desarrollado por Ximian: la ventana principal de este programa es un simple contenedor de componentes, es decir, que no tiene mucha funcionalidad por si solo, sino que la funcionalidad la aportan los distintos componentes que se cargan en ella. Esta arquitectura permite, sin ninguna dificultad, fácilmente extender Evolution añadiéndole nuevos componentes que se irán cargando. De hecho, se comentó hace poco, en la lista de componentes del proyecto GNOME (ver referencias), que una empresa estaba desarrollando un programa para gestión de proyectos, y que su intención era, en un futuro, liberarlo como un componente Bonobo para que pudiera ser cargado en Evolution.

Y, aún más importante, este sistema nos permite reutilizar estos controles de Evolution en nuestras aplicaciones. Así, por ejemplo, podríamos tener una opción de menú en nuestro programa para permitirle al usuario el acceso a la libreta de direcciones de Evolution. En este caso, simplemente tendríamos que cargar en nuestra aplicación el control (evolution-addressbook) suministrado por Evolution.

Los controles son, quizás, los componentes más fáciles de implementar. Por ello, y por la utilidad que tienen y que ya hemos comentado, son los más usados. Así que, vamos a entrar ya de lleno en la implementación de aplicaciones Bonobo que usen controles.

Uso de Controles - clientes

Aunque en Bonobo no existe la noción de cliente/servidor, sino más bien, de contenedor/componente, para hacerlo más sencillo, vamos a utilizar esta nomenclatura. En este caso, cliente sería la aplicación que carga los componentes, y servidores serían todos los procesos que exportan de alguna manera componentes.

Para los clientes, el código es bastante sencillo. Empezando por la inicialización de la aplicación, en la función main:

	int main (int argc, char *argv[])
	{
		CORBA_ORB orb;

		/* inicializamos GNOME */
		gnome_program_init ("contenedor", "0.1", argc, argv, oaf_popt_options, 0, NULL);

		/* inicializamos Bonobo/OAF */
		orb = bonobo_activation__init (argc, argv);
	        if (bonobo_init(orb, NULL, NULL) == FALSE) {
			g_error("No se pudo inicializar Bonobo\n");
		}
		bonobo_activate();

		g_idle_add (crear_ventana, NULL);
	        bonobo_ui_main();
		return 0;
	}
      

Como puede observarse, las primeras líneas de la función main son las que inicializan todo el sistema, tanto las librerías GNOME, como OAF (el sistema de activación de objetos del proyecto GNOME) y Bonobo. Para la inicialización de GNOME es necesario llamar a la función gnome_init_with_popt_table en vez de gnome_program_init, debido a que, para el correcto funcionamiento de Bonobo-Activation, es necesario que se procesen los parámetros que pueda recibir nuestro programa. Los parámetros son procesados por medio de la librería popt (usada internamente en las librerías de GNOME) y de la variable oaf_popt_options, definida en Bonobo-Activation, y que tenemos que usar para que los parámetros que se le puedan pasar a Bonobo-Activation a través de la línea de comandos puedan ser procesados. En el caso de los clientes (aplicación contenedora) este paso no es estrictamente necesario, pero se explica aquí para que luego este mismo código nos sirva para el servidor (implementación del componente).

Seguidamente, tras la inicialización de todas las librerías necesarias, el siguiente paso es activar Bonobo, que se hace mediante la llamada a bonobo_activate. Esta función activa internamente todo lo necesario para que Bonobo funcione. Tras esta llamada, ¡ya estamos dentro del mundo de Bonobo!

El siguiente paso es crear nuestra ventana principal, en la que iremos cargando los componentes. Para ello, hay que tener en cuenta que, una vez activado Bonobo (con la llamada a bonobo_activate), no se puede hacer ninguna llamada a Bonobo/CORBA hasta que no estemos en el bucle principal de la aplicación, que se ejecuta en la llamada a la función bonobo_main. Así, para poder crear nuestra ventana una vez dentro del bucle de la aplicación, hacemos una llamada a la función de GTK gtk_idle_add, cuyo cometido es instalar una función que será llamada cuando el bucle del programa esté inactivo ("idle"). En nuestro caso, lo que hacemos es instalar la función crear_ventana, que tiene el siguiente aspecto:

	guint crear_ventana (void)
	{
		GtkWidget* ventana;
		GtkWidget* control;

		ventana = gtk_window_new(GTK_WINDOW_TOPLEVEL);
		g_signal_connect (G_OBJECT(ventana), "destroy", gtk_main_quit, NULL);

		/* cargamos el control */
		control = bonobo_widget_new_control ("OAFIID:GNOME_GtkHTML_Editor", NULL);
		if (!BONOBO_IS_WIDGET(control))
			g_error("No se pudo cargar el control Bonobo");
		gtk_container_add(GTK_CONTAINER(ventana), control);
		gtk_widget_show_all(ventana);

		return FALSE; /* para que no se llame más a esta función */
	}
      

En esta función, lo primero que hacemos es crear una ventana, que será donde carguemos el control Bonobo. Esto se hace mediante la llamada a gtk_window_new(), para seguidamente conectar la función gtk_main_quit a la señal "destroy" de la ventana, lo que causará, como es de imaginar, que, cuando el usuario cierre la ventana, se termine el programa.

La siguiente línea ya forma parte de lo que realmente nos interesa: la carga del control Bonobo. Esto se hace mediante la función bonobo_widget_new_control, a la que tenemos que pasarle como parámetro el OAFIID (Identificador de la Implementación de un objeto OAF) del componente que queremos cargar. En este caso, lo que estamos cargando ("OAFIID:GNOME_GtkHTML_Editor") es un editor HTML que viene incluido en la librería gtkhtml, que es necesaria para el correcto funcionamiento de Evolution, por lo que si tenemos instalado Evolution, seguro que tenemos este componente instalado en nuestro sistema.

Esta función, bonobo_widget_new_control, devuelve un puntero a un BonoboWidget que no es otra cosa que un simple GtkWidget, por lo que podemos tratarlo como tal e insertarlo en la ventana que hemos creado con las funciones que GTK incluye para ello. En este caso, puesto que un GtkWindow es también un GtkContainer, usamos la función gtk_container_add para insertar el componente Bonobo que hemos cargado en nuestra ventana. En este ejemplo lo hacemos de esta manera, pues es la más sencilla para mostrar fácilmente la funcionalidad de Bonobo, pero evidentemente, podríamos insertar el componente Bonobo en cualquier GtkWidget que lo permita (GtkContainer, GtkTable, GtkBox, GnomeDialog, etc), y de hecho es lo que haremos cuando empecemos a hacer aplicaciones más sofisticadas y complejas.

En la siguiente captura de pantalla podemos observar nuestra aplicación contenedora en funcionamiento, con el componente de edición HTML cargado.

Figura 3. Componente de edición GtkHTML

Componente de edición GtkHTML

Como última nota sobre la función crear_ventana, comentar que es muy importante que la función devuelva FALSE, pues si no GTK seguirá llamándola durante toda la duración del programa, cada vez que el bucle principal esté inactivo. Y evidentemente, eso nos es lo que queremos que haga nuestra aplicación, pues eso significaría que se crearía una ventana, con un control de edición HTML insertado, cada ¡n milisegundos!

Bien, pues con esto ya tenemos una bonita aplicación Bonobo, sin mucha funcionalidad, pues le faltaría añadirle menús, barras de tareas y alguna que otra cosa más, pero que nos sirve para empezar a entender todo el funcionamiento de la arquitectura Bonobo. Ahora, pasemos a la creación de nuevos controles.

Creación de nuevos controles

Si bien la creación de nuevos controles no es tan sencilla como su uso (como hemos visto en el apartado anterior), la verdad es que la dificultad es mínima, más bien ligada a la serie de pasos que hay que seguir que a la implementación en si. Una vez que se entienden los pasos básicos, no es complicado meterse a indagar en las entrañas de Bonobo, y así comenzar a usar características mucho más avanzadas que las que mostramos en este artículo.

Los controles (y todos los demás tipos de componentes) suelen implementarse en procesos independientes, aunque, por supuesto, también pueden implementarse en librerías dinámicas, que son cargadas por OAF según se vayan necesitando, o incluso en el mismo proceso en el que se utilizan. En nuestro caso, vamos a implementar nuestro control en un proceso independiente, pues resulta más sencillo. Y vamos a hacer que nuestro control sea una calculadora, mediante el uso del "widget" GnomeCalculator, que forma parte de las librerías básicas de GNOME (gnome-libs). Este "widget" implementa una útil calculadora que podemos fácilmente integrar en nuestro programa. Este "widget" es el mismo que utiliza el programa gcalc, la calculadora incluida en GNOME.

Así, aquí está nuestra función main, bastante parecida a la de los clientes anteriormente mostrada:

	int main (int argc, char *argv[])
	{
		CORBA_ORB orb;

		/* inicializamos GNOME */
		gnome_program_init ("prueba-control", "0.1", argc, argv, oaf_popt_options, 0, NULL);

		/* inicializamos Bonobo/OAF */
		orb = bonobo_activation_init (argc, argv);
		if (bonobo_init(orb, NULL, NULL) == FALSE)
			g_error("No se pudo inicializar Bonobo\n");

		bonobo_activate();
	        crear_factoria();
		bonobo_main();

		return 0;
	}
      

Aquí se puede ver que es prácticamente igual que la versión "cliente", con la única diferencia de la llamada a crear_factoria en lugar de la llamada a gtk_idle_add que teníamos en el cliente. Y se preguntará el lector: ¿factoría? ¿para qué? La explicación es muy sencilla: en Bonobo, para la implementación de componentes, se usa lo que se llaman factorías, que son objetos CORBA que permiten la creación de nuevos objetos, para así facilitar todo el acceso a los distintos componentes. Así, nuestra función crear_factoria tendrá la siguiente forma:

	void crear_factoria (void)
	{
		BonoboGenericFactory* factoria;

		factoria = bonobo_generic_factory_new("OAFIID:GNOME_MiCalculadora",
		                                      crear_nuevo_objeto,
		                                      NULL);
		if (!BONOBO_IS_GENERIC_FACTORY(factoria))
			g_error("No se pudo crear factoría para nuestro control");
	}
      

Como puede verse, lo único que hacemos en esta función es crear la factoría (de tipo BonoboGenericFactory). Pero hay un pequeño detalle, que es el segundo parámetro que le pasamos a la función bonobo_generic_factory_new, que es un puntero a una función que será la encargada de crear nuestros objetos según lo vayan solicitando. Esta función, quedaría de la siguiente manera.

	BonoboObject *
	crear_nuevo_objeto (BonoboGenericFactory *factoria, const char *id, void *data)
	{
	        BonoboControl*     control;
		GtkWidget*         calculadora;

		calculadora = gnome_calculator_new();
		gtk_widget_show(calculadora);

		control = bonobo_control_new(calculadora);
		bonobo_control_set_automerge(control, TRUE);
		return BONOBO_OBJECT(control);
	}
      

Bien, con esta simple función ya tenemos la implementación de nuestro control iniciada y funcional. Lo que se hace en esta función es, primero, crear el "widget" que va a ser incluido en el control, que luego será pasado como parámetro a la función bonobo_control_new, que ya se encarga de recubrir nuestro "widget" (en este caso, una calculadora) con todo lo necesario para que pueda ser cargado en la aplicación contenedora.

Bueno, y como una imagen vale más que mil palabras, aquí se puede observar, en la siguiente captura de pantalla, nuestro control Bonobo cargado en la aplicación cliente comentada en el punto anterior (para ello, simplemente tenemos que cambiar el identificador del objeto a cargar en la llamada a bonobo_widget_new_control. En este caso, sustituiriamos la cadena "OAFIID:GNOME_GtkHTML_Editor" por "OAFIID:GNOME_MiCalculadora").

Figura 4. El control calculadora en la ventana contenedora

El control calculadora en la ventana contenedora

Como última nota de esta parte, mencionar la llamada a bonobo_control_set_automerge, que lo que hace es mezclar automáticamente los menús y barras de tareas del control con los de la aplicación contenedora. Esta es otra de las características que hacen realmente útiles a los controles. Podemos dividir nuestra aplicación en distintos módulos "visuales", e irlos cargando según se necesite. El usuario no notará absolutamente, simplemente verá que según activa uno u otro de los componentes de la aplicación, algunos menús y botones de la barra de tareas aparecen/desaparecen, algo que agradecerá enormemente, pues de nada le sirve la opción de, por ejemplo, corrección ortográfica incluida en el módulo de edición, mientras está retocando una imagen en el módulo de diseño de la aplicación. Esto es sólo un ejemplo, y de hecho vamos a dejar la explicación detallada de esta parte para el siguiente artículo de la serie, pues por su tamaño y extensión, merece un artículo para sí misma. Pero si el lector quiere ir investigando, puede consultar el código fuente de algunas de las aplicaciones que hacen uso de esta característica de Bonobo, como el mismo Evolution, GNOME-DB, Nautilus, Eye Of GNOME, etc.

Menus y barras de tareas