GTK+ Programador Objeto Uma introdução à poderosa biblioteca de objetos gráficos, a mesma usada para construir o Gnome
Rodrigo Parra Novo
rodarvus@conectiva.com.br
Como desenvolver um programa com interface gráfica no Linux ?
Esta é normalmente a primeira pergunta feita por um programador que esteja
migrando, ou pensando em migrar seus programas para o Linux. São várias as
opções. Pode-se utilizar linguagens como C, C++, Java, Perl, Python ou mesmo
assembly para os mais radicais. Adicionalmente, é necessario escolher um
Widget Set, ou seja, uma biblioteca de ferramentas que permita que o
programador especifique coisas como `crie uma janela de tamanho X, e que
contenha um botão, contendo o texto Ok' para a linguagem escolhida. Alguns
exemplos de Widget Sets são o GTK+, Qt, Tk, fltk e outros. A nossa escolha
será a dupla C/GTK+, pela simplicidade e eficiência de ambos.
Podemos definir o GTK+ como um conjunto de widgets (semelhantes aos
componentes do Delphi), como botões, caixas de entrada de texto, radio
buttons, listboxes, etc., além de várias funções que tornam mais fácil o
desenvolvimento do programa. O GTK+ foi desenvolvido na linguagem C e para
uso com esta, mas existem wrappers/extensões para várias outras linguagens,
como C++, Objective C, Python, Perl, Guile, Pascal, e outras, o que torna
esse Widget Set muito flexível para a escolha da linguagem a ser utilizada.
Assume-se nesse artigo que o leitor tenha um conhecimento básico da
linguagem C, além dos procedimentos de compilação de um programa. Caso você
queira se aprofundar em C antes de prosseguir, recomendamos o clássico C - A
linguagem de programação, por Brian W. Kernighan & Dennis M. Ritchie.
Apesar de ter sido desenvolvido em C, o GTK+ tem o paradigma de orientação a
objetos como uma característica básica. O que quer dizer que todos os
widgets são contidos em objetos (as variáveis da linguagem C) e são
acessíveis através dos seus métodos (as funções em C).
O GTK+ pode ser definido em três partes distintas:
- GLIB - Uma biblioteca de funções de utilidade geral, que pode ser usada
independentemente do GTK+. Inclui coisas como um alocador de memória com
suporte estendido à depuração, funções para uso de listas ligadas, funções
para aumentar a portabilidade do programa, etc.
- GDK - Um conjunto de funções gráficas, úteis para que o programador não
precise lidar direto com a Xlib (Biblioteca gráfica do XFree86).
- GTK - Contém os Widgets que podem ser utilizados pelo programador, além de
toda estrutura para a criação de widgets novos.
Todo e qualquer componente gráfico do GTK+ é definido por um tipo de
variável especial, o GtkWidget, que contém informações como nome, cor,
posição na tela, etc. Adicionalmente, a nomenclatura das funções do GTK+ é
constante, na seguinte forma:
<biblioteca>_<widget-ou-tipo>_<ação>(parâmetros_a_serem_passados),
como em:
janela = gtk_window_new (GTK_WINDOW_TOPLEVEL); |
Neste caso, a biblioteca é a GTK, o widget que trabalharemos é um window
(uma janela normal) e a ação é a de criar esta janela. Da mesma forma,
existe uma função:
gtk_widget_show (janela); |
que mostra na tela a janela criada no exemplo anterior. Esta variável
janela, que usamos nestes dois exemplos, é do tipo GtkWidget, que é o tipo
usado por todos os widgets GTK. A sua declaração é feita da seguinte forma:
Além do tipo GtkWidget, vários outros tipos de variáveis são oferecidos pelo
GTK+ (Neste caso, pela GLIB especificamente). Alguns deles são: gint, guint,
gchar, além de tipos mais avançados como guint64, que garante um inteiro sem
sinal, de 64 bits. É recomendado o uso destes tipos oferecidos pela GLIB,
pois eles aumentam em muito a portabilidade de qualquer programa que os
utilize.
Vamos aproveitar o conhecimento que recebemos agora, para fazer nosso
primeiro programa em GTK+. Como todos os primeiros exemplos do mundo, um
programa que mostra a string "Hello, World!" na tela. Só que, no nosso caso,
dentro de uma janela criada por nós.
***** Começo do programa hello.c *****
#include <gtk/gtk.h>
int main (int argc, char **argv)
{
GtkWidget *janela;
GtkWidget *label;
gtk_init (&argc, &argv);
janela = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_border_width (GTK_CONTAINER (janela), 10);
label = gtk_label_new ("Hello, World!");
gtk_container_add (GTK_CONTAINER (janela), label);
gtk_widget_show (label);
gtk_widget_show (janela);
gtk_main ();
return (0);
}
***** Fim do programa hello.c *****
|
|
Você pode compilar o programa acima com o gcc, usando:
# gcc -o hello hello.c `gtk-config --cflags --libs` |
O significado dos parâmetros incomuns será explicado depois.
Todos os programas GTK+ incluem gtk/gtk.h, que declara variáveis, funções,
estruturas e coisas desse tipo que serão usadas em seu programa. A próxima
linha:
chama a função gtk_init (gint *argc, gchar ***argv), que é utilizada em
todos os programas GTK+. Esta função configura coisas que todo programa para
o XFree86 deve ter, como o visual padrão, o colormap, etc. Posteriormente
ela remove quaisquer parâmetros internos do GTK+, que o usuário tenha
passado ao chamar o programa.
janela = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
A próxima linha cria uma janela. O parâmetro GTK_WINDOW_TOPLEVEL diz para o
Gerenciador de Janelas controlar quais cores, bordas, etc., serão usadas na
janela. Uma janela que não tenha nenhum filho (o nosso caso, até o momento)
tem um tamanho padrão de 200x200 pixels, para que possamos trabalhar com ela
assim mesmo.
gtk_container_border_width (GTK_CONTAINER (janela), 10); |
Aqui nós dizemos para o GTK+ que qualquer widget que venha a ser inserido na
nossa janela ficará a uma distância mínima de 10 pixels da borda da janela.
label = gtk_label_new ("Hello, World!"); |
Criamos um label com o conteúdo "Hello, World"
gtk_container_add (GTK_CONTAINER (janela), label); |
e inserimos este botão na nossa janela.
gtk_widget_show (label);
gtk_widget_show (janela);
|
Após a criação e a configuração de todos os Widgets que serão usados,
devemos mostrá-los na tela, com a função gtk_widget_show. Recomenda-se que a
janela principal seja o último widget da lista, para que se evite a sensação
de "flickering", ou seja, os widgets serem desenhados um a um, depois da
janela principal. (Isto é facilmente detectável em computadores lentos
e com pouca memória.)
Aqui passamos o controle para o GTK+.
A função gtk_main fica parada esperando por eventos que tenhamos configurado
antes, timeouts, notificações de E/S em arquivos, etc. No nosso pequeno
exemplo todo e qualquer evento é ignorado. Por este motivo, quando você
fechar a janela o controle não irá voltar para o shell. (Presssione CTRL+C
para sair do programa.)
Vamos agora para um exemplo um pouco mais avançado.
Neste exemplo, iremos criar um programa com um botão simples, que quando
clicado fecha o programa. O exemplo parece básico, mas mostra uma das
características mais importantes do GTK+, o uso dos chamados callbacks,
que são funções que executam uma determinada ação quando acionadas.
Para economizar espaço, primeiro mostraremos o programa todo, mas
comentaremos apenas as partes que mudarem em relação ao exemplo anterior.
***** Começo do programa callback.c *****
#include <gtk/gtk.h>
void hello (GtkWidget *widget, gpointer data)
{
g_print ("Hello, World!\n");
}
gint delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete_event detectado\n");
return (TRUE);
}
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit();
}
int main (int argc, char **argv)
{
GtkWidget *janela;
GtkWidget *botao;
gtk_init (&argc, &argv);
janela = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_signal_connect (GTK_OBJECT (janela), "delete_event",
GTK_SIGNAL_FUNC(delete_event),
NULL);
gtk_signal_connect (GTK_OBJECT (janela), "destroy",
GTK_SIGNAL_FUNC(destroy),
NULL);
botao = gtk_button_new_with_label ("Clique aqui para sair");
gtk_signal_connect (GTK_OBJECT (botao), "clicked",
GTK_SIGNAL_FUNC (hello),
NULL);
gtk_signal_connect_object (GTK_OBJECT (botao), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
GTK_OBJECT (janela));
gtk_container_add (GTK_CONTAINER (janela), botao);
gtk_widget_show (botao);
gtk_widget_show (janela);
gtk_main ();
return (0);
}
***** Fim do programa callback.c *****
|
Lembramos que somente as partes novas para nós serão comentadas.
void hello (GtkWidget *widget, gpointer data)
{
g_print ("Hello, World!\n");
}
|
Esta função é um callback do GTK+. Callbacks são caracterizados por retornar
sempre void (ou simplesmente não retornar nada) e ter dois parâmetros. O
primeiro é a widget à qual o callback está relacionado, e o segundo é um
gpointer (basicamente um ponteiro normal), que, usando-se um cast do C, pode
ser qualquer tipo de dado. No nosso exemplo, ignoramos os dois parâmetros
que são passados, e simplesmente mostramos uma mensagem no callback, para
mostrar o seu funcionamento.
gint delete_event (GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("delete_event detectado\n");
return (TRUE);
}
|
A função delete_event é um dos eventos do GTK+ (Lembre-se, o GTK+ é um
Widget Set orientado a objetos, apesar de ter sido desenvolvido em C.)
Abaixo segue a lista dos eventos existentes:
- event
- button_press_event
- button_release_event
- motion_notify_event
- delete_event
- destroy_event
- expose_event
- key_press_event
- key_release_event
- enter_notify_event
- leave_notify_event
- configure_event
- focus_in_event
- focus_out_event
- map_event
- unmap_event
- property_notify_event
- selection_clear_event
- selection_request_event
- selection_notify_event
- proximity_in_event
- proximity_out_event
- drag_begin_event
- drag_request_event
- drag_end_event
- drop_enter_event
- drop_leave_event
- drop_data_available_event
- other_event
E, por último, repare que este evento retorna TRUE. Quanto este evento
retorna TRUE, o GTK+ ignora a tentativa de sair do programa. Caso você
quisesse sair do programa na ocorrência desse envento, seria necessária
apenas a substituição do TRUE por um FALSE. Isso é útil quando o programador
quer colocar um diálogo perguntando por exemplo `Você realmente deseja sair
deste programa ?'
void destroy (GtkWidget *widget, gpointer data)
{
gtk_main_quit();
}
|
O Callback "destroy" criado por nós é ativado quando a janela principal do
programa é fechada (quando clica no botão). O que ela está fazendo neste
caso é simplesmente chamar a função gtk_main_quit(), que finaliza o GTK+.
Esta função deve ser chamada em todos programas que utilizem o GTK+, ao
finalizar.
gtk_signal_connect (GTK_OBJECT (janela), "delete_event",
GTK_SIGNAL_FUNC (delete_event),
NULL);
|
Aqui, apresentamos um conceito novo. A função
gtk_signal_connect conecta um evento, neste caso o evento
`delete_event' à função delete_event, que definimos acima. O ato
de conectar um evento a um callback é uma das características
básicas do GTK+, pois permite que associemos algo como um
clique num botão a uma ação qualquer que definimos no nosso callback.
Note que nessa função são usadas as construções GTK_OBJECT (janela)
e GTK_SIGNAL_FUNC (delete_event). Elas são macros do GTK+, que
fazem um cast para o tipo desejado, e, no caso da GTK_SIGNAL_FUNC,
checa-se se a função delete_event tem realmente dois parâmetros,
e se eles são do tipo GtkWidget e gpointer, respectivamente. O
último parâmetro da função gtk_signal_connect, que no nosso caso é NULL, é
exatamente o parâmetro `gpointer data', que é passado para o nosso callback.
Um exemplo de uso desse parâmetro seria passar um ponteiro para uma cadeia
de caracteres (string), que seria usada no nosso callback.
gtk_signal_connect (GTK_OBJECT (janela), "destroy",
GTK_SIGNAL_FUNC (destroy),
NULL);
|
Aqui temos mais um evento (destroy), conectado ao nosso objeto (a janela).
Conectamos esse evento aqui, para demonstrar que podemos conectar qualquer
quantidade de eventos a um objeto. O resultado final, no nosso exemplo, é
que quando se tenta fechar a janela, clicando no ícone no canto superior
direito (ou esquerdo algumas vezes) da janela, é mostrada a mensagem
`delete_event detectado', e a tentativa de sair é ignorada. Já no segundo
evento conectado ao nosso objeto, nós simplesmente informamos ao GTK+ que,
quando o o evento destroy for acionado (ou seja, a janela foi fechada,
clicando-se o botão `sair'), se deve chamar a função gtk_main_quit(), que
finaliza o GTK+.
botao = gtk_button_new_with_label ("Clique aqui para sair"); |
Aqui criamos um botão com o texto `Clique aqui para sair'
gtk_signal_connect (GTK_OBJECT (botao), "clicked",
GTK_SIGNAL_FUNC (hello),
NULL);
|
Conectamos o evento `clicked' ao callback `hello'. (Novamente: informamos ao
GTK+ que quando o botão for clicado, deve-se chamar a função hello, com os
parâmetros de sempre)
gtk_signal_connect_object (GTK_OBJECT (botao), "clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),
GTK_OBJECT (janela));
|
A função gtk_signal_connect_object é útil, por exemplo, neste caso em que
conectamos o evento `clicked' do botão à execução do evento
`gtk_widget_destroy' da janela, que simplesmente fecha esta janela.
gtk_container_add (GTK_CONTAINER (janela), botao); |
Inserimos o botão dentro da janela:
gtk_widget_show (botao);
gtk_widget_show (janela);
|
Mostramos os dois, lembrando que se deve mostrar a janela principal sempre
por último:
e, pronto, temos o nosso programa funcionando.
Há muito mais sobre GTK+. Caso tenha se interessado, recomendamos a leitura
de um dos bons livros sobre GTK+ disponíveis na Internet (Além de terem sido
incluídos no CD que acompanha a Revista do Linux - para a sua comodidade).
Na próxima edição, continuaremos com este tutorial em
GTK+, quando passaremos à teoria das `boxes' (O modo que o
GTK+ utiliza para inserir vários objetos dentro de um widget qualquer), além
de outras técnicas avançadas.
No CD da Revista nº2 há farta documentação, em inglês, sobre GTK+ incluindo
os itens:
- Guia de referência da GLIB (glib_reference_guide.tar.gz)
- Guia de referência do GTK (gtk_reference_guide.tar.gz)
- Um tutorial sobre GTK+ (gtk_tutorial.tar.gz)
- O livro GTK+/Gnome Application Development (gtk_gnome_application_development.tar.gz)
|