Programación de Perl y Gtk


Perl es un lenguaje de programación diseñado por Larry Wall en 1987. Perl toma características del lenguaje C, del lenguaje interpretado bourne shell (sh), AWK, sed, Lisp y, en un grado inferior, de muchos otros lenguajes de programación.

Estructuralmente, Perl está basado en un estilo de bloques como los del C o AWK, y fue ampliamente adoptado por su destreza en el procesado de texto y no tener ninguna de las limitaciones de los otros lenguajes de script.

Programación de Interfaces Gráficas con Perl y Gtk

Este tutorial comprende una introducción a la programación de interfaces gráficas con Perl utilizando sus bindings de Gtk2.

Es recomendado que el lector tenga nociones de Perl sobre todo de su enfoque oriendado a objetos,
referencias, subrutinas y manejo de módulos.

El desarrollo del tutorial va de la siguiente manera: La primera parte consta de un paseo introductorio sobre los conceptos básicos de Gtk y sus componentes, una breve descripción del modelo de eventos y mostrando así nuestro primer programa.

La segunda parte comprende el uso de Gtk2 en Perl, una reseña del uso de la API de C sobre Perl, conceptos de widget y contenedor, señales y eventos, y agrupamiento y posicionamiento de widgets, terminando esta parte con un pequeño visualizador de imagenes.

La tercera y última parte constituye la unión de las primeras 2, anexándole el uso de una cantidad considerable de widgets, y reafirmando los conceptos ya vistos, para así elaborar un pequelo cliente de SQL.

1. INTRODUCCIÓN

1.1 ¿Qué es Gtk?

El Gimp Toolkit es un conjunto de bibliotecas multiplataforma, que crea una poderosa herramienta útil para modelar y crear interfaces gráficas con grandes capacidades, principalmente utilizadas por la suite Gnome.


1.2 Sus componentes principales

GDK ( Gimp Drawing Kit )
Todo lo que tenga que ver con dibujar ( polígonos, líneas ), carga y manipulación de imágenes, y en general cualquier tipo de elemento gráfico, corre a cargo de Gdk.
Glib
Conjunto de bibliotecas que conforman todas el corazón de Gtk y Gnome. Se encarga principalmente de todo el manejo de estructuras de datos, portabilidad, el manejo del sistema de objetos, hilos( threads ) y en general todo lo que corresponde al sistema base de Gtk.
ATK ( Accesibility Toolkit )
Este conjunto de bibliotecas permiten que Gtk mantenga todas esas opciones de accesibilidad desde teclas especiales y dispositivos especiales.
Pango
Se encarga de todo lo relacionado al manejo de fuentes, internacionalización y afines.

1.3 El modelo de eventos ( Event Driven Model )

El funcionamiento de Gtk probablemente (y muy posiblemente) no fuera satisfactorio sin un modelo gobernado por eventos.

Es decir, Gtk no funciona de manera lineal, no se puede anticipar cual será la acción que el usuario elegirá para interactuar con la aplicación y es por tanto que el modelo de eventos-señales está allí para dar control a todos los sucesos ocurridos con dichas interacciones.

En pocas palabras lo que constituye a éste modelo es: un despachador principal, el cual casi todo el tiempo está en espera de eventos( <Gtk2-main> ).

Cuando ocurre un evento es insertado en una cola en la cual cada uno de acuerdo a su turno emite una señal de suceso y el usuario pueda cacharlo y realizar alguna fución específica.

Mas delante en el tutorial veremos un capítulo dedicado a eventos y señales.


1.4 Hola Mundo

No podía hacernos falta como ejemplo introductorio el ya muy conocido y poco temido "Hola Mundo", el cual nos mostrará una breve sinópsis de la sintaxis básica de Gtk2 en Perl:


Codigo Fuente:

#!/usr/bin/perl
#
# hello.pl
#
# Autor: Marco Antonio Manzo <amnesiac@unixmonkeys.com>
#
# Descripción:
#   Programa1 de ejemplo - Tutorial Gtk2-Perl
#
# Licencia:
# Este programa puede ser distribuido y modificado bajo
# los mismos términos de la licencia artística de Perl.
#

use strict;
use warnings;

# Cargamos nuestra biblioteca y la inicializamos.
# usar '-init' es Igual a utilizar Gtk->init en el cuerpo del
# programa.
use Gtk2 '-init';

# use Glib qw( TRUE FALSE );
# Definición de constantes que generalmente son
# utilizadas para encender/apagar atributos.
use constant TRUE => 1;
use constant FALSE => !TRUE;

# función( callback ) para manejar el evento 'clicked'
# al presionar el botón.
sub clicked {
    Gtk2->main_quit;
}

# Generamos nuestra nueva ventana instanciando Gtk2::Window
# y le establecemos algunos atributos como el grosor del
# borde. Después conectamos la señal del evento 'destroy'
# con nuestro callback, dicho evento se presenta cuando le damos
# "cerrar" a nuestra ventana
my $window = Gtk2::Window->new( 'toplevel' );
$window->set_border_width( 10 );
$window->signal_connect( destroy => sub{ Gtk2->main_quit } );

# Nuestro mensaje inicial, instanciando Gtk2::Label
my $label = Gtk2::Label->new( 'Hola Mundo( puff!! )' );

# Creamos nuestro botón para cerrar la aplicación, nuevamente
# creamos un objeto de tipo Gtk2::Button. Después conectamos
# la señal del evento 'clicked' hacia nuestro callback definido
# arriba.
my $button = Gtk2::Button->new( 'Salir' );
$button->signal_connect( clicked => \&clicked );

# Mas delante en el tutorial veremos a fondo la clase Gtk2::VBox
# la cual nos ayuda a agrupar widgets.
my $vbox = Gtk2::VBox->new( 5, FALSE );
$vbox->pack_start( $label, FALSE, FALSE, FALSE );
$vbox->pack_start( $button, FALSE, FALSE, FALSE );

# Agregemos nuestra caja con objetos a la ventana, y una vez
# todo listo mostramos los objetos.
$window->add( $vbox );
$window->show_all;

# Y finalmente... nuestro event loop de gtk2, el cual queda
# corriendo mientras no se reciba el Gtk2::main_quit ( o
# en su defecto el programa sea interrumpido )
Gtk2->main;


2. DE C A PERL: UN PASEO POR LA API

Algo peculiar y muy importante que comparten ambos proyectos es su documentación. Y, puesto que la API (Application Programming Interface) de Gtk en C es una referencia completa, el proyecto de documentación de Gtk2-Perl no pretende hacer una copia textual de la de C, por lo tanto se limita a ofrecernos una referencia sobre los métodos de cada clase y algunos cambios que sean de importante mención.

En esta sección se abordarán los puntos básicos e importantes para hacer uso de la API de C en nuestras aplicaciones en Perl y Gtk2. Dichos puntos están completamente documentados en Gtk2::api el cual se encuentra automáticamente en la instalación de Gtk2 y claro, incluyendo la página principal del proyecto y directamente en CPAN.


Espacios de nombre y objetos

Los espacios de nombre en C ( g_,gtk_,etc ) están asociados directamente con sus paquetes correspondientes en Perl. Veamos una tabla con los correspondientes espacios de nombre de C en Perl, los cuales nos ayudarán de mucho al momento de hacer referencia a cualquiera de las clases que vayamos a utilizar en nuestras aplicaciones:


(Glib) g_ => Glib
(Gtk) gtk_ => Gtk2
(Gdk) gdk_ => Gtk2::Gdk
(Pango) pango_ => Gtk2::Pango

Ahora bien, los objetos en Perl toman su espacio de nombre automáticamente de acuerdo al paquete que corresponde siendo propiamente instanciados, y es gracias a esto, que en Perl no se necesitan hacer conversiones de tipo, puesto que los bindings que nos ofrece lo realizan automáticamente en su capa de más bajo nivel (XS). Por ejemplo:


GtkWindow => Gtk2::Window
GdkPixbuf => Gtk2::Gdk::Pixbuf
---------------------------------------------
en C:
GtkWidget *boton;
boton = gtk_check_button_new_with_mnemonic( 'Ejecutar' );
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( boton ), TRUE );
gtk_widget_show( boton );

en Perl:
my $boton = Gtk2::CheckButton->new( 'Cambiar' );
$boton->set_active( TRUE );
$boton->show;

Es sencillo apreciar la facilidad con la que Perl nos permite usar Gtk2 de una manera Orientada a Objetos (a la manera de verlos en Perl) y hacer uso de una manera agradable el llamado de los métodos correspondientes a la clase. Este tipo de libertades son muy propias de Perl.


Constantes ( Flags/Enums )

Las constantes utilizadas como banderas(flags) o valores enumerados son manejadas en Gtk2-Perl como simples cadenas. Mostremos algunas equivalencias:

GTK_WINDOW_TOP_LEVEL => 'toplevel'


GTK_WINDOW_TOP_LEVEL => 'toplevel'
GTK_STOCK_OK => 'gtk-ok' # *
GTK_BUTTONS_OK_CANCEL => 'gtk-cancel'

*Cabe señalar que los objetos de "stock" no todos persiguen la misma nomenclatura, la lista completa de los elementos de "stock" puede ser obtenida utilizando la clase Gtk2::Stock y mas específicamente el método list_ids.


Otros puntos importantes

Funciones que retornan 2 o mas valores

Puesto que en C es "imposible" retornar múltiples valores, la manera con la que generalmente se realiza dicha actividad es utilizando el paso de apuntadores a la función que requiera de modificarlas. En Perl, es tan natural que simplemente se regresa una lista de elementos como se muestra a continuación:


en Perl:
( $ancho, $alto ) = $ventana->get_size;
@atributos = $ventana->get_size;

mientras que en C:
gtk_window_get_size( GTK_CONTAINER( ventana ), *ancho, *alto );

Sencillo eh? En realiad no es una comparación sobre funcionalidad, sino sobre la manera en la que sencillamente podemos manipular los datos que deseemos.


Manejadores de señales ( callbacks )

De la misma manera que el ejemplo anterior, Perl permite utilizar de manera nata los manejadores de señales, generando los callbacks ( funciones que permiten manejar específicamente un suceso ) como referencias a código (coderefs) o por medio de subrutinas anónimas como lo mostramos a continuación:


en C:
gtk_signal_connect( GTK_OBJECT( boton ), "clicked" ,
G_CALLBACK( imprime ),
(gpointer) "hola mundo" );

en Perl:
$boton->signal_connect( clicked => \&imprime, "hola mundo" );
o mejor aun:
$boton->signal_connect( clicked => sub { print "hola mundo" } );

Con esto podemos observar la naturaleza funcional (del paradigma funcional) que también nos ofrece Perl.


Otras consideraciones

Lo anterior fué solo un resumen del contenido que podremos consultar de Gtk2::api, aun falta por tocar el tema de cambios en ciertos nombres de funciones, algunas modalidades de métodos, manejo de memoria, entre otros. Aspectos los cuales quedan fuera del alcance de este tutorial pero que se requiere y se recomienda consultarlos.
Si bien la documentación original de la API de Gtk es la principal ayuda, hay aspectos que pueden ser complementados con los manuales y ejemplos del proyecto Gtk2-Perl. Al final del documento se citan algunas URL's sobre la documentación pertinente.

3. WIDGETS Y CONTENEDORES

Sabemos que en la computadora podemos representar información utilizando algunas formas reales y algunas muy abstractas. En el lingo de los GUIs es importante mencionar que, aunque la mayoría son abstracciones de algún objeto real, es necesario dividir dichas abstracciones en 2 bandos, una para almacenar objetos y otra los objetos como tal.


3.1 ¿Qué es un contenedor?

Un contenedor es un "recipiente" de widgets, un almacén donde podemos ponerlos, acomodarlos y ordenarlos, y que como grupo, podemos establecerles algunos atributos "globales" a todos ellos dentro de este contenedor. Así mismo, un contenedor es un también un widget.

Existen varios tipos de contenedores, entre ellos podemos mencionar las ventanas(Gtk2::Window), los frames(Gtk2::Frame), las cajas(Gtk2::Box), menús(Gtk2::Menubar), entre otros, y que todos estos mencionados heredan la mayoría de sus atributos y comportamientos como contenedores de la clase Gtk2::Container.


3.2 ¿Qué es un widget?

Entonces... qué es un widget? Bueno su significado literal en español realmente es "cosa". Así es, widget es una palabra de juego del Inglés que sirve para describir una "cosa" normalmente manufacturada en pequeño. En el mundo de los GUI los widgets son "componentes" que nos ayudan a crear otros componentes, ensamblarse con otros componentes o simplemente hacerlos funcionar.

Botones, ventanas, tablas, listas, menús, selectores, diálogos, espacios de texto, todos ellos son widgets, y algunos necesitan ensamblarse en otros para visualizarse correctamente como lo veremos en ejemplos posteriores.


3.3 ¿Cómo uso un widget?

En general cada widget tiene su comportamiento, sus métodos y atributos. Sin embargo, podemos describir de manera general los pasos a seguir para utilizar cualquier widget:

1) Instanciar el widget por medio de su constructor => Gtk2::*->new( ... );
2) Establecerle atributos propios o heredados.
3) Conectar los manejadores hacia las señales emitidas por los eventos que nos interesen de dicho widget.
4) Agrupar o empacar dicho widget en algun contenedor y darle algún atributo especial de posicionamiento.
5) Mostrar el widget => Gtk2::Widget->show( ... ).
Analicemos cada uno de los pasos creando una ventana:


my $ventana = Gtk2::Window->new( 'toplevel' );
$ventana->set_border_width( 4 );
$ventana->set_default_size( 300, 400 );
$ventana->signal_connect( destroy => sub{ Gtk2->main_quit } );
$ventana->show;

En la primera línea creamos un nuevo objeto de tipo Gtk2::Window permitiéndole estar al frente de todas las ventanas(toplevel). Una vez teniendo nuestro objeto, en las líneas 2 y 3 establecemos algunos atributos, en este caso el ancho del borde de nuestra ventana hacia sus descencientes, y el tamaño inicial que ocupará. En la línea 4 conectamos la señal emitida por el evento "cerrar ventana" a una función que termine nuestra aplicación. Y finalmente mostramos nuestra ventana.

4. EVENTOS Y SEÑALES

4.1 ¿Qué es un evento?

Como ya vimos al inicio del tutorial, Gtk es un conjunto de herramientas las cuales están gobernadas por un modelo de señales y eventos. Cuando damos un click con el mouse sobre un botón, cuando arrastramos un elemento de una lista, cuando seleccionamos algún elemento de un menú, cada una de estas acciones constituyen un evento.

Por lo tanto podemos definir un evento como el resultado de la interacción entre el usuario y la aplicación.


4.2 ¿Qué es una señal?

Una vez ejecutado y conocido el evento, la manera en la se nos permite cacharlo o mas propiamente, la manera en la que nos permite manipularlo es emitiendo un "mensaje" de regreso al programa diciendo que dicho evento sucedió, y así el usuario pueda, mediante un callback, manipularlo. A ese mensaje, se le conoce como señal.

Es importante mencionar que cuando aquí nos referimos a señal, no hablamos propiamente de señales como lo realiza el sistema operativo( llamense POSIX ), si bien puede ser una buena analogía, el manejo y manipulación de ellas se realiza de una manera muy diferente, por lo tanto queda dicho que cuando hablemos de señales en este tutorial nos referimos implicitamente a las de Gtk.


4.3 ¿Cómo conecto una señal?

Bien, ya establecimos que la señal es un mensaje el cual nos indica el evento que sucedió. Para que nuestro programa realice una acción determinada tras la ejecución de dicho evento, es necesario "conectar" esa señal emitida con nuestra función( callback ) de la siguiente manera:


Forma canónica:
Glib::Object->signal_connect( objeto, señal, función, [ parámetros ] )

Aplicado:
$widget->signal_connect( evento => \&funcion )

Recordando que todos los widgets(y contenedores, por ser widgets) heredan de GObject, la forma canónica y la jerarquía de clases nos indican que CUALQUIER widget puede emitir señales si así está indicado en su naturaleza. Veamos un ejemplo utilizando un botón:


...
my $boton = Gtk2::Button->new( '_Salir' );
$boton->signal_connect( clicked => sub { Gtk2::main_quit } );
...

La primera línea nos permite instanciar la clase Gtk2::Button añadiéndole una etiqueta, que sería similar a llamarlo con el constructor new_with_mnemonic, sin embargo estamos en Perl, y podemos hacerlo de esa manera. La siguiente linea nos indica que al recibir una señal de que el evento "clicked" fué realizado, se ejecute el callback alli mencionado ( en este caso una subrutina anónima ) el cual llama a <Gtk2-main_quit> para finalizar la aplicación.

También cabe señalar que no son las únicas funciones para manipular señales, es necesario asomarse a la referencia completa sobre señales en la API.

5. Agrupación y posicionamiento de widgets

Normalmente cuando elaboramos una aplicación GUI tenemos la necesidad de colocar nuestros widgets en lugares clave para su utilización, o simplemente para darle ese look'n feel a nuestro programa. Otro caso podría ser que queremos que al cambiar el tamaño en pantalla de nuestra aplicación, los widgets mantengan su estado original o crezcan proporcionalmente a este.

O bien, queremos agrupar un conjunto de widgets (que pueden o no tener algo en común), y que al modificar algún atributo de posicionamiento a este paquete, todos los widgets respeten dichas reglas.

En Gtk el agrupamiento y posicionamiento relativo de widgets tiene que ver con contenedores tales como Cajas y Tablas. Estos contenedores tienen la habilidad de agrupar widgets y establecerles atributos de posicionamiento. Así pues, dichos contenedores nos dan la oportunidad de ofrecer también un posicionamiento automático si así es requerido. En este tutorial nos centraremos principalmente a los widgets de tipo Box(caja) las cuales son mayormente utilizados.

Para la utilización de tablas se recomienda la documentación contenida en Gtk2::Table

Las cajas nos permitirán un agrupamiento de widgets de manera Horizonal(HBox) o Vertical(VBox) segun sea necesario. Dichos contenedores nos permitirán establecer atributos a nuestro grupo de widgets tales como el acolchonado, expansión y llenado de espacio.

Veamos cada uno de los atributos con el siguiente ejemplo:


$vbox = Gtk2::VBox->new( FALSE, 3 );
$vbox->pack_start( $menu, TRUE, FALSE, 3 );
$vbox->pack_start( Gtk2::HSeparator->new, FALSE, FALSE, 3 );
$vbox->pack_start( $img, TRUE, TRUE, 0 );

Primeramente se creó una caja vertical, la cual contiene los siguientes atributos:


# $widget = Gtk2::VBox->( homogeneous, spacing );
$widget = Gtk2::VBox( tamaño_homogeneo, espaciado ) # Gtk2::HBox aplica igual

El primer parámetro se refiere a un valor verdadero/falso que indique si queremos que los widgets tengan todos el mismo tamaño o no. El siguiente parámetro se refiere a la cantidad de espacio( en pixeles ) que queremos entre cada elemento de la caja, es decir entre cada widget empacado.

Una vez creado nuestra caja, y teniendo listos los widgets a utilizar, llamamos pack_start()/pack_end que son métodos contenidos en la clase Gtk2::Box, según el posicionamiento interno que queramos darle, es decir si quieremos que se posicionen al inicio o al final de la caja respetando su posición con los otros widgets, y sus respectivos parámetros:


# Gtk2::Box->pack_start( widget, expand, fill, padding );
Gtk2::Box->pack_start( widget, expandir, llenar, acolchonado ); #pack_end(...)

El atributo expandir nos permite decirle a nuestra caja que todos los widgets contenidos tendrán el mismo tamaño. llenar únicamente será valido si expandir está encendido, de esta manera le indicamos a nuestra caja que los widgets tomarán espacio extra para quedar del mismo tamaño y ocupar todo el espacio libre. El último atributo se refiere a la cantidad de pixeles que internamente llenará nuestro widget.

Antes de dejar el tema del empacado y posicionamiento de widgets, cabe mencionar que existen cajas específicas para widgets en común, por ejemplo las cajas para botones(Gtk2::HButtonBox) que utilizaremos mas delante en nuestros ejemplos.

El visor de imágenes

Nuestra siguiente aplicación será un sencillo visor de imagenes donde el usuario por medio de un selector de archivos(Gtk2::FileChooser), elegirá un archivo de imagen(incluyendo íconos), y el programa desplegará la imagen a su tamaño real, incluyendo el nombre y el tamaño en KB en una etiqueta.


Codigo Fuente:


#!/usr/bin/perl
#
# app1.pl
#
# Autor: Marco Antonio Manzo <amnesiac@unixmonkeys.com>
#
# Descripción:
#   Programa2 de ejemplo - Tutorial Gtk2-Perl
#
# Licencia:
# Este programa puede ser redistribuido y/o modificado bajo
# los mismos términos de la licencia artística de Perl.
#

use strict;
use warnings;
use File::Basename 'basename';
use Glib qw( TRUE FALSE );
use Gtk2 '-init';

sub _init {
    my( $window, $menu, $vbox, $statusbar, $params, $img, $footer );

    # Utilizamos Gtk2::Image para guardar el buffer actual de la imágen
    # y Gtk2::Label para el nombre del archivo y su tamaño.
    $img = Gtk2::Image->new,
    $footer = Gtk2::Label->new( 'Tutorial Gtk2-Perl' );

    # Creamos todo nuestro entorno utilizando funciones hechas por
    # nosotros para encapsular los objetos.
    $window = create_window_space();

    # Utilizamos un hashref con los objetos que queremos ir modificando en el
    # transcurso. Principalmente a la hora de seleccionar un nuevo archivo.
    # Es decir, guardar persistencia entre dichos objetos.
    $params = {
    img => $img,
    footer => $footer,
    window => $window
   };

    $menu = create_menu_bar( $params );
    $statusbar = create_statusbar();

    # Agrupamos nuestros objetos de en una caja vertical. Entre el menú
    # y nuestra imagen a abrir ponemos una línea horizonal para separarlos.
    # Nuestro programa también cuenta por supuesto con una barra de estado!
    $vbox = Gtk2::VBox->new( FALSE, 0 );
    $vbox->pack_start( $menu, FALSE, FALSE, 3 );
    $vbox->pack_start( Gtk2::HSeparator->new, FALSE, FALSE, 3 );
    $vbox->pack_start( $img, FALSE, FALSE, 3 );
    $vbox->pack_start( $footer, FALSE, FALSE, 3 );
    $vbox->pack_end( $statusbar, FALSE, FALSE, 3 );

    # Agregamos nuestra caja vertical a la ventana, y  mostramos todo.
    $window->add( $vbox );
    $window->show_all;
    return;
}

# Encapsulamos la creación de nuestra ventana, le establecemos algunos
# atributos y el manejador del evento destroy. Regresamos el objeto
# creado.
sub create_window_space {
    my $window = Gtk2::Window->new( 'toplevel' );
    $window->set_border_width( 4 );
    $window->set_default_size( 300, 400 );
    $window->signal_connect( destroy => sub{ Gtk2->main_quit } );
    return $window;
}

# Creamos nuestra barra de menú con la ayuda de Gtk2::Menu, Gtk2::MenuItem
# y Gtk2::MenuBar.
sub create_menu_bar {
    my $args = shift;

    # Creamos algunos objetos contenedores de elementos para el menú,
    # en este caso submenús.
    my $file_menulist = Gtk2::Menu->new;
    my $help_menulist = Gtk2::Menu->new;

    # Creamos cada uno de esos elementos los cuales serán desplegados
    # al seleccionar cada uno de los menús y establecemos sus respectivos
    # manejadores para el evento de activación ('activate').
    my $open_item = Gtk2::MenuItem->new( '_Abrir' );
    $open_item->signal_connect( activate => \&open_file, $args );
    my $exit_item = Gtk2::MenuItem->new( '_Salir' );
    $exit_item->signal_connect( activate => sub{ Gtk2::main_quit } );
    my $about_item = Gtk2::MenuItem->new( '_Acerca de...' );
    $about_item->signal_connect( activate => \&about_this );

    # Agregamos los elementos creados a nuestros contenedores( submenús ).
    $file_menulist->append( $open_item );
    $file_menulist->append( $exit_item );
    $help_menulist->append( $about_item );

    # Ahora necesitamos generar aquellos nombres de elementos que contendrá
    # nuestra barra de menú principal y le agregamos nuestros submenús.
    my $file_menu = Gtk2::MenuItem->new( '_Archivo' );
    $file_menu->set_submenu( $file_menulist );
    my $help_menu = Gtk2::MenuItem->new( 'A_yuda' );
    $help_menu->set_submenu( $help_menulist );

    # Finalmente creamos nuestra barra de menú con todos los elementos
    # fabricados y retornamos dicho objeto.
    my $menubar = Gtk2::MenuBar->new;
    $menubar->append( $file_menu );
    $menubar->append( $help_menu );
    return $menubar;
}

# Esta función es llamada al recibir el evento 'activated' del menú File->Open
# el cual genera un objeto Gtk2::FileChooserDialog que nos permitirá seleccionar
# un archivo( en nuestro caso imagen ) para abrirlo.
sub open_file {
    # El primer elemento en @_ es el widget que emitió la señal, el segundo
    # los parámetros.
    my ( $widget, $args ) = @_;
    my $fc = Gtk2::FileChooserDialog->new( 'Elije una imagen...', undef, 'open',
     'gtk-ok' => 'ok',
     'gtk-cancel' => 'cancel' );
    # Al correr nuestro widget, y después de seleccionar el archivo y presionar
    # el botón de OK, se genera el response 'ok' el cual manejamos con el siguiente
    # bloque
    if( $fc->run eq 'ok' ) {
 my $file = $fc->get_filename;
 my $fileinfo = sprintf( "[ %s %.2f K ]", basename( $file ),
    ( stat( $file ) )[7] / 1024 );
 $args->{footer}->set_label( $fileinfo ); # Guardamos su nombre y tamaño
 $args->{img}->set_from_file( $file ); # Tomamos el archivo elegido
 # Le ayudamos a la ventana a crecer y principalmente decrecer de
 # acuerdo al tamaño de la imagen.
 $args->{window}->resize( $args->{img}->get_pixbuf->get_width,
     $args->{img}->get_pixbuf->get_height );
    }

    $fc->destroy;
    return TRUE;
}

# Nuestra barra de estado utilizando Gtk2::Statusbar.
sub create_statusbar {
    my $stbar = Gtk2::Statusbar->new;
    #El primer parametro de push() es el id de contexto de nuestra barra, el cual es un
    #identificador único que se obtiene llamando get_context_id del mismo objeto.
    $stbar->push( $stbar->get_context_id( 'statusbar' ), 'Tutorial Gtk2-Perl' );
    return $stbar;
}

# Y claro... nuestro diálogo de Acerca de..., no nos olvidemos de nuestros créditos.
sub about_this {
    my $about = Gtk2::Dialog->new( 'Acerca de...', undef, 'modal',
       'gtk-ok' => 'ok' );

    my $text =<<INFO;
Programa de prueba para
el Tutorial de Gtk2-Perl.

Autor: Marco Antonio Manzo

Consol 2005
INFO

    # Podemos observar como un objeto Gtk2::Dialog contiene un vbox internamente
    # para poder empaquetarle otros items.
    $about->vbox->pack_start( Gtk2::Label->new( $text ), FALSE, FALSE, 4 );
    $about->vbox->show_all;

    # Simplemente, presionamos el botón y cerramos la ventana.
    $about->destroy if $about->run;
    return TRUE;
}

_init;

Gtk2->main;

6. UNIENDO TODO

Bien, una vez dada una amplia reseña introductoria a lo que Gtk2 y Perl nos ofrecen para la creación de nuestras aplicaciónes gráficas, en este parte del tutorial haremos uso de varios( muchos ) widgets creando una pequeña aplicación, la cual consiste en un pequeño pero útil cliente de SQL. El cliente será capaz de leer un archivo de configuración escrito en YAML con la información necesaria sobre la BD a la que nos queremos conectar. Luego la ventana principal contendrá 2 frames, en uno de ellos estará en el area de texto donde colocaremos la sentencia de SQL que deseemos, y en la otra los resultados, los cuales serán visualizados como una lista.

Podemos apreciar la aplicación final a continuación:


Codigo Fuente:


#!/usr/bin/perl
#
# app2.pl
#
# Autor: Marco Antonio Manzo <amnesiac@unixmonkeys.com>
#
# Descripción:
#   Programa3 de ejemplo - Tutorial Gtk2-Perl
#
# Licencia:
# Este programa puede ser redistribuido y/o modificado bajo
# los mismos términos de la licencia artística de Perl.
#

use strict;
use warnings;

use Glib qw( TRUE FALSE );
use Gtk2 '-init';
use Gtk2::SimpleMenu;
use Gtk2::SimpleList;

# Módulos necesarios para el funcionamiento de nuestra aplicación.
use DBI;
use YAML qw( LoadFile );
use UNIVERSAL 'isa';

sub _init {
    # Variable que utilizaremos para mantener el estado
    # entre algunos objetos de nuestra aplicación.
    my $args = { }; 

    # Creamos todos los widgets principales de nuestra aplicación,
    # utilizando nuevamente funciones fabricadas por nosotros para
    # encapsular dichos objetos.
    my $window = create_window( $args );
    my $query_frame = create_query_frame( $args );
    my $results_frame = create_results_frame( $args );
    my $menu = create_menu( $args );
    my $hbbox = create_app_buttons( $args );

    # Caja vertical que contiene todos nuestros widgets creados,
    # listos para ser mostrados.
    my $vbox = Gtk2::VBox->new( FALSE, 1 );
    $vbox->pack_start( $menu->{widget}, FALSE, FALSE, 1 );
    $vbox->pack_start( Gtk2::HSeparator->new, FALSE, FALSE, 1 );
    $vbox->pack_start( $query_frame, TRUE, TRUE, 3 );
    $vbox->pack_start( $results_frame, TRUE, TRUE, 3 );
    $vbox->pack_end( $hbbox, FALSE, FALSE, 1 );

    $window->add( $vbox );

    # Un AccelGroup es la clase que nos permite tener 'teclas rápidas'
    # de acceso, en nuestro caso, para el menú creado con Gtk2::SimpleMenu.
    # Automáticamente nuestro Gtk2::SimpleMenu contiene entre sus atributos
    # el accelgroup generado usando nuestro árbol de menú. Lo utilizamos como
    # parámetro en la ventana principal.
    $window->add_accel_group( $menu->{accel_group} );
    $window->show_all;
    return;
}

# Esta función nos regresa un objeto de tipo Gtk2::HButtonBox el cual contiene
# agrupados los botones principales de nuestra aplicación.
sub create_app_buttons {
    my $args = shift;

    # A diferencia de los otros constructores, new_from_stock() te permite crear
    # un botón utilizando imágenes y nombres del stock de GTk. Una vez creado
    # cada botón conectamos su señal de 'clicked' correspondiente.
    my $exit_btn = Gtk2::Button->new_from_stock( 'gtk-quit' );
    $exit_btn->signal_connect( clicked => \&on_window_destroy, $args );
    my $clear_btn = Gtk2::Button->new_from_stock( 'gtk-refresh' );
    $clear_btn->signal_connect( clicked => \&clear_results, $args );
    my $exe_btn = Gtk2::Button->new_from_stock( 'gtk-execute' );
    $exe_btn->signal_connect( clicked => \&exec_query, $args );

    # Gtk2::Tooltips nos permite generar algunos mensajes emergentes a la hora
    # de posicionarnos sobre el widget que establezcamos como parámetro, nuestro
    # caso los botones principales.
    my $tooltips = Gtk2::Tooltips->new;
    $tooltips->set_tip( $exit_btn, 'Salir de la aplicación' );
    $tooltips->set_tip( $clear_btn, 'Limpiar area de resultados' );
    $tooltips->set_tip( $exe_btn, 'Ejecutar Query' );
    $tooltips->enable;

    # Utilizamos un Gtl2::HButtonBox para agrupar un conjunto de botones los cuales
    # queremos que se dispersen a lo largo de la área de la aplicación( 'spread' ),
    # con un espaciado de 15px entre c/u de ellos.
    my $hbbox = Gtk2::HButtonBox->new;
    $hbbox->set_layout( 'spread' );
    $hbbox->set_spacing( 15 );

    $hbbox->add( $exe_btn );
    $hbbox->add( $clear_btn );
    $hbbox->add( $exit_btn );
    return $hbbox;
}

sub create_menu {
    my $args = shift;
    
    # Nuestro menú ahora será creado utilizando la clase Gtk2::SimpleMenu, la
    # cual require como parámetro un arbol con todos los nodos, callbacks,
    # aceleradores e hijos correspondientes.
    my $menu_tree = [
    _Archivo =>
    {
     item_type => '<Branch>',
     children => [
    '_Cargar Configuración' => {
           callback => \&on_open_file,
           callback_action => 0,
           accelerator => '<ctrl>C',
           callback_data => $args
          },
    Separator => {
           item_type => '<Separator>'
          },
    '_Salir' => {
          callback => sub {
       $args->{db}->disconnect
         if exists $args->{db};
       Gtk2->main_quit
          },
          callback_action => 1,
          accelerator => '<ctrl>S',
         }
          ]
    },
    A_yuda =>
    {
     item_type => '<Branch>',
     children => [
    'Acerca de...' => {
         callback => \&on_about,
         callback_action => 2,
         accelerator => '<ctrl>H'
        }
          ]
    }
   ];

    # Generamos nuestro menú.
    my $menu = Gtk2::SimpleMenu->new( menu_tree => $menu_tree );
    return $menu;
}

sub create_window {
    my $args = shift;
    my $window = Gtk2::Window->new( 'toplevel' );
    $window->set_title( 'Gtk2-Perl: Cliente SQL' );
    $window->set_default_size( 500, 400 ); # Tamaño inicial
    $window->set_border_width( 5 ); # Tamaño del borde
    $window->signal_connect( destroy => \&on_window_destroy, $args );
    return $window;
}

# Esta función se encargará de crear un frame con el área de texto donde
# podremos escribir nuestra sentencia de SQL.
sub create_query_frame {
    my $args = shift;
    # Un Gtk2::TextView nos servirá para poder almacenar un buffer con texto
    # el cual utilizaremos para escribir nuestra sentencia de SQL.
    my $textview = Gtk2::TextView->new;
    $textview->set_size_request( 0, 100 );

    # Es necesario guardar referencia sobre este objeto para poder manipularlo
    # mas delante.
    $args->{textwidget} = $textview;

    # nuestro Frame a retornar
    my $frame = Gtk2::Frame->new( '  SQL Query  ' );
    $frame->set_border_width( 5 );
    $frame->add( $textview );
    return $frame;
}

# Esta función retorna otro frame en el cual se podrán visualizar los resultados
# de la sentencia de SQL escrita.
sub create_results_frame {
    my $args = shift;
    my $frame = Gtk2::Frame->new( '  Resultados  ' );
    $frame->set_border_width( 5 );
    $frame->set_size_request( 0, 200 );

    # Gtk2::SimpleList nos permite generar una Lista de valores con tipos
    # de datos tales como texto, pixbufs, entre otros. Para nuestro propósito
    # Solamente utilizaremos campos de texto.
    $args->{results} = Gtk2::SimpleList->new(
          name => 'text',
          value => 'text'
         );
    # Una ventana con barra de desplazamiento nos será de mucha ayuda en caso de
    # que la cantidad de filas retornadas por la base de datos sea muy grande.
    my $sc = Gtk2::ScrolledWindow->new;
    # La barra de desplazamiento aparecerá automáticamente, es decir solo cuando
    # sea necesaria.
    $sc->set_policy( 'automatic', 'automatic' ); 
    $sc->add( $args->{results} );
    $frame->add( $sc );
    # Guardamos referencia a nuestro widget para utilizarlo posteriormente
    $args->{scwin} = $sc;
    return $frame;
}

sub on_open_file {
    my $args = shift;
    my $fc = Gtk2::FileChooserDialog->new( 'Selecciona archivo de configuración', 
        undef, 'open',
        'gtk-ok' => 'ok',
        'gtk-cancel' => 'cancel'
      );
    if( $fc->run eq 'ok' ) {
 # Al seleccionar nuestro archivo de configuración, lo cargamos en una
 # estructura de datos.
 my $yaml = LoadFile( $fc->get_filename );
 my $dsn = make_dsn( $yaml ); # generamos nuestro DSN para la BD
 # Si todo sale bien, DBI->connect nos retorna una referencia a un objeto
 # de tipo DBI, caso contrario muere nuestra aplicación.
 $args->{db} = DBI->connect( $dsn, $yaml->{username}, $yaml->{password},
        { RaiseError => 1 } ) or die $DBI::errstr;
    }
    $fc->destroy;
    return TRUE;
}

sub on_about {
    my $about = Gtk2::Dialog->new( 'Acerca de...', undef, 'modal',
       'gtk-ok' => 'ok' );
    
    my $text =<<INFO;
Programa de prueba para
el Tutorial de Gtk2-Perl.

Autor: Marco Antonio Manzo

Consol 2005
INFO

    $about->vbox->pack_start( Gtk2::Label->new( $text ), FALSE, FALSE, 4 );
    $about->vbox->show_all;
    $about->destroy if $about->run;
    return TRUE;
}

# Manejador para el evento 'destroy'
sub on_window_destroy {
    my( $pwidget, $args ) = @_;
    # Desconecta nuestro manejador de la BD si este fué creado.
    $args->{db}->disconnect if exists $args->{db};
    Gtk2->main_quit;
    return;
}

# Esta función nos retorna una cadena con el DSN generado apartir del archivo
# de configuración.
sub make_dsn {
    my $yaml = shift;
    my $dsn = sprintf( "dbi:%s:dbname=%s;host=%s", $yaml->{dbdriver},
         $yaml->{dbname}, $yaml->{dbhost} );
    return $dsn;
}

# Esta función limpiará el área de resultados cuando presionemos el botón
# Actualizar.
sub clear_results {
    my( $widget, $args ) = @_;
    # Si nuestra área de resultados se trata de un Gtk2::TextView, simplemente
    # borramos el buffer actual, sino, se trata obviamente de un Gtk2::SimpleList
    # entonces borramos su área de datos.
    unless( $args->{results}->isa( 'Gtk2::SimpleList' ) ) {
 $args->{results}->get_buffer->set_text( '' );
 return TRUE;
    }
    $args->{results}->set_data_array( [] );
    return TRUE;
}

# Nos despliega un cuadro de diálogo con el mensaje de error recibido.
sub error_window {
    my $msg = shift;
    my $dialog = Gtk2::Dialog->new( 'Error', undef, 'modal',
        'gtk-ok' => 'ok' );
    $dialog->vbox->pack_start( Gtk2::Label->new( $msg ), FALSE, FALSE, 4 );
    $dialog->vbox->show_all;
    $dialog->destroy if $dialog->run;
    return TRUE;

}

# Esta función es la que, dependiendo del tipo de query, serán los resultados
# visualizados, y se manda llamar cuando presionamos el botón Ejecutar.
sub exec_query {
    my ( $widget, $args ) = @_;

    # Si el archivo de configuración no está cargado, despliega un mensaje
    # de error.
    error_window( "Faltar cargar configuración" ) and return
      unless $args->{db};

    # Gtk2::TextView contiene dentro un objeto de tipo Gtk2::TextBuffer, el cual
    # contiene  el texto que nosotros escribimos, es necesario obtenerlo para
    # poder ejecutar el query.
    my $buffer = $args->{textwidget}->get_buffer;
    my $query = $buffer->get_text( $buffer->get_start_iter, 
       $buffer->get_end_iter, TRUE );

    # Si el buffer está vacío, significa que el usuario presionó ejecutar
    # sin escribir una sentencia de SQL.
    error_window( "No hay query a ejecutar" ) and return
      unless $query;

    my $sth = $args->{db}->prepare( $query );
    $sth->execute;

    my $newwidget; # Aqui almacenamos el widget a utilizar para los resultados
    if( $query =~ /^select/i ) {
 # Si se trata de un SELECT entonces utilizaremos un Gtk2::SimpleList
 # para mostrar los resultados.
 my( @ret, $row );
 push @ret, $row while $row = $sth->fetchrow_hashref;
 # Retornamos los nombres de las columnas
 my @columns = get_columns_ready( $ret[0] );
 $newwidget = Gtk2::SimpleList->new( @columns );
 # Establecemos el área de datos de nuestra lista.
 $newwidget->set_data_array( get_column_values( \@ret ) );
    } else {
 # Si se trata de cualquier otra cosa que no sea SELECT utilizaremos
 # un Gtk2::TextView para mostrar las filas afectadas.
 my $count = $sth->rows;
 my $msg = "$count fila(3Cs) afectada(s)";
 $newwidget = Gtk2::TextView->new;
 $buffer = Gtk2::TextBuffer->new;
 $buffer->set_text( $msg );
 $newwidget->set_buffer( $buffer );
    }
    $sth->finish;

    $args->{scwin}->remove( $args->{results} );
    $args->{scwin}->add( $newwidget );
    # Guardamos nuestro nuevo widget de resultados para su futura
    # utilización.
    $args->{results} = $newwidget;
    $args->{scwin}->show_all;
    return TRUE;
}

# Esta función nos retorna el nombre de las columnas utilizadas
# por la sentencia en SQL.
sub get_columns_ready {
    my $row = shift;
    my @ret;
    push( @ret, $_, 'text' ) for keys %{ $row };
    return @ret;
}

# Esta función nos retorna una referencia a un array con los valores
# retornados por la sentencia de SQL, el cual utilizaremos con nuestro
# Gtk2::SimpleList
sub get_column_values {
    my $rows = shift;
    my $ret;
    @{$ret} = map{ [ values %{ $_ } ] } @$rows;
    return $ret;
}

_init;

Gtk2->main;

7. AHORA.... ¿QUÉ? DOCUMENTACIÓN

Este apartado lo dedicaremos a mostrar algunas URL's sobre información clave para el aprendizaje de Gtk2 en Perl.

Página principal de Gtk

Aquí encontrarás toda la información sobre dicha herramienta, desde tutoriales introductorios, la referencia completa de la API, código fuente, noticias, parches, etc.
http://www.gtk.org


Proyecto Gtk2-Perl

Esta es el sitio principal del proyecto de los bindings de Gtk2 en Perl, al igual que la anterior encontrarás tutoriales, ejemplos, listas de correo, código fuente, etc.
http://gtk2-perl.sourceforge.net


Gtk2-Perl CVS

La parte interesante de este sitio es la cantidad de ejemplos que puedes consultar sobre Gtk2-Perl y Gnome2-Perl inclusive.
http://cvs.sourceforge.net/viewcvs.py/gtk2-perl/


Gtk-Perl Tutorial

La versión inicial del tutorial de Perl, puesto que en Gtk2 varias cosas han cambiado, este tutorial puede ser de una gran ayuda para los widgets que funcionan igual aún en Gtk2, incluyendo ejemplos.
http://personal.riverusers.com/~swilhelm/gtkperl-tutorial/


perldoc Gtk2

Y claro... no podía faltar nuestra página de manual de Gtk2 desde perldoc.

0 comentarios: sobre Programación de Perl y Gtk

Publicar un comentario para Programación de Perl y Gtk

:a   :b   :c   :d   :e   :f   :g   :h   :i   :j   :k   :l   :m   :n   :o   :p   :q   :r   :s   :t

Calculando Tiempo
Alienspace Theme © Copyright 2017 By Proxor
Mi Ping en TotalPing.com FeedBurner FeedBurner FeedBurner FeedBurner FeedBurner