Charlas de GNOME Hispano: GTK+ básico

Relator: Germán Poo-Caamaño (gpoo)
Moderador: (setepo)
Domingo 19 de enero de 2003, 21:30 horas UTC
irc.gnome.cl #gnome-hispano

setepo
Buenas noches a todos.
Hoy, tal y como dice el topic de este canal, gpoo nos dará
una charla de GTK+ a nivel básico
El log de esta charla será colgado 
en la sección de eventos en la web de gnome-hispano, en http://es.gnome.org/eventos/index.php

las preguntas hacedmela a mí
y yo os daré voz para que se las paséis a gpoo
gpoo, cuando quieras :-)
gpoo
ok
gracias setepo
la charla de hoy:
GTK+ basico
donde veremos el concepto de widget, el modo de ejecución, las señales

los contenedores y un ejemplo "hola mundo"
todo para no asustarlos y puedan madurar de a poco la programación gráfica
 con GTK+
como siempre, nuestra referencia será el Libro GNOME
http://libros.es.gnome.org/librognome/librognome/librognome/book1.html

Introducción: un poco de historia
Cuando se desea programar en un ambiente gráfico, como es X
se debe dibujar absolutamente todo, es decir, si queremos un botón
debemos dibujar varios rectángulos, cada uno con distintos colores
lo que finalmente se traducirá en un botón tal cual lo vemos.
Sería el equivalente de programar aplicaciones de consola usando Assembler.

Esa biblioteca es XLib.
Sin embargo, es engorroso para construir aplicaciones que tengan
una apareciencia coherente y atenta contra la productividad del programador.

Es así como nacen los Toolkits, que permiten tomar bloques de pantalla

estándar llamados widgets.  Existen varios Toolkits, como Xt Intrinsics

(el más básico), Motif, Open Look, QT, GTK+.
De está forma, cuando se programa con un Toolkit, simplemente se indica:

añada un botón, una ventana, etc. y nos olvidamos del resto.
Es decir, nos ofrece una capa de abstracción respecto al sistema gráfico.

GTK+ significa GIMP Tool Kit, y es una biblioteca que agrupa
un conjunto de widgets (botones, barra de herramientas, menú,
etc.) y que permite crear nuevos widgets.
En su inicio, fue creado para desarrollar GIMP.  Es el toolkit que
se dicidió adoptar para el desarrollo de GNOME porque es un ToolKit libre.

Y eso también explica la relación tan estrecha entre GIMP y GNOME.
 
Los widgets
Anteriormente indiqué a un widget como un bloque de pantalla
estándar. En realidad es más que eso.
Porque un widget, además, posee propiedades y podemos programarlo 
para que reaccione frente a cierto tipo de señales que ocurran 
durante la ejecución de nuestro programa.
A través de la combinación de widgets, podemos construir
las interfaces gráficas de nuestras aplicaciones.
En GTK+ es posible derivar de cada widget para crear uno nuevo.
La clase básica es GtkObject, la cual deriva de GObject, 
como lo habrán visto con rodrigo la semana pasada.
Sin embargo, en el día a día, veremos a GtkWidget como la clase básica,

que es derivada de GtkObject y de la cual derivan todos los widgets de
 GTK+.
Debemos tener muy claro que:
La creación de cualquier widget, siempre retornará un widget.
Así, si creamos un botón, la función de creación del botón retornará un
 widget.
 
Control de la ejecución de un programa
Nuestros programas son escritos en forma secuencial y ordenada.
Sin embargo, en un ambiente gráfico, la interacción no
es secuencial.
Es así que, podemos tener una ventana abierta, y cuando seleccionemos
opciones distinas de un menú queremos que se lleven a cabo distinto
tipo de acciones.
Para eso, GTK+ nos provee de dos funciones para controlar
nuestro programa.
gtk_init () la cual debe ser llamada *antes* que cualquier
otra función de GTK+, ya que es la encargada de inicializar
todo lo necesario para trabajar con el ToolKit.
El prototipo de esta función es:
void gtk_init (int *argc, char ***argv);
Donde espera que le pasemos por referencia los valores que
recibimos al momento que se ejecute nuestro programa.
Más adelante lo veremos con un ejemplo.
La otra función es gtk_main () y es una de la últimas
funciones de GTK+ que usaremos en nuestro programa principal.
Está función ejecuta un ciclo infinito y es la que se encarga
de atender todo lo que ocurra en nuestra aplicación 
es decir, es la que encargará de de esperar
la ocurrencia de eventos para posteriormente procesarlos, para lo cual
 emitirá una señal.
Por ejemplo, cuando se presione un botón.
 
Señales
Una señal es una forma de mantener una lista de funciones
que serán llamadas cuando se "emita" una señal.
Esas funciones son llamadas callbacks.
De esta forma, cuando ocurre un evento, gtk_main emite una señal
y es señal lo que nos interesa programar para que nuestro programa tome
 vida.
Alguna duda (antes de ver el primer ejemplo)?
setepo
gpoo, parece que no
gpoo
seguimos entonces
Un pequeño ejemplo
El siguiente ejemplo lo pueden escribir en cualquier editor
aunque les recomiendo usar Anjuta.
#include <gtk/gtk.h>
int main (int argc, char **argv)
{
    GtkWidget *window;
    
    gtk_init (&argc, &argv);
    
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_widget_show (window);
    
    gtk_main ();
    
    return 0;
}
Este ejemplo, lo único que hace es abrir una ventana.
Nuestra primera ventana :-)
Guardamos nuestro ejemplo, como ejemplo1.c y lo compilamos
en un terminal de la siguiente forma:
$ gcc -Wall `pkg-config --cflags --libs gtk+-2.0` -o ejemplo1 ejemplo1.c

Es necesario que tengan instalados los paquetes de desarrollo de GTK+-2.0,
 en debian el paquete es libgtk2.0-dev 
setepo
gpoo, tenemos una pregunta
--- setepo fija modos [#gnome-hispano +v pollo]
gpoo
adelante
setepo
pollo, adelante
pollo
ok hola gpoo
gpoo
hola pollo, cual es la pregunta?
pollo
se compila igual en GTK 1 que en GTK2 ?
gpoo
No
la compilación es distinta
en GTK2 se utiliza pkg-config, mientras que en GTK1 se usa gtk-config
pollo
ok y supongo que el tema de gtk+-2.0 lo cambiaremos por gtk+-1.4 o la que
 sea no ? 
gpoo
no entendi la pregunta
si tienes pkg-config y las definiciones apropiadas, tambien te sirve para
 compilar GTK1
setepo
pollo, para gtk 1.x puedes usar gcc -Wall `gtk-config --cflags --libs`
 -o ejemplo
gpoo
en vez de indicar gtk+-2.0 se coloca gtk+
pollo
ok esta claro solo eso  gracias :D
--- setepo fija modos [#gnome-hispano -v pollo]
gpoo
ahora entiendo, si, asi es.
El primer ejemplo funcionará tanto en GTK 1.4 como en GTK-2.x
setepo
seguimos :-)
gpoo
Pero cuando comencemos a añadir señales, tendremos las primeras incompatibilidades

haré la mención para los interesados.
Como decía, el ejemplo lo único que hace es mostrar una ventana.
Sobre la línea de compilación:
Nos valemos de pkg-config para añada toda la información que el compilador
 necesita para usar la bilioteca GTK+ 2.x.
La opción -Wall no es obligatoria, pero *siempre* es recomendable,
ya que le indica al compilador que nos muestre cualquier
posible equivocación por más mínimo que sea
(variables no inicializadas, mala asignación de punteros, variables sin
 uso).
Analicemos el programa línea a línea.  Lo primero que hemos indicado
es donde se encuentran las definiciones de las funciones que emplearemos
 (GTK+).
Posteriormente definimos la variable window, que es un puntero
a un GtkWidget y que corresponderá a la ventana de nuestra aplicación.

Como ya lo había indicado antes, la primera función a llamar será
gtk_init () para que inicialice todo lo necesario para ejecutar
nuestra aplicación con el Toolkit.
Vemos que le pasamos por referencia los argumentos del programa principal:
 argc y argv.
Creamos una ventana, con gtk_window_new, indicándole como parámetro
que es la ventana principal.  Todas las funciones gtk_*_new 
retornan un puntero de GtkWidget, que en este caso lo hemos dejado en window.

Sin embargo, crear un widget no significa que se mostrará en pantalla
simplemente se crea el objeto.  Para mostrarlo en pantalla
usamos la función gtk_widet_show (window).
Finalmente, llamamos gtk_main ().
Debemos notar algunos detalles
para quien lo haya compilado y ejecutado se dará cuenta de lo siguiente:

Al presionar el botón de cerrado de la ventana, ésta desaparecerá
pero el programa continúa en ejecución
esto ocurre porque el programa aún se encuentra en el ciclo infinito gtk_main
 ()
si bien, el ciclo nos sirve de ayuda para que nuestra aplicación siga en
 ejecución
queremos que termine cuando lo deseamos
pues bien, para ello debemos emplear la función gtk_main_quit ()
que le indica al ciclo infinito que debe terminar
entonces lo que debemos hacer es que nuestro programa responda a la señal
 de cierre de la ventana.
luego
lo que debemos hacer es juntar lo que hemos aprendido hoy
Los widgets con las señales
por el momento sólo hemos creado un widget, gtk_main está esperando a que
 ocurran
pero no le hemos indicado acción alguna a ejecutar cuando estos ocurran.

Es tiempo de nuestro ejemplo2
el cual es una breve extensión del ejemplo1
veamos el código:
#include <gtk/gtk.h>
gint delete_event( GtkWidget *widget,
                   GdkEvent  *event,
     gpointer   data )
{
     return FALSE;
}
void destroy( GtkWidget *widget,
              gpointer   data )
{
    gtk_main_quit ();
}
int
main (int argc, char **argv)
{
 GtkWidget *window;
 gtk_init (&argc, &argv);
 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 
 g_signal_connect (G_OBJECT (window), "delete_event",
        G_CALLBACK (delete_event), NULL);
    
    g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy), NULL);
 
 gtk_widget_show (window);
 gtk_main ();
 return 0;
}
por el pegado no quedo con la mejor indentación, pero se entiende.  Revisemos...

Hemos añadidos 2 funciones para nuestras señales (callbacks)
delete_event y destroy
las explicaré después del programa principal
he añadido g_signal_connect, que es una función de glib
(en GTK 1.4 esa función se llama gtk_signal_connect)
Lo que estamos haciendo, es asociaciar al objeto «window» la señal sea
 «delete_event»
anteriormente quise decir GTK 1.2, GTK 1.4 no existe :-)
la señal delete_event se emite cuando se presiona el botón "X" en el marco
 de la ventana
si vemos, esta función lo único que hace es retorna FALSE
nos sirve para indicar si se debe emitir la señal destroy
esta separación es útil si queremos añadir un diálogo en nuestra aplicación
 que pregunta al usuario
si está seguro de salir
porsteriormente, indicamos que atienda a la señal destroy
que es donde finalmente hemos puesto gtk_main_quit, y que hará que la aplicación
 termine
en la eventualidad que quisieramos pasar algún parámetro a alguna de las
 señales, podemos hacerlo con el último
parámetro de g_signal_connect, que en esta ocasión ha sido NULL.
vamos bien hasta aquí? alguna consulta?
continuamos
nos falta algo de emoción en nuestro programa, entonces añadiremos un botón

cuyo texto sea "Hola mundo" y que al momento de presionarlo, nos imprima
 en terminal
el mensaje "Hola mundo".
Pero antes de poder hacerlo, es necesario hablar de:
 
Los contenedores
 
Quienes hayan diseñado interfaz gráficas en Windows notarán una diferencia
 a la manera de hacerlo en GTK+
salvo que hayan diseñado en Java, en donde también se emplean los contenedores

en un principio resulta un poco más complejo, pero los beneficios son mayores

Los contenedores son widgets especiales, que alojan a otros widgets
y la idea es que permitar que nuestras interfaces se ajusten a cualquier
 tamaño
o tipografía
es así entonces, que un contenedor ocupará todo el espacio disponible que
 encuentre
y si maximizamo la ventana, el conetenedor con su widget adentro también
 cambiará de tamaño
debemos entender que un contendor aloja a 1 widget, si queremos que aloje
 más, debemos trabajar con 
contenedores especiales, que se verá en la próxima charla.
Un botón, GtkButton, tambien es un contenedor, el cual aloja una etiqueta
 GtkLabel
es útil que sea un contenedor, porque también podría alojar una imagen

o un contenedor especial para alojar adentro una imageny una etiqueta,
 o lo que queramos
vamos al ejemplo:
int main( int   argc,
          char *argv[] )
{
    GtkWidget *window;
    GtkWidget *button;
    
     gtk_init (&argc, &argv);
    
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    g_signal_connect (G_OBJECT (window), "delete_event",
        G_CALLBACK (delete_event), NULL);
    
    g_signal_connect (G_OBJECT (window), "destroy",
        G_CALLBACK (destroy), NULL);
    
    button = gtk_button_new_with_label ("Hola mundo");
    
    g_signal_connect (G_OBJECT (button), "clicked",
        G_CALLBACK (hello), NULL);
    
    gtk_container_add (GTK_CONTAINER (window), button);
    
    gtk_widget_show (button);
    
    gtk_widget_show (window);
   
    gtk_main ();
    
    return 0;
}
He añadi la definición:
GtkWidget *button;
     button = gtk_button_new_with_label ("Hola mundo");
como dije antes, todas funciones gtk_*_new retornan un widget, en este
 el botón 
y le hemos indicado:
que cuando ocurra la señal "clicked" ejecuta la función "hello"
la cual aun no hemos definido
pero si analizamos fríamente
hasta ese punto hemos creado la ventana, hemos creado el botón, pero como
 le decimos que coloque el botón dentro de la ventana?
Pues bien, la ventana también es un contenedor y como tal usamos:
    gtk_container_add (GTK_CONTAINER (window), button);
es decir, al contenedor ventana le añadimos el widget button.
Luego mostramos el botón y finalmente mostramos la ventana:
    gtk_widget_show (button);
    gtk_widget_show (window);
otra opción pudo ser:
    gtk_widget_show_all (window);
que muestra al widget "window" y todos los widgets dentro de window.
luego creamos la función "hello" (la colocamos al inicio)
void hello( GtkWidget *widget,
            gpointer   data )
{
    g_print ("Hola mundo\n");
}
De más está decir, que las funciones delete_event y destroy que creamos
 hace un rato, deben permanecer
porque las usaremos, no las volví a escribir porque eran las mismas.
En general, la tónica de creación de widgets y asociación de señales es
 siempre la misma
y es muy sencilla
el problema que ocurre es: Cómo saber que funciones existen?
Oskuro: cómo saber que parámetros recibe un señal?
opps, xchat me completo o: por Oskuro :-)
para eso debemos tener la documentacion del API de programación de GTK+

la cual iremos descubriendo las próximas semanas
gracias a la aplicación «devhelp» podemos navegar fácilmente a través de
 ellas
y deben notar que:
si la función dice "gtk_widget_algo" el primer parámetro *siempre* será
 un Widget.
Si la función dice:
gtk_button_set_label, entonces el primer parámetro será un GtkButton
entonces resulta fácil asociar el nombre de la función (su primeras palabras)
 con el widget que será el primer
parámetro.
lo mismo sucedió con gtk_container_add
    gtk_container_add (GTK_CONTAINER (window), button);
el primer parámetro es un CONTAINER.
como sabemos que window es una venana (lo creamos con gtk_window) y que
 una ventana deriva de un
contenedor, entonces le decimos al compilador: pase este widget ventana
 como un contenedor, porque sabemos que tambien lo es.
preguntas
setepo
parece que no hay ninguna
¿alguien quiere formular alguna pregunta? ¿hay alguien despierto? :-)
gpoo, hay una
--- setepo fija modos [#gnome-hispano +v hardy]
gpoo
yo creo que no queda mas que invitar a los pacientes asistentes a asistir

 a la próxima charla
setepo
hardy, haz tu pregunta
hardy
Gracias.
gpoo
el próximo domingo sobre Gtk Intermedio
oh... hola hardy adelante.
hardy
Germán, si una ventana es un contenedor y podemos crear dentro otros contenedores,
 entonces es posible tener contendores anidados ???
gpoo
si
existen 3 contenedores especiales
hardy
Un  gtk_widget_show_all (window); "llama" a todos los contenedores anidados
  ???
gpoo
en efecto
así, si una ventana tiene muchos widgets
no tenemos que llamarlos uno por uno
quiero decir, no tenemos que mostrarlos uno por uno
lo cual es un gran ahorro :-) sobre todo si hacemos modificaciones
no obstante, y a pesar que hemos creados las ventanas y widgets de esta
 forma
hardy
Si cierto y muy bune caracteística. Y respecto a los "eventos", un contenedor
 deja pasar los eventos transparentemente a otro contenedor ?
gpoo
por supuesto
los eventos lo asociamos a los widgets
así, si tenemos un GtkLabel dentro de un contenedor, dentro de otro contenedor

dentro de una ventana, y asociamos el evento al GtkLabel, entonces la señal
 la recibe el GtkLabel
con la excepción que escogí el peor widget para el ejemplo :-/
reemplaza GtkLabel por GtkButton :-)
hardy
ah ! ok :-)
gpoo
ahora bien, después que pasemos las 3 charlas de GTK+
hardy
gracias.
setepo
hardy, ¿alguna pregunta más?
gpoo
veremos una de libglade, que nos permitirá crear y mantener nuestras interfaces
 de una manera más fácil
pero eso no será útil, sino sabemos lo más básico que es esto.
--- setepo fija modos [#gnome-hispano -v hardy]
hardy
si una más, pero no se si deba preguntar, llegué un poco tarde a la charla

 :-/
--- setepo fija modos [#gnome-hispano +v hardy]
setepo
ups..
hardy
cuál es la línea de comando para compilar en programa en GTK ?
setepo
hardy, no te preocupes, tú pregunta
gpoo
la que he indicado anteriormente y la vuelvo a indicar:
gcc -Wall `pkg-config --cflags --libs gtk+-2.0` -o ejemplo ejemplo.c
hardy
muchas gracias :-D !
gpoo
si quieres saber mas opciones de pkg-config, simplemente prueba pkg-config
 --help y/o pkg-config --list-all
--- setepo fija modos [#gnome-hispano -v hardy]
--- setepo fija modos [#gnome-hispano +v PeSa]
hardy
No mas preguntas.
setepo
PeSa, pregunta 
PeSa
una pregunta... es posible que desde otro programa se genere una señal
 y que pueda interpretar un widget... ejemplo (un reconocedor de voz)
o un teclado en pantalla?
gpoo
eso lo desconozco
si te refieres a que ejeucto programa1 y programa2
y quieres que programa2 emita una señal que interprete un widget de programa1

eso es lo que quieres?
PeSa
si
setepo
PeSa, no podrás con las señales de GObject. Tendrás que usar algún mecanismo
 de IPC, como sockets
PeSa
ok gracias
gpoo
bueno, con g_io_channel se pueden conectar programas
setepo
PeSa, ¿alguna más?
gpoo
eso lo vimos en una charla de glib que di
PeSa
no gracias
--- gpoo fija modos [#gnome-hispano +o rodrigo]
gpoo
veamos, rodrigo tiene una solucion para ello
setepo
PeSa, ferulo comenta que eso que dices también es posible con bonobo 
rodrigo
puedes comunicar widgets entre dos procesos con GtkPlug/GtkSocket
eso es lo que usa bonobo
gpoo
claro que eso será en otra charla :-)
rodrigo
:-)
setepo
¿alguna otra pregunta? ¿algún otro comentario sobre esto?
gpoo
bueno, de apoco los vamos amarrando a las siguientes charlas.
setepo
parece que no
gpoo, ¿damos por terminada la charla?
gpoo
setepo: si, e invitarlos para la próxima semana
--- setepo fija modos [#gnome-hispano -m]