Capítulo II: Programa Gráfico en C++.


6 Usando Glade, modo básico

Práctica II-6: En Anjuta, crea un proyecto nuevo, selecciona programa proyecto Gtkmm 2.0 (por defecto el lenguaje de programación es  C++), como nombre gladealfin (o el que se te antoje).

Recuerda que dará error al generar el proyecto. Puedes editar el archivo configure, pero esta vez no servirá de nada, de todas formas hazlo Edita el archivo configure en la carpeta correspondiente /home/[usuario]/Projects/gladealfin y reemplaza las alusiones a gtkmm-2.0 por gtkmm-2.4 (que es la librería que nosotros hemos instalado).

Si al generar el proyecto no te aparece el archivo main.cc, sino otros distintos (nombre_programa.cc, windows1.cc, windows1.hh, windows1_glade.cc,  windows1_glade.hh ) tienes instalado el programa glademm que genera dichos archivos.  
Para seguir las siguientes instrucciones deberías desinstalarlo (provisionalmente).

En Anjuta, selecciona [Proyecto->Editar GUI de la aplicación], esto llama a la aplicación Glade, para abrir un archivo (gladealfin.glade) generado de forma automática por Anjuta.


No pretendo que este sea un manual de Glade, entre otras cosas porque no soy muy diestro con él, pero trataré de explicar al menos lo más elemental...

La ventana nº 1, es la ventana principal del programa, en ella se reflejan las ventanas o cuadros de diálogo que tenemos incluidos en nuestro proyecto. En este caso, sólo una que denomina window1.

El resto de ventanas te pueden aparecer o no, incluso hay otra dedicada al portapapeles. Para ver los distintos cuadros de diálogo ir al menú Ver de la ventana 1 y seleccionar los que se gusten.

La ventana nº2, es una muestra de la interfaz visual, de nuestra ventana. Aparece vacía a la espera de que se le añada un widget (recuerda el método add de Gtk::Window). Esta ventana aparece en la pantalla cuando se encuentra seleccionada en la ventana principal de glade, nº1.

Los componentes se seleccionan de la ventana nº3, la paleta, que contiene los distintos widget posibles de incluir en nuestra interfaz gráfica.

La ventana nº4, es el editor de propiedades, y allí aparecen todos los aspectos relativos al widget seleccionado en la ventana nº2. En este caso, como la ventana nº2 está vacía, aparecen los datos de la ventana principal. Vamos a aprovechar para hacer las siguientes modificaciones:
Nombre-> miventanica
Título-> maestrodenada.com

La ventana nº5 es el árbol de los widgets. Recuerda que los widgets se contienen unos a otros, así que puede resultar útil en un momento dado acceder a los distintos controles usando este sistema.

Recuerda que lo primero que hacíamos era añadir un VBox (una caja vertícal) a nuestra ventana. Hacemos lo mismo, y seleccionamos de la paleta, el VBox (la caja vertícal -en el díbujo con un círculo naranja), una vez seleccionados "clickamos" sobre la ventana nº2,... ¿Nos pregunta por el número de filas? Como nosotros, le añadíamos tres cosas (tres pack_start), pues le decimos que tres (por defecto).

Nos aparece la ventana dividido en tres partes iguales, ahora, seleccionamos la etiqueta (círculo verde) y pulsamos sobre la zona superior. En la ventana de propiedades, cambíamos:
Nombre-> etiqueta
etiqueta-> Pulsa el botón de abajo

Seleccionamos la línea horizontal y pulsamos en la zona media. Como no voy a modificar éste durante la programación, dejo los valores por defecto. ¡Ahh! lo que si voy a hacer es en la pestaña EMPAQUETADO del editor de propiedades, modificar la opción de expandir: para la etiqueta voy a poner sí y para la línea no. Varía el tamaño de la ventana y observa las diferencias (cuestión de gustos).

Ahora hay que poner el botón, pero voy a hacer un poco de trampa, en vez de un botón estandar (debajo de la etiqueta) voy a seleccionar una caja de botones horizontal (marco rojo) y "cliko" sobre la zona inferior. A la pregunta que me realiza (nº de columnas) le voy a indicar que 1 (pues sólo quiero un botón). El botón sale centrado (colocación: default), pero podíamos optar por que estuviese desplazado (colocación:start o end).

Sobre la ventana 2, selecciono el botón, y en el editor de propiedades edito:
Nombre-> boton
etiqueta-> ¡AQUÍ!


También me he permitido la licencia de añadirle un icono tanto a la ventana principal como al botón.
El resultado final, es similar a la figura de la izquierda, adjunto también el árbol de widget, donde se observan como los distintos contenedores tienen una flecha y tabulados, respecto a ellos aparecen los distintos widgets.


Con todo esto, le damos a grabar y escribimos la función main para que quede tal que así:



#include <gtkmm.h>
#include <libglademm/xml.h>
#include <iostream>


Gtk::Label* etiqueta;
int veces=0;
void pulsar_boton();

int main(int argc, char *argv[])

        // Inicializacion de la aplicación Gtk 
        Gtk::Main progr(&argc, &argv); 
 
        Glib::RefPtr<Gnome::Glade::Xml> refXml; 
   
        refXml = Gnome::Glade::Xml::create("../gladealfin.glade"); 


       
Gtk::Window* mi_aplicacion;
        refXml->get_widget("miventanica", mi_aplicacion); 
 
        Gtk::Button* boton;
        refXml->get_widget("boton", boton);
        boton->signal_clicked().connect(sigc::ptr_fun(pulsar_boton));
       
        refXml->get_widget("etiqueta", etiqueta);
           
        progr.run (*mi_aplicacion);
           
   
 
        return 0;
  
  
}
void pulsar_boton()
{
    char h[30];
    sprintf (h, "Has pulsado %i veces el botón", ++veces);
    etiqueta->set_text(h);
   
      
}

Antes de explicar...


...Vamos a tratar de ejecutar el programa...

Si has editado el archivo configure, te encontrarás al hacer Make con algo como esto:



La compilación da problemas al no conocer "libglademm/xml.h".

 Al seleccionar un proyecto Gtkmm; Anjuta, de forma automática,  incluye las librerías que requiere Gtkmm  en las opciones del compilador (g++) las cuales nosotros no hemos tocado.

 En el punto anterior veíamos que para incluir nuestros propios archivos debíamos añadir los archivos fuente al proyecto.

En este caso, debemos incluir la librería libglademm. ¿ Cómo se hace ? Según el tutorial de Anjuta (avanzado) - según mi traducción particular- existen dos modos de realizarlo: una correcta y otra "coja". La correcta utiliza todas las capacidades de autoconf. No sabría muy bien poner ejemplos para que se vean las diferencias (no tengo mucha idea de Gnome (por  ahora), así que Si Bwana, hagamos caso al jefe (anjuta) y realizamos el método correcto...



En primer lugar, en [Proyecto->Configuración del proyecto] en la pestaña Bibliotecas (de la pestaña Configuración) introducimos:

PKG_CHECK_MODULES( prueba, [libglademm-2.4 >= 2.6])
AC_SUBST(prueba_CFLAGS)
AC_SUBST(prueba_LIBS)

Con este paso, básicamente hemos configurado las macros para utilizar en los configure.

En un segundo paso, debemos añadir esas macros al compilador , lo cual se realiza en [Opciones-> opciones del compilador y el enlazador].

Allí, en la pestaña Opciones, en el "frame" Opciones adicionales añadimos:
$(prueba_CFLAGS) en Flags del compilador y,
$(prueba_LIBS) en Flags del enlazador

le damos cerrar y ... una ventana nos advierte que el proyecto ha cambiado y que debe autogenerarse nuevamente (De acuerdo, aceptamos)...

... Otra vez vuelve a generar el error de gtkmm 2.0 ... Volvemos a cambiar el archivo configure, autogeneramos (¡bien!), compilamos (¡bien!) y ejecutamos...



Explicación:


Si recuerdas la programación básica con Gtk, el programa es relativamente sencillo de comprender (a mí me lo parece).

En la programación básica de Gtk utilizamos variables puntero (al menos para aquellas variables que manipulabamos en funciones distintas a main).

Aquí TODAS las variables de componentes Gtk (globales o no ) se definen como variables puntero.

Estas variables puntero las asociabamos al objeto correspondiente mediante el operador &.

Aquí las variables puntero se asocian con el objeto correspondiente mediante la instrucción:
refXml->get_widget("nombre_glade", variable_puntero);
donde entre comillas aparece el nombre dado al widget en el archivo .glade y, como segundo argumento la variable puntero.

Como ya explicamos, a los distintos miembros de las variables puntero se accede con el operador -> y no con .

refXml es un puntero a un objeto Gnome::Glade::Xml, que es una clase que permite la manipulación de los archivos Xml (un tipo de formato de archivo, como lo pueda ser .odt, .xls,...) que es lo que genera Glade.

Glib::RefPtr<Gnome::Glade::Xml> refXml;
Esta es la declaración de refXml, es quizás lo más raro. Glib::RefPtr aspira a ser una clase genérica de Glib para la manipulación de los punteros (Glib tiene "copias" de los tipos predefinidos gint para int,...) Según la documentación esto es necesario para una adecuada gestión de la memoria, ¿?; la cuestión es que el uso de Gnome::Glade::Xml* no es factible.
<Gnome::Glade::Xml> es la forma (plantillas) de utilizar el puntero básico de Glib como del tipo Gnome::Glade::Xml.

Este puntero hay que inicializarlo, como cualquier otro, cosa que realiza la instrucción :
refXml = Gnome::Glade::Xml::create("../gladealfin.glade");

Esta función recibe como argumento el archivo .glade de la aplicación. Como Anjuta, no genera este archivo en el directorio src del proyecto, sino en el raíz del mismo; debemos colocar ../ antes del nombre del archivo glade de la aplicación.

Todas estas operaciones de inicialización pueden fracasar, es decir, que no se pueda crear el objeto. En esos casos la función devuelve NULL, que es el valor asociado a un puntero para indicar que no apunta a nada. Por eso, lo normal es que tras cualquier inicialización se compruebe su éxito antes de seguir trabajando, creándose una serie de if (anidados). Así nuestro archivo main quedaría...
int main(int argc, char *argv[])

        // Inicializacion de la aplicación Gtk 
        Gtk::Main progr(&argc, &argv); 
        // Declaración de refXml
        Glib::RefPtr<Gnome::Glade::Xml> refXml; 
        // Inicialización de refXml
        refXml = Gnome::Glade::Xml::create("../gladealfin.glade"); 

        
// Declaración de mi aplicación
        Gtk::Window* mi_aplicacion;
        // Inicialización de mi_aplicacion
        refXml->get_widget("miventanica", mi_aplicacion); 
        if (mi_aplicacion) {
                   // Declaración de boton
                   Gtk::Button* boton;
                    // Inicialización de boton
                   refXml->get_widget("boton", boton);
                   if (boton){
                                  boton->signal_clicked().connect(sigc::ptr_fun(pulsar_boton));
                                   // Inicialización de etiqueta       
                                   refXml->get_widget("etiqueta", etiqueta);
                                   if (etiqueta){
                                                                // el programa se ejecuta si todo ha ido bien
                                                                progr.run (*mi_aplicacion);
                                   }
                  }
        }
 
        return 0;
  
  
}
Con refXml, no lo he utilizado, porque lo habitual es utilizar la fórmula (más elegante) de try ... catch

try
  {
    refXml = Gnome::Glade::Xml::create("../gladealfin.glade");
  }
  catch(const Gnome::Glade::XmlError& ex)
  {
    std::cerr << ex.what() << std::endl;
    return 1;
  }

lo cual informa del error y provoca la salida del programa.


VENTAJAS:
Quizás no observes mucha diferencia en cuánto a cantidad de código entre escribir con Glade o sin él. La verdad es que esto es así, porque un servidor ha sido bastante perezoso y siempre he aceptado los controles por defecto, sin añadir ningún parámetro, pero el uso del Glade puede ahorrarnos muchas horas de trabajo.

INCONVENIENTES: El archivo .glade de la aplicación tiene que viajar siempre con la distribución de nuestro programa.



Y por fin... Glade con clase
Inicio