Desarrollo de aplicaciones gráficas (GTK+) con Perl y Glade

Tutorial para programadores de Perl que deseen crear aplicaciones gráficas con librerías GTK (Versión 1.4).
Alejandro Garrido Mota <garridomota@gmail.com>


  1. Introducción
  2. ¡Hola Mundo! Nuestro primer programa
  3. Manejo de Señales
  4. Escribiendo información en las etiquetas
  5. Obteniendo información introducida por el usuario
  6. Manejo de múltiples ventanas
  7. Vistas
  8. Calendarios
  9. Botones
  10. ¿Dónde puedo conseguir más información?
  11. Sobre este documento...


1. Introducción

1.1. ¿Qué necesitamos?

Cuando queremos empezar a crear aplicaciones gráficas utilizando las librerías GTK y un diseñador de interfaces es primordial tener instalado Glade, ya que Perl viene en el sistema base de casi todas las distribuciones de GNU/Linux. En la página oficial de Glade puedes descargarte el código fuente, compilarlo e instalarlo. Pero si estás en Debian basta con ejecutar lo siguiente para tenerlo instalado:

  aptitude install glade

Una vez que tenemos Glade instalado, podemos empezar a "dibujar" el programa que vayamos a desarrollar, o técnicamente dicho, a crear la interfaz gráfica. Éste no es un tutorial para enseñar a usar Glade, ya que existe suficiente documentación en la internet que lo enseñan y explican paso a paso. Nosotros nos vamos a enfocar en trabajar con los módulos necesarios para Programar en Perl utilizando este diseñador de interfaces. Para que Perl pueda interpretar el archivo XML que es generado por Glade(archivo con extensión .glade) necesitamos tener instalado y en funcionamiento un módulo llamado Gtk2::GladeXML. Existen dos maneras de instalarlo:


NOTA IMPORTANTE: En la versión 3 de Glade la propiedad de visibilidad de las "ventanas" vienen por defecto en no visible, cosa que no pasa en Glade versión 2. Esto implica dos cosas: Que cambiemos la propiedad de visibilidad a "Si visible" desde glade o que en nuestro script de Perl hagamos visible la ventana. En el artículo yo me guiaré por la última, haremos visible todas las ventanas desde el script en perl y no desde glade. (lee esto después del primer ejemplo: Es por eso que utilizo en método show_all() en casi todos los script).

2. ¡Hola Mundo! Nuestro primer programa

Nuestro primer programa es el tradicional 'Hola Mundo'. Una captura de pantalla del programa es la siguiente:

Lo primero que tenemos que hacer para poder diseñar este programa es crear la interfaz en Glade. Claro que sólo vamos a hacer la interfaz, nada más. Seguidamente guardamos el proyecto y comenzamos nuestro script de Perl.

El script de Perl que interpretará el archivo XML y generará las ventanas basándose en la información dada por el archivo XML generado glade será el siguiente:


  #!/usr/bin/perl
  use strict;
  
  ### Este modulo obtiene la información de la interfaz hecha en glade
  ### basándose en el archivo XML generado por este (le hace un parser).
  ### Los parámetros obtenidos se los pasa al módulo Gtk2.
  
  use Gtk2::GladeXML;
  
  ### Módulo que recibe los parámetros obtenidos por Gtk2::GladeXML
  ### y genera las ventanas basándose en ellos.
  
  use Gtk2 -init;
  
  ### Declaro las variables que usaré en el programa
  ### Si programas en Perl ya sabes a que me refiero ;-)
  
  my($programa, $ventana_principal);
  
  ### Le estoy indicando la ruta (ubicación) donde se encuentra el archivo XML
  ### generado por Glade, del cual Gtk2::GladeXML analizará y extraerá
  ### los parámetros más importantes y se los facilitará al Gtk2. En base a esos
  ### parámetros se crea un objeto al que llamé "programa" el cual contendrá
  ### todos los widgets del programa e interactuará con este mediante los métodos 
  ### predefinidos que contendrá cada widget.
  
  $programa = Gtk2::GladeXML->new('prueba.glade'); 
  
  ### Ahora estoy cargando la ventana principal al hacer esto
  ### esto se crea un objeto "ventana_principal" el cual nos permite
  ### interactuar con la ventana mediante métodos ya predefinidos.
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Por defecto (depende de la versión del módulo Gtk2::GladeXML
  ### tengas) no se muestra la ventana, se carga más mas no se muestra
  ### Con el método show_all() hacemos que sea visible para el usuario
  
  $ventana_principal->show_all();
  
  ### El loop del programa. Esto es para que el programa se
  ### quede abierto siempre esperando la interacción del usuario, 
  ### ya sea cerrar la ventana, presionar un botón o cualquier acción.
  
  Gtk2->main;

Descargar código del programa sin comentarios

Existen varios métodos predefinidos cuando creamos un objeto, estos métodos no son definidos por nosotros, se crean automáticamente cuando creamos el objeto. Es importante que tengamos claro que siempre que definamos objetos estamos asociando a este con un widgets para así poder interactuar con este, a cada método una acción por parte del widget. En el ejemplo anterior no hay mucho que interactuar ya que sólo tenemos un ventana, sin embargo, cuando trabajamos con varias ventanas podemos esconderlas o mostrarlas mediante esos métodos predefinidos basándonos en condiciones que se cumplan.

En el programa anterior utilizamos el método get_widget, el cual recibe como parámetro el ID (o nombre) del widget. Dicho ID se define en glade (en las propiedades) y es utilizado para referirnos desde nuestro script a un widget en específico, en este caso es la ventana principal, pero es factible y común cargar múltiples ventanas. Con este método lo que hacemos es crear un objeto (en el ejemplo anterior el objeto es ventana_principal) el cual tendrá métodos predefinidos. Ya en los próximos ejemplos entenderemos y utilizaremos estos métodos predefinidos para manipular el comportamiento de los widgets.

El método main del módulo Gtk2 (ver Gtk2->main; en el ejemplo) lo que hace es crearnos un loop. Para comprender esto debemos saber que muchos programas lo que hacen ejecutarse, cumplir su función y terminar la ejecución. En entornos gráficos siempre queremos que el programa esté abierto en espera de cualquier señal o evento que un usuario haga con un widget. Debido a esto, para que el programa siempre esté ejecutándose en en espera de esta señal o evento, debemos de crear un loop infinito, eso es lo que hace Gtk2->main: Dejar el programa en un ciclo infinito de ejecución.

3. Manejo de Señales

Cuando programamos con Perl y glade, tenemos la posibilidad de manejar las señales de las sguientes dos formas:

El segundo método quizás sea el que nos convenga debido a que nos hace más corto, manipulable y sencillo de mantener nuestro programa, ya que los manejadores junto con la señal asociada los declaras en Glade y luego los invocas mediante subrutinas en el script de Perl (ya veremos ejemplos, no se asusten).

Si utilizamos el primer método, tendríamos que declarar el tipo de señal en el script de Perl, lo que hace más tedioso el trabajo. Al declarar el tipo de señal también la debes de asociar a una función respuesta (que es una subrutina).

Por cuestiones de simplicidad todos los programas los diseñaremos utilizando el método de los manejadores. Todos excepto el el que viene a continuación debido a que les quiero mostrar un mismo programa diseñado a partir de cada uno de los dos métodos.


Seguiremos ampliando el ejemplo anterior, por lo que ahora añadiremos un botón que cuando lo presionemos se cierre la aplicación. El ejemplo a continuación se realizó utilizando el método de declaración de señales en el script y luego el método de los manejadores.

Así lucirá nuestro programa:

3.1. Señales declaradas en el programa

Lo único que debemos hacer en Glade es crear el botón y asignarle un identificador (ID), debido a que la señal la vamos a declarar en nuestro script (he aquí la gran diferencia con respeto al otro método). Una vez hecho esto, nuestro programa en Perl quedaría así:


  #!/usr/bin/perl
  use strict;
  use Gtk2 -init;
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $boton_cerrar);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Creo el objeto "$boton_cerrar" (haciendo referencia al botón
  ### creado en glade) para así tener métodos predefinidos que  
  ### me permitan trabajar he interactuar con el botón. 
  
  $boton_cerrar = $programa->get_widget('boton_cerrar');
  
  ### Estoy asignándole al objeto "$boton_cerrar" (al botón)
  ### la señal "clicked" que a su vez hace referencia a una
  ### subrutina. Así, una vez presionado el botón se ejecutará
  ### la subrutina "&salir"
  
  $boton_cerrar->signal_connect(clicked => \&salir);
  
  Gtk2->main;
  
  ### Esta subrutina llamada salir sólo se va a ejecutar si sucede 
  ### el evento que le asignamos al botón
  sub salir {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

Por primera vez hacemos uso del método signal_connect() el cual es predefinido y se crea automáticamente cuando declaramos el objeto $boton_cerrar. Éste nos permite asignar señales a widgets dentro de nuestro programa. Para asignar señales lo que debemos hacer es pasarle como parámetro al método mencionado el tipo se señal que deseamos y la subrutina a ejecutar cuando se cumpla esa señal. Así como se muestra en este último ejemplo.

También utilizamos el método main_quit del módulo Gtk2 cuyo fin es terminar el loop creado por el método main, en pocas palabras se usa para salir del programa.

En el ejemplo anterior el programa esperará a que el usuario presione el botón "Salir", para luego llamar a la subrutina &salir en cuyo cuerpo ejecuta el método main_quit el cual sale del programa.

3.2. Señales bajo manejadores

Realizaremos de nuevo el mismo ejemplo anterior con los manejadores, ya que el concepto será el mismo. A diferencia de declarar la señal en el programa, crearemos manejadores asociados a una señal, esto lo hacemos en Glade.

Recuerda: Los manejadores asociados a la señal los defines en glade en la parte de propiedades del widget (en este caso en las propiedades del botón) y le puedes colocar el nombre que quieras al manejador, yo siempre dejo la notación o nombre que coloca Glade por defecto

Nuestro script será de la siguiente manera:


  #!/usr/bin/perl
  use strict;
  use Gtk2 -init;
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Está línea lo que hace es es levantar o permitir los manejadores
  ### de señales definidos en el XML generado por glade
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  ### Creamos una subrutina que _obligatoriamente_ debe llamarse
  ### igual al nombre del manejador de señales que definimos en 
  ### glade, dentro de esta subrutina definimos lo que hará el
  ### programa cuando se ejecute el evento definido en el glade
  
  sub on_boton_cerrar_clicked {
  	Gtk2->main_quit;
  }

Descargar código del programa sin comentarios

Cuando desarrollamos usando manejadores debemos utilizar el método signal_autoconnect_from_package() para que active los manejadores de señales definidos en el XML que genera glade. Si hacemos caso omiso a este método nuestro programa no interpretará la subrutina &on_boton_cerrar_clicked, por lo que no funcionará el botón.

El método signal_autoconnect_from_package() recibe a main como argumento, lo cual quiere decir que todas las funciones o subrutinas las declararemos en el mismo script. Pero, ¿dónde más podrían estar? Pues es factible crear un paquete que sólo contenga las subrutinas que hacen referencia a los manejadores de señales, de manera que podríamos crear un paquete con todas las subrutinas y su contenido. Si optamos por esto último tendríamos que cambiar el argumento main por el nombre del paquete.

4. Escribiendo información en las etiquetas

En esta sección escribiremos sobre las etiquetas. Sólo haremos uso de un método, bastante trivial por cierto. Aquí muestro la interfaz (desde glade) de nuestro programa para que observen la etiqueta vacía, esta así (vacia) para luego poder escribir en esta desde el script.

Nuestro script debe quedar de la siguiente manera:


  #!/usr/bin/perl
  use strict;
  use Gtk2 -init;
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $etiqueta);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Creo el objeto "etiqueta" el cuál hace referencia al ID o nombre
  ### "etiqueta" (el cual definí en Glade) para poder interactuar con la 
  ### etiqueta vacía.
  
  $etiqueta = $programa->get_widget('etiqueta');
  
  ### Del objeto antes creado, utilizo un método predefinido que me permite
  ### escribir el mensaje que yo quiera en la etiqueta. El argumento 
  ### para este método es el mensaje que queremos mostrar en la etiqueta.
  
  $etiqueta->set_markup("Este es un mensaje escrito\n desde el script");
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main; 
  
  sub on_boton_cerrar_clicked {Gtk2->main_quit;}

Descargar código del programa sin comentarios

En principio creamos el objeto $etiqueta el cual hace referencia (mediante el ID o nombre) a la etiqueta vacía que definimos en Glade, creamos este objeto para aprovechar los métodos predefinidos que éste trae, específicamente utilizaremos el que nos permite escribir caracteres en dicha etiqueta.

Luego de que utilizamos el método set_markup el cual recibe como parámetro el texto que aparecerá en la etiqueta, es factible utilizar el lenguaje de marcado que usa Glade para subrayar, colorear, tachar, etc el texto que a colocar en la etiqueta

El programa anterior se observa de la siguiente manera cuando lo ejecutamos:

5. Obteniendo información introducida por el usuario

Es común que en la mayoría de los programas que elabores desees obtener algún tipo de información del usuario, la cual seguramente manipularás o introducirás en una base de datos. Te puede interesar obtener el nombre y apellido para luego introducir esos datos a una base de datos, por ejemplo. Pero ¿Cómo obtener esa información del usuario? Mediante widgets, las dos más comunes son: mediante entradas de texto o mediante entradas de caja de combo.

5.1. Entradas de texto

Las entradas de texto son pequeñas cajas en donde el usuario introduce texto el cual podemos manipular, podríamos guardarlos en una base de datos, por ejemplo. En Glade hay un widget (en la paleta) llamado "Entrada de texto" o "entry text" el cual podemos ubicar en cualquier parte de nuestra interfaz. Como a todo widget le debemos colocar un ID o nombre para lograr identificarlo en nuestro script, es cómodo dejar el nombre por defecto ("entryX") o asignarle el que queramos, yo normalmente le coloco "entrada1", "entrada2" y así sucesivamente.

Una vez que colocada la entrada de texto en mi programa y haberle asignado ID puedo comenzar a trabajar en el script. Voy a utilizar el mismo ejemplo anterior pero esta vez escribiremos lo introducido por el usuario en la etiqueta vacía, aunque lo más común es introducir en texto en una base de datos. Nuestro programa en Glade debe verse muy parecido al siguiente:

Es importante notar la etiqueta vacía. El objetivo de este programa es solicitarle al usuario su nombre para que este lo introduzca en la entrada de texto que observas en la captura de pantalla. Una vez introducido el nombre escribiremos este en la etiqueta vacía (aplicando lo aprendido en al capítulo anterior). También puedes notar que agregué un botón ("Recoger Datos"), el cual será presionado cuando el usuario halla introducido su nombre para posteriormente obtener este nombre, guardarlo en un variable y escribirlo etiqueta vacía.

Nuestro código debe lucir de la siguiente manera:


  #!/usr/bin/perl
  use strict;
  use Gtk2 -init;
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $etiqueta, $widget_entrada_de_texto);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  $etiqueta = $programa->get_widget('etiqueta');
  
  ### Creo el objeto "$widget_entrada_de_texto" el cual contiene métodos
  ### predefinidos para interactuar con la "entrada de texto" y así 
  ### obtener los introducido por el usuario. En parámetro que recibe es 
  ### el ID o nombre que especificamos en el Glade. Yo le puse entrada1
  
  $widget_entrada_de_texto = $programa->get_widget('entrada1');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  sub on_boton_cerrar_clicked {Gtk2->main_quit;}
  
  ### Esta subrutina es la que se ejecutará cuando el usuario presione el botón
  ### "Recoger Datos" 
  
  sub on_boton_recoger_datos_clicked {
          
  	### Declaro una variable interna en subrutina la cual guardará el 
  	### texto introducido por el usuario
  	
  	my($texto_introducido);
  	
  	### Uso uno de los métodos predefinidos(``get_text()``) que tiene el 
  	### objeto creado anteriormente el cuál me permite obtener lo introducido
  	### por el usuario en la entrada
          
  	$texto_introducido = $widget_entrada_de_texto->get_text();
          
  	### Una vez obtenido el valor introducido por el usuario(en este ejemplo 
  	### sería el nombre) y está guardado en la variable $texto_introducido 
  	### entonces escribo ese valor en la etiqueta vacía al igual que vimos en 
  	### capítulo anterior
  
  	$etiqueta->set_markup("$texto_introducido");
  }

Descargar código del programa sin comentarios

Una captura de pantalla del programa en funcionamiento:

Lo único que nuevo que se incorpora al mismo es el método get_text el cual nos permite obtener el texto introducido por el usuario en el widget Entada de texto (del ingles Entry Text).

5.2. Entrada de Caja de combo

Las entradas de caja de combo se diferencian de las "Entradas de texto" en que tienen la posibilidad de incorporar una lista predefinida de valores donde el usuario expande un menú y elige algún valor(texto) predefinido. También funciona como entrada de texto ya que el usuario también tiene la posibilidad de introducir(escribir) texto al igual que en el widget 'Entrada de Texto' que vimos en la sección anterior.

Una captura de pantalla del ejemplo desde la interfaz de glade donde se utiliza la entrada de de combo:

El código que utilizaremos será el siguiente:


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $caja_de_combo_pais, $caja_de_combo_ciudad, $etiqueta);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  ### Declaramos los objetos haciendo referencia a los widgets que utilizaremos
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  $caja_de_combo_pais = $programa->get_widget('entrada_caja_de_combo_pais');
  $caja_de_combo_ciudad = $programa->get_widget('entrada_caja_de_combo_ciudad');
  $etiqueta = $programa->get_widget('etiqueta_datos_introducidos');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  ### Subrutina a ejecutar cuando se presione el botón aceptar
  
  sub on_boton_aceptar_clicked {
  	### Variable $pais contendrá el valor introducido por el usuario
  	### el cual se obtiene mediante el método señalado   
  	my $pais = $caja_de_combo_pais->get_active_text();
  	
  	### La variable $ciudad contendrá la cuidad escrita o señalada por
  	### el usuario
  	my $ciudad = $caja_de_combo_ciudad->get_active_text();
  	        
  	### Escribimos en la etiqueta vacía los datos proporcionados por 
  	### por el usuario.	
  	$etiqueta->set_markup("El <b>país</b> introducido por el usuario fue: <span foreground=\"blue\">$pais</span>
  	La <i>ciudad</i> introducida fue: <span foreground=\"blue\">$ciudad</span>
  	");
  }
  sub on_boton_cerrar_clicked {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

Antes de ampliar la explicación del programa quiero mostrarles la captura de pantalla de este en funcionamiento.

Los elementos que contendrá la caja de entrada de texto son definidos en el glade(mirar las propiedades del widget).

Lo único nuevo que hemos incorporado en el ejemplo es el método que utilizamos para obtener los datos. get_active_text() es el método que nos permitirá extraer los datos introducidos(o seleccionados) por el usuario cuando se presione el botón "Aceptar" debido a que el método está dentro de la subrutina que se ejecuta cuando se presiona el botón aceptar. La única diferencia con respecto al ejemplo anterior es que en este ejemplo utilizamos get_active_text() y en el otro get_text()

6. Manejo de múltiples ventanas

Cuando desarrollamos aplicaciones gráficas es probable que necesitemos trabajar con varias ventanas. Lo común es que halla una ventana principal y otras que que aparecerán siempre y cuando se cumplan ciertas condiciones. Condiciones que se especificarán en el script.

Para el ejemplo utilizaremos el mismo programa anterior. Cuando la persona que escriba su nombre en el widget "Entrada de texto" le aparecerá una ventana preguntándole si está seguro de lo que desea hacer o quiere cancelar. Si se presiona aceptar entonces se procederá a escribir el nombre en la etiqueta, de lo contrario, se cancelará y se esconderá la ventana quedando solamente con la ventana principal (la cuál siempre está presente).

Para empezar es evidente que debemos de crear otra ventana en Glade con el mensaje y los dos botones que queremos que tenga (el de "Cancelar" y el de "Aceptar"), una vez hecho eso debemos asegurarnos que en Propiedades->Comunes tenga la propiedad "Visible" con el valor "No". Esto es para asegurarnos que seamos nosotros quienes muestren la ventana, de lo contrario (lo pueden corroborar) la ventana se cargará al inicio del programa y no cuando la persona quiera insertar su nombre. Recuerden que nosotros sólo queremos que la ventana se cargue cuando la persona presione en el botón "Recoger Datos"

NOTA: A partir de Glade-3 las ventanas vienen por defecto en estado No-Visible, por lo que es innecesario el paso anterior.

Así luce la ventana en glade:

Y así luce el programa en funcionamiento:

Nuestro código en Perl quedará de la siguiente manera:


  #!/usr/bin/perl
  use strict;
  use Gtk2 -init;
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $etiqueta, $widget_entrada_de_texto, $ventana_confirmar);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Declaro el objeto "$ventana_confirmar" quien contendrá los métodos
  ### necesarios para mostrar u ocultar la ventana de confirmación. Así
  ### como también otros métodos predefinidos que usaremos
  
  $ventana_confirmar = $programa->get_widget('ventana_confirmar');
  
  $etiqueta = $programa->get_widget('etiqueta');
  $widget_entrada_de_texto = $programa->get_widget('entrada1');
  
  $programa->signal_autoconnect_from_package('main');
  
  ### Norlmalmente en los programas nunca desaparece la ventana principal.
  ### De igual forma, podemos ocultarla con el método hide()
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  ### Se crea la subrutina que se ejecutará cuando el botón de 
  ### recoger datos sea presionado
  
  sub on_boton_recoger_datos_clicked {
          
  	### Aquí utilizo el método predefinido llamado ``show_all()``
  	### del objeto "$ventana_confirmar" que me permitirá 
  	### mostrarle al usuario la pantalla a la cual hace referencia
  	### dicho objeto(en este caso es la ventana de confirmación)
  
  	$ventana_confirmar->show_all();
  }
  
  ### Subrutina que se ejecutará cuando el usuario presione el botón 
  ### "Aceptar" de la ventana de confirmación. Esta subrutina se ejecuta
  ### cuando la ventana de confirmación está cargada, ya que allí es donde
  ### está el botón a la cual hace referencia esta subrutina
  
  sub on_boton_aceptar_clicked {
  	my($texto_introducido);
          $texto_introducido = $widget_entrada_de_texto->get_text();
          $etiqueta->set_markup("El nombre que colocaste fue: <b> $texto_introducido </b>");
  	
  	### Una vez que se introduce el nombre en la etiqueta vacía se 
  	### esconde la ventana, de manera que el usuario no la siga viendo
  
          $ventana_confirmar->hide();
  }
  
  ### Esta subrutina se ejecuta cuando el usuario hace click en el botón
  ### "Cancelar" de la ventana de confirmación.
  
  sub on_boton_cancelar_clicked {
  	
  	### Lo único que hace esta función es esconder la ventana de 
  	### manera que el usuario no la siga viendo.
  
          $ventana_confirmar->hide();
  }
  
  sub on_boton_cerrar_clicked {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

En este capítulo aparecen dos métodos predefinidos que son muy utilizados cuando trabajamos con múltiples ventanas. El primer método es show_all() el cual me permite mostrar el widget al cual hace referencia el objeto donde se usó este método, en nuestro caso ese objeto hace referencia a la ventana de confirmación para el usuario, por lo tanto, si usamos el método show_all() lo que estamos haciendo es mostrar la ventana. El otro método es hide() que es antagónico a show_all(). hide() es utilizado para esconder un widget, en nuestro caso sería esconder la ventana, de manera que seguirá cargada en memoria pero el usuario no la estará visualizando. Esta ventana la podemos mostrar y ocultar cuantas veces queramos.

7. Vistas

Las vistas son widgets que utilizamos para mostrar cualquier tipo de información al usuario quien utiliza el programa.

7.1. Vistas de texto - Creando un lector de archivos

El Widget Vista de texto (treeview del inglés) nos permite mostrarle al usuario algún texto, caracter, información, etc. Posee la propiedad (opcional) de que el usuario también pueda editar esta información. Para atestar de información el widget lo podemos hacer mediante la interfaz de glade o más útil aún, mediante el propio script. Cuando estamos mostrando la información mediante el script, debemos que crear un buffer donde introduciremos la información, para luego introducir todo ese buffer con información en el widget "Vista de Texto".

Con lo que hemos visto hasta ahora nos basta para crear un lector de archivos, en eso consistirá este ejemplo. Como es de imaginarse el contenido del archivo se mostrará en el Widget "Vista de texto" y haciendo uso de la ventana predefinida de selección archivos que trae Glade (GtkFileChooserDialog) cargaremos el archivo.

Lo único que debemos agregar a la ventana de selección de archivos son dos botones, "Cancelar" y "Abrir". De forma predefinida la ventana viene con dos aberturas donde colocar dichos botones.

Unas capturas de pantalla del lector de archivo:



El código es el siguiente:


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  
  ### Declaro Variables
  my($programa, $ventana_principal, $vista_de_texto, $buffer_de_texto, $ventana_selector_de_archivos);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  ### Cargo Widgets
  $ventana_principal = $programa->get_widget('ventana_principal');
  $ventana_selector_de_archivos = $programa->get_widget('ventana_selector_de_archivos');
  $vista_de_texto = $programa->get_widget('vista_de_texto');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  ### Esta es la subrutina que se ejecutará cuando la persona haga click en el botón
  ### abrir de la ventana de selección de archivo. Básicamente se abrirá el archivo
  ### que se halla seleccionado
  
  sub on_ventana_abrir_archivo_boton_abrir_clicked {
  
  	### Declaro un buffer que contendrá la información, estará vacío
          ### pero ya al menos tenemos una referencia a él para luego 
  	### poder introducir información.
  
  	$buffer_de_texto = Gtk2::TextBuffer->new;
  	my $archivo_seleccionado = $ventana_selector_de_archivos->get_filename;
  	open(LISTA, "$archivo_seleccionado") || die('No pude abrir el archivo');
  	while(<LISTA>){
  		### Se lee cada línea del archivo y a su vez se introduce 
  		### al buffer que declaramos anteriormente.
  		$buffer_de_texto->insert_at_cursor("$_");
  	}
  	close(LISTA);
  
  	### Ahora le asigno a la vista de texto el buffer ya atestado
  	### de información.
  	$vista_de_texto->set_buffer($buffer_de_texto);
  	$ventana_selector_de_archivos->hide;
  }
  
  ### Cuando presionen el botón abrir de la ventana principal deberá mostrarse 
  ### la ventana de selección de archivo (ver screenshot 2 arriba)
  
  sub on_boton_abrir_clicked {$ventana_selector_de_archivos->show_all;}
  
  ### Cuando la persona presione el botón cancelar de la ventana donde seleccionas el archivo
  ### ésta se debe esconder, es decir, ocultarse
  sub on_ventana_abrir_archivo_boton_cancelar_clicked  {$ventana_selector_de_archivos->hide;}
  
  sub on_boton_cerrar_clicked {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

Lo nuevo que hemos incluido es el uso del módulo Gtk2::TextBuffer que viene con Gtk2, por lo que no debemos declararlo con use.

Utilizamos el método new() para declarar un objeto que hace referencia a un Buffer vacío el cual llenaremos con información. Una ves que el usuario seleccionó el archivo tuvimos que abrirlo he introducir línea por línea al buffer, esto lo hicimos con el método insert_at_cursor del widget el cual recibe como argumento el texto que queremos introducir.

Por último cerramos el archivo, ya que no lo utilizaremos más y además ya está lleno el buffer con la información que contenía este. Para culminar debemos delegar el buffer al widget "Vista de texto" quién es el que mostrará el contenido del buffer al usuario, esto lo hacemos utilizando el método set_buffer que posee el widget vista de texto.

7.2. Vistas en forma de árbol

Las vistas de árbol son un componente gráfico que nos proporcionan una manera estructurada y jerárquica (en forma de árbol) para ver algún tipo de datos (números, texto, imágenes, etc ). Para trabajar con vistas de árbol en GTK y Perl utilizaremos un módulo dedicando explícitamente a esto, se llama Gtk2::SimpleList.

Gtk2::SimpleList es el módulo que nos permite generar e interactuar con las vistas de árbol.

Si ya tienes instalado el módulo Gtk2 es muy probable que ya tengas instalado también Gtk2::SimpleList de todas maneras lo puedes descargar de CPAN.

7.2.1. Estableciendo las columnas y algunas propiedades

Vamos a empezar con nuestro primer ejemplo cuyo objetivo será crear una vista de árbol en Glade para luego en el programa asignarle las columnas junto con el tipo de valor que contendrá cada una de estas. También especificaremos algunas propiedades de las columnas, como por ejemplo si podrás ser presionadas, redimensionables en cuanto a tamaño, etc. Muchas veces es útil colocarle a la columnas la característica de que puedan ser presionadas debido a que es común que queramos ordenar los datos de la misma mediante un criterio(por ejemplo orden alfabético).

Nuestro programa lucirá de la siguiente manera:

NOTA: la primera captura de pantalla es de la interfaz en Glade y la segunda es del programa ya ejecutandoce.

Lo único que hay que hacer en glade es agregar el widget de vista de árbol y asignarle un ID(o nombre), lo demás se hará desde el scrip. Ahora vamos a ver el código del programa.


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  use Gtk2::SimpleList;
  
  my($programa, $ventana_principal, $vista_arbol, $vista_de_arbol_widget);
  
  $programa = Gtk2::GladeXML->new('vista_arbol.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  
  ### Declarando el objeto vista_de_arbol_widget que me permitirá
  ### interactuar con el widget mediante el módulo Gtk2::SimpleList
  $vista_de_arbol_widget = $programa->get_widget('vista_de_arbol');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  ### Llamamos a la función que hará todo lo referente a la vista de árbol
  &todo_vista_arbol;
  
  Gtk2->main;
  
  sub todo_vista_arbol {
          ### Declarando el objeto $vista_arbol el cuál manejará y controlará todo
          #### lo referente a la vista de árbol.
          $vista_arbol = Gtk2::SimpleList->new_from_treeview($vista_de_arbol_widget,
                          'Nombre'        => 'text',
                          'Apellido'      => 'text',
                          'Edad'          => 'int'
                          );
  
          ### Hacemos que las columnas puedan ser presionadas
          $vista_arbol->set_headers_clickable(1);
  
          ### Mediante el foreach trabajaremos y le asignaremos propiedades
          ### a cada columna
          foreach ($vista_arbol->get_columnAl igual que casi todo esto se hace mediante widgets, las dos más comunes son: mediante entradas de texto o mediante entradas de caja de combo.s()) {
                  
  		### Hacemos que la columna pueda ser redimensionable.
                  ### Si no queremos que lo sea cambiamos el '1' por '0'
                  $_->set_resizable(1);
                  ### Aquí estamos especificando la propiedad de tamaño de la columna.
                  $_->set_sizing('grow-only');
          }
  }
  
  sub on_boton_salir_clicked {Gtk2->main_quit;}

Descargar código del programa sin comentarios

En el programa anterior utilizamos el método new_from_treeview del módulo Gtk2::SimpleList el cuál recibe como parámetro el objeto $vista_de_arbol_widget que fue declarado en anteriormente. Otro parámetro que recibe el método new_from_treeview es las columnas que queremos que tenga nuestra vista de árbol junto con el tipo de valor que contendrá cada una. En el ejemplo anterior estoy declarando que habrán 3 columnas(Nombre, Apellido, Edad). Los valores para Nombre y Apellido serán textos normales(valor 'text') y para la edad serán números enteros(valor 'int').

Probablemente te estarás preguntando por que hacer que las columnas tengan la propiedad de ser presionadas, para que se entienda mejor, imagínate que tienes muchos nombres en la primera columna(la columna 'Nombres', y quieres que esta los ordene todos de forma alfabética, para esto tenemos que hacer que las columnas tenga la propiedad de ser presionadas, de manera que cuando el usuario presione la columna 'Nombre' esta se ordene de manera automática. El método set_headers_clickable recibiendo el parámetro '1' permite que las columnas tengan la propiedad de ser presionadas y recibiendo el parámetro '0' pasa lo contrario.

7.2.2. Añadiendo registros a la vista de árbol

Ya que hemos aprendido como declarar las columnas y es hora de que empecemos a agregar datos a estas. Agregar datos es muy sencillo, solo utilizamos un método del objeto principal(en el ejemplo anterior el objeto vista_arbol)

Utilizaremos el mismo ejemplo anterior y nuestro programa se verá de la siguiente forma:

El programa será el siguiente:


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  use Gtk2::SimpleList;
  
  my($programa, $ventana_principal, $vista_arbol, $vista_de_arbol_widget);
  
  $programa = Gtk2::GladeXML->new('vista_arbol.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  $vista_de_arbol_widget = $programa->get_widget('vista_de_arbol');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  &todo_vista_arbol;
  
  Gtk2->main;
  
  sub todo_vista_arbol {
          $vista_arbol = Gtk2::SimpleList->new_from_treeview($vista_de_arbol_widget,
                          'Nombre'        => 'text',
                          'Apellido'      => 'text',
                          'Edad'          => 'int'
                          );
  
          $vista_arbol->set_headers_clickable(1);
  
          foreach ($vista_arbol->get_columns()) {
                  $_->set_resizable(1);
                  $_->set_sizing('grow-only');
          }
  
          @{$vista_arbol->{data}} = (
                  ['Alejandro', 'Garrido', '17'],
                  ['Andreina', 'Garrido', '12'],
                  ['Indira','Gonzalez', '34'],
                  ['Jaime','Velazquez','47']
          );
  
  }
  
  sub on_boton_salir_clicked {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

En este ejemplo hemos utilizado arrays de arrays o también denominado 'listas de listas' donde hay elementos que son las filas y elementos que es los datos que tiene cada columna. Podemos agregar cuantas filas querramos. Normalmente lo útil es hacerle un "parser" a un archivo que tenga nombres, apellidos y edades para mostrarlos en la vista de árbol.

Es importante que notes el orden en que se introducen los datos ya que los estos deben estar entre comillas y separados por comas. Todo lo que está entre corchetes es una fila. El primer valor que se encuentre entre corchetes será lo que aparecerá en la primera columna, el segundo valor en la segunda y así sucesivamente.

7.2.3. Algunos ejemplos sencillos

Para culminar con el capítulo sobre vistas de árbol voy a realizar y explicar dos ejemplos muy útiles donde se le puede notar el uso a todo lo referente a vistas de árbol.

7.2.3.1. Llenando la vista de árbol desde el programa

Este primer ejemplo se basará en que la persona tendrá que llenar unos campos y una vez llenos los mismo tendrá que presionar el botón 'Insertar' para que luego esos valores que colocó en los campos de entrada aparezcan inmediatamente en la vista de árbol

Nuestro programa lucirá así:

Este ejemplo es muy simple, si has leído todos los capítulos anteriores lo entenderás de manera inmediata.


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  use Gtk2::SimpleList;
  
  my($programa, $vista_arbol, $vista_de_arbol_widget);
  
  ### Declaro los objetos widgets
  my($ventana_principal, $entrada_nombre, $entrada_edad, $entrada_comida, $entrada_telefono);
  
  $programa = Gtk2::GladeXML->new('vista_arbol.glade');
  
  ### Cargo todos los widgets, incluyendo los de las entradas de texto
  ### para poder extraer el valor que introduzca el usuario en los campos.
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  $vista_de_arbol_widget = $programa->get_widget('vista_de_arbol');
  $entrada_nombre = $programa->get_widget('entrada_nombre');
  $entrada_edad = $programa->get_widget('entrada_edad');
  $entrada_comida = $programa->get_widget('entrada_comida');
  $entrada_telefono = $programa->get_widget('entrada_telefono');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  ### Llamo a la función todo_vista_arbol la cuál me dibujará
  ### las columnas y establecerá las propiedades de cada una de
  ### ellas
  
  &todo_vista_arbol;
  
  Gtk2->main;
  
  sub todo_vista_arbol {
  	
  	### Establezco las columnas y los tipos de valores que contendrán
  	### las mismas.         
  	$vista_arbol = Gtk2::SimpleList->new_from_treeview($vista_de_arbol_widget,
                          'Nombre'                => 'text',
                          'Comida Favorita'       => 'text',
                          'Teléfono'              => 'scalar',
                          'edad'                  => 'int'
                          );
  
  	### Hago que las columnas tengan la propiedad de ser presionables.
  	### Puedes comentar esta línea ya que realmente no hace y produce 
  	### nada en el programa.
  
          $vista_arbol->set_headers_clickable(1);
  	
  	### Para cada una de las columnas le establezco unas propiedades
  	### por como por ejemplo que sea redimensaionables(set_resizable)
  
          foreach ($vista_arbol->get_columns()) {
                  $_->set_resizable(1);
                  $_->set_sizing('grow-only');
          }
  }
  
  ### Función que se ejecutará cuando la persona presione el botón insertar.
  sub on_boton_insertar_clicked {
  	
  	### Declaro las variables que contendrán cada valor introducido por
  	### el usuario en cada uno de los campos(una variable por cada campo)
           
  	my($nombre, $comida, $edad, $telefono);
  	
  	### Extraigo los valores introducidos por el usuario en cada una de
  	### las entradas.	
  		    
  	$nombre = $entrada_nombre->get_text();
  	$comida = $entrada_comida->get_text();
  	$edad = $entrada_edad->get_text();
  	$telefono = $entrada_telefono->get_text();
          
  	### Introduzco a la lista(o array) cada uno de los calores de manera que los
  	### agregue a la vista de árbol.		
  
  	push @{$vista_arbol->{data}}, [$nombre, $comida, $telefono ,$edad];
  }
  
  sub on_boton_salir_clicked {Gtk2->main_quit;}

Descargar código del programa sin comentarios

Cuando el usuario llena todos los campos de entrada y presiona el botón 'Insertar' se ejecuta la función on_boton_insertar_clicked la cual utilizando los métodos predefinidos de los objetos(declarados al principio de programa) que hacen referencia a los campos de entrada extraerá los valores introducidos por el usuario. Ha sido este un buen ejemplo de por que es necesario declarar objetos por cada widget, ya que de lo contrario no podríamos extraer el valor de cada una de las entradas sin previamente haber declarado un objeto que hiciera referencia al widget de los campos de entrada.

Una vez que extraemos los valores lo que hacemos es colocarlos en la lista @{$vista_arbol->{data}} de manera que sean agregados automáticamente a la lista y el usuario los pueda ver de manera inmediata. Aclaro que todo lo que coloquemos en esa lista o array es lo que aparecerá como filas en la vista de árbol.

7.2.3.2. Extrayendo los registros de un archivo

Este segundo ejemplo será un poco más elaborado debido a que tendremos un archivo plano el cuál contendrá los valores(separados por comas) que se mostrarán en el programa en forma de vista de árbol. Siempre debe haber un caracter de separación, yo utilicé la coma pero es factible utilizar otros (espacios, punto, comas, etc)

El programa lucirá así:

El archivo que contendrá los valores que se mostrarán tendrá la siguiente estructura:

NOMBRE, APELLIDO, TELÉFONO, CUIDAD, PAÍS

El nombre de archivo para el ejemplo será "datos_personales.html" y su contenido será el siguiente:

  Andreina, Garrido, 0412-7223721, Caracas, Venezuela
  Alejandro, Garrido, 0414-2297329, Caracas, Venezuela
  Nicolas, Gonzalez,, San Juan de los Morros, Venezuela
  Indira, Gonzale,, Alicante, España

Notar que el delimitador entre valores es la coma(",") por lo que cada valor comprendido entre este delimitador se guardará una variable para luego incorporar esas variables al la lista(array) principal para que sea muestre en la vista de árbol.

El programa en Perl sería el siguiente:


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  use Gtk2::SimpleList;
  
  my($programa, $vista_arbol, $vista_de_arbol_widget);
  my($ventana_principal, $entrada_nombre, $entrada_edad, $entrada_comida, $entrada_telefono);
  
  $programa = Gtk2::GladeXML->new('articulo.glade');
  
  $ventana_principal = $programa->get_widget('ventana_principal');
  $vista_de_arbol_widget = $programa->get_widget('vista_de_arbol');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  &todo_vista_arbol;
  
  &filas_vista_arbol;
  
  Gtk2->main;
  
  ### Todo igual, estableciendo columnas y las propiedades de la vista de árbol
  
  sub todo_vista_arbol {
          $vista_arbol = Gtk2::SimpleList->new_from_treeview($vista_de_arbol_widget,
                          'Nombre'                => 'text',
                          'Apellido'              => 'text',
                          'Teléfono'              => 'scalar',
                          'Cuidad'                => 'text',
                          'País'                  => 'text'
                          );
  
          $vista_arbol->set_headers_clickable(1);
  
          foreach ($vista_arbol->get_columns()) {
                  $_->set_resizable(1);
                  $_->set_sizing('grow-only');
          }
  }
  
  ### Subrutina que se ejecutará para agregar las filas a la vista de árbol
  ### basándose en lo que contenga en el archivo
  
  sub filas_vista_arbol {
  	### Estoy abriendo el archivo datos_personales.txt el cual es el que 
  	### contiene todos los registros que aparecerán en la vista de árbol
  	open(ARCHIVO, 'datos_personales.txt') || die('No existe ese archivo');
  	while(<ARCHIVO>){
  		### Quitando el salto de línea               
  		chomp; 
  		### Estoy haciéndole un split a cada línea del archivo y para que
  		### todo lo que esté entre comas lo asigne a cada una de las 
  		### variables que aparecen declaras con my()
                 
  		my($nombre, $apellido, $telefono, $cuidad, $pais) = split(/,/, $_);
  		### Cada uno de los valores extraidos del archivo que ya están
  		### guardados en sus respectivas variables los guardo en el array
  		### principal donde se mostrarán en la vista de árbol                
  		push @{$vista_arbol->{data}}, [$nombre, $apellido, $telefono, $cuidad, $pais];
          }
  	### Cierro el archivo        
  	close(ARCHIVO);
  }
  
  sub on_boton_salir_clicked {Gtk2->main_quit;}

Descargar código del programa sin comentarios

El programa te debe haber resultado sencillo de entender si ya has utilizado o manejador archivos con Perl. Primero lo que hacemos es abrir el archivo cuyo nombre es datos_personales.txt, una vez hecho esto leemos línea por línea para hacerle un split indicando el delimitador para poder de manera que podamos guardar cada valor en una variable. Una vez que logramos guardar cada registro en variables procedemos a introducir todas esas variables en el array principal de manera que aparezcan en la vista de árbol.

8. Calendarios

Gtk2 posee un widget que nos permite recoger la el año, día y mes, como es de imaginarse es un calendario. El nombre del widget es GtkCalendar. Debemos de tomar en cuenta que los meses no comienzan desde el número 1 si no desde el número 0, por ende tendrán un ámbito del 0 al 11.

El ejemplo consistirá en habrá calendario y el usuario escogerá un fecha, seguidamente presionará el botón '¿Qué fecha coloqué?' para luego mostrarle una pantalla con los datos que introdujo (día, mes y año). Una captura de pantalla del programa:

El script contiene lo siguiente:


  #!/usr/bin/perl
  use strict;
  use Gtk2 '-init';
  use Gtk2::GladeXML;
  
  my($programa, $ventana_principal, $vetana_fecha, $calendario, $etiqueta_para_escribir_fecha);
  
  $programa = Gtk2::GladeXML->new('prueba.glade');
  
  ### Cargo Widgets
  $ventana_principal = $programa->get_widget('ventana_principal');
  $calendario = $programa->get_widget('calendario');
  $vetana_fecha = $programa->get_widget('ventana_fecha');
  $etiqueta_para_escribir_fecha = $programa->get_widget('etiqueta_para_escribir_fecha');
  
  $programa->signal_autoconnect_from_package('main');
  
  $ventana_principal->show_all();
  
  Gtk2->main;
  
  ### Subrutina que se ejecutará cuando el usuario haga click 
  ### en el bontón '¿Qué fecha coloqué?'
  
  sub on_boton_averiguar_fecha_clicked {
         
          ### Declaro tres variables y con el método predefinido get_text
          ### se les asigna a estas su valor correspondiente.
          my($ano, $mes, $dia) = $calendario->get_date;
          
          ### Ya una vez teniendo los datos año, mes, día en variables lo 
          ### lo que hago es imprimirlos es la etiqueta vacía
          
          $etiqueta_para_escribir_fecha->set_markup("El año que seleccionaste fue: $ano
  El mes que seleccionaste fue: $mes\n El día que seleccionaste fue: $dia");
          ### Muestro la ventana con los la etiqueta ya llena.
          $vetana_fecha->show_all;
  }
  sub on_ventana_fecha_boton_aceptar_clicked {$vetana_fecha->hide;}
  sub on_boton_salir_clicked {Gtk2->main_quit;}
  

Descargar código del programa sin comentarios

9. Botones

Los botones son mecanismos simples que nos permiten obtener preferencias del usuario. Existe una gran variedad de botones, los que conozco son los que explicaré a continuación.

9.1. De verificación

El botón de verificación(botones checkout) es el que posee dos estados: activado o desactivado. Con esto podemos obtener algunas preferencias del usuario en base al estado de dicho botón.

Este ejemplo consiste en una ventana sencilla donde nos indicará si el botón está activado o desactivado. Una captura de pantalla del programa será la siguiente:

10. ¿Dónde puedo conseguir más información?

Muchas personas me preguntan si existe alguna lista donde estén todos los métodos predefinidos que puede contender los objetos que hacen referencia a los widgets, por eso siempre les recomiendo un paseo por la documentación oficial del proyecto Gtk2-Perl donde se encuentra la mayor cantidad de información acerca de Perl y Gtk2. Siempre que vayan a utilizar un widget que nunca antes han usado les recomiendo dar un paseo por la documentación oficial para hojear que métodos tienen disponible y como usarlos. La página del proyecto también incluye artículos (en inglés) que pueden ser de utilidad.

11. Sobre este documento...

Copyright (c) 2006 Alejandro Garrido Mota.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".