GTK+/Começando
Capítulo 3: Começando
A primeira coisa a fazer, é claro, é descarregar o código-fonte do GTK e instalá-lo. Você sempre poderá obter a última versão de ftp://ftp.gtk.org. Você também pode ver outras fontes de informação sobre o GTK em http://www.gtk.org/ (em inglês). O GTK usa o GNU autoconf para a configuração. Assim que desempacotar o código-fonte, digite ./configure --help para ver uma lista de opções.
A distribuição do código-fonte do GTK também contém o código completo para todos os exemplos usados neste tutorial, além de Makefiles para ajudar na compilação.
Também é possível usar distribuições binárias, disponíveis para várias distribuições de Linux e sistemas Unix (consulte o sistema de pacotes da sua distribuição) e para ambiente Windows.
Para iniciar nossa introdução ao GTK, começaremos com o programa mais simples possível. Ele criará uma janela de 200x200 pixels e não terá nenhuma maneira de ser terminado, a não ser pelo shell ou por algum gerenciador de processos.
#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;
}
Você pode compilar o programa acima com o GCC com o comando:
gcc base.c -o base `pkg-config --cflags --libs gtk+-2.0`
O significado das opções não usuais de compilação está explicado abaixo, em "Compilando o Hello World".
Todos os programas irão, é claro, incluir o arquivo gtk/gtk.h, que declara as variáveis, funções, estruturas, etc. que serão usadas na sua aplicação GTK.
A linha seguinte:
gtk_init (&argc, &argv);
chama a função gtk_init(gint *argc, gchar ***argv), que será chamada em todas as aplicações GTK. Ela ajusta algumas coisas para nós, como a aparência padrão e os mapas de cores, e depois segue para chamar gtk_init(gint *argc, gchar ***argv). Esta função inicializa a biblioteca para uso, configura os tratadores padrão de sinal, e verifica os argumentos passados para sua aplicação na linha de comando, procurando por um dos seguintes:
- --gtk-module
- --g-fatal-warnings
- --gtk-debug
- --gtk-no-debug
- --gdk-debug
- --gdk-no-debug
- --display
- --sync
- --name
- --class
A função remove esses itens da lista de argumentos, deixando tudo o que não reconhecer para sua aplicação interpretar ou ignorar. Isso cria um conjunto de argumentos padrão aceitos por todas as aplicações GTK.
As próximas duas linhas de código criam e exibem uma janela.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window);
O argumento GTK_WINDOW_TOPLEVEL especifica que queremos que a janela seja submetida à decoração e ao posicionamento do gerenciador de janelas. Em vez de criar uma janela de tamanho 0x0, uma janela sem controles (widgets) é ajustada para 200x200 por padrão de modo que você ainda possa manipulá-la.
A função gtk_widget_show() deixa o GTK saber que terminamos de ajustar os atributos deste widget, e que ele pode agora ser exibido.
A última linha é responsável por entrar no loop de processamento principal do GTK.
gtk_main ();
gtk_main() é outra chamada que você verá em toda aplicação GTK. Quando o controle chega a esse ponto, o GTK irá apenas ficar à espera de eventos do X (como o apertamento de botões ou teclas), tempos-limite ou notificações de E/S em arquivos. No nosso exemplo simples, no entanto, os eventos são ignorados.
Hello World em GTK
editarAgora, um programa com um widget (um botão). É o clássico programa "Hello world", a la GTK.
#include <gtk/gtk.h>
//Uma novidade que vem do c..
//Os comentarios podem ser escritos com // ou /* */
/* Esta é uma função callback. Os argumentos de dados são ignorados
* neste exemplo. Mais sobre callbacks abaixo. */
static void hello( GtkWidget *widget,
gpointer data )
{
g_print ("Hello World\n");
}
static gboolean delete_event( GtkWidget *widget,
GdkEvent *event,
gpointer data )
{
/* Se você retornar FALSE no tratador do sinal "delete_event",
* o GTK emitirá o sinal "destroy". Retornar TRUE significa
* que você não quer que a janela seja destruída.
* Isso é útil para exibir diálogos do tipo 'tem certeza de
* que deseja sair?'. */
g_print ("evento 'delete' ocorreu\n");
/* Mude TRUE para FALSE e a janela principal será destruída com um
* "delete_event". */
return TRUE;
}
/* Outro callback */
static void destroy( GtkWidget *widget,
gpointer data )
{
gtk_main_quit ();
}
int main( int argc,
char *argv[] )
{
/* GtkWidget é o tipo de dado para os widgets */
GtkWidget *window;
GtkWidget *button;
/* Esta função é chamada em todas as aplicações GTK. Argumentos da linha
* de comando são interpretados e retornados à aplicação. */
gtk_init (&argc, &argv);
/* criar uma nova janela */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Quando a janela recebe o sinal "delete_event" (dado pelo gerenciador
* de janelas, geralmente pela opção "fechar", ou na barra de título),
* nós pedimos que ela chame a função delete_event () como definido
* acima. Os dado passado para a função callback é NULL e é ignorado. */
g_signal_connect (G_OBJECT (window), "delete_event",
G_CALLBACK (delete_event), NULL);
/* Aqui conectamos o evento "destroy" a um tratador de sinal. Esse
* evento ocorre quando chamamos gtk_widget_destroy() na janela, ou
* se retornamos FALSE no callback "delete_event". */
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK (destroy), NULL);
/* Ajusta a largura da borda da janela. */
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
//Coloca um nome na janela
gtk_window_set_title (GTK_WINDOW (window), "Aqui o nome da Janela");
/* Cria um novo botão com o texto "Hello World". */
button = gtk_button_new_with_label ("Hello World");
/* Quando o botão recebe o sinal "clicked", chamará a função hello()
* passando NULL como argumento. hello() é definida acima. */
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (hello), NULL);
/* Isso fará com que a janela será destruída pela chamada
* gtk_widget_destroy(window) quando o botão for clicado ("clicked").
* Novamente, o sinal destroy poderia vir daqui ou do gerenciador
* de janelas. */
g_signal_connect_swapped (G_OBJECT (button), "clicked",
G_CALLBACK (gtk_widget_destroy),
G_OBJECT (window));
/* Isto empacota o botão na janela (um recipiente gtk). */
gtk_container_add (GTK_CONTAINER (window), button);
/* O passo final é exibir o widget recém-criado. */
gtk_widget_show (button);
/* e a janela */
gtk_widget_show (window);
/* Toda aplicação GTK deve ter uma chamada gtk_main(). O controle
* termina aqui e espera por um evento (como um apertamento de tecla
* ou evento do mouse). */
gtk_main ();
return 0;
}
Compilando o Hello World
editarPara compilar, use:
gcc -Wall -g helloworld.c -o helloworld `pkg-config --cflags gtk+-2.0` \ `pkg-config --libs gtk+-2.0`
Usamos o programa pkg-config, que pode ser obtido de www.freedesktop.org. Esse programa lê o arquivo .pc que vem com o GTK para determinar as opções do compilador necessárias para compilar programas que usam GTK. pkg-config --cflags gtk+-2.0 mostrará uma lista de diretórios de inclusão nos quais o compilador deve olhar, e pkg-config --libs gtk+-2.0 mostrará a lista de bibliotecas às quais o compilador deve ligar o executável e os diretórios nos quais encontrá-las. No exemplo acima os dois comandos poderiam ter sido combinados num só, como pkg-config --cflags --libs gtk+-2.0.
Note que você deve digitar um "acento grave" e não uma aspa simples.
As bibliotecas que costumam ser usadas são:
- A biblioteca GTK (-lgtk), a biblioteca de widgets, baseada no GDK.
- A biblioteca GDK (-lgdk), o intermediário da Xlib.
- A biblioteca gdk-pixbuf (-lgdk_pixbuf), a biblioteca de manipulação de imagens.
- A biblioteca Pango (-lpango) para texto internationalizado.
- A biblioteca gobject (-lgobject), que contém o sistema de tipos no qual é baseado o GTK.
- A biblioteca gmodule (-lgmodule), usada para carregar extensões em tempo de execução.
- A biblioteca GLib (-lglib), que contém funções miscelâneas; apenas g_print() é usada neste exemplo particular. O GTK é construído sobre a GLib, portanto você sempre precisará desta biblioteca. Veja a seção sobre a GLib para mais detalhes.
- A biblioteca Xlib (-lX11) que é usada pelo GDK.
- A biblioteca Xext (-lXext). Ela contém código para pixmaps compartilhados na memória e outras extensões X.
- A biblioteca de matemática (-lm). É usada pelo GTK para vários propósitos.
Teoria dos sinais e callbacks
editarNota: Na versão 2.0, o sistema de sinais foi movido do GTK para a GLib, portanto as funções e tipos explicadas nesta seção têm o prefixo "g_" e não "gtk_". Não falaremos em detalhes sobre as extensões do sistema de sinais da GLib 2.0 em relação ao do GTK 1.2.
Antes de olharmos detalhadamente para o programa helloworld, discutiremos sinais e callbacks. O GTK é uma biblioteca movida a eventos, o que significa que ele "dormirá" na função gtk_main() até que um evento ocorra e o controle seja passado para a função apropriada.
Essa passagem de controle é feita usando a idéia de "sinais". (Note que esses sinais não são os mesmos que os sinais de sistema Unix, e não são através deles implementados, apesar de a terminologia ser quase idêntica.) Quando um evento ocorre, como o pressionamento de um botão do mouse, o sinal apropriado será "emitido" pelo widget que foi pressionado. É assim que o GTK faz a maior parte de seu trabalho útil. Há sinais que todos os widgets herdam, como "destroy" (destruir), e há sinais específicos de um certo widget ou conjunto de widgets, como "toggled" (alternado) num botão de alternar.
Para fazer um botão realizar uma ação, configuramos um tratador de sinal para capturar esses sinais e chamar a função apropriada. Isso é feito com uma função como:
gulong g_signal_connect( gpointer *object, const gchar *name, GCallback func, gpointer func_data );
onde o primeiro argumento é o widget que emitirá o sinal, e o segundo é o nome do sinal que você deseja capturar. O terceiro argumento é a função que você deseja que seja chamada na captura do evento, e o quarto, um ponteiro para os dados que você deseja passar para essa função.
A função especificada no terceiro argumento é chamada de "função de callback", e deve estar geralmente na forma
void callback_func( GtkWidget *widget, ... /* outros argumentos do sinal */ gpointer callback_data );
onde o primeiro argumento será um ponteiro para o widget que emitiu o sinal, e o último um ponteiro para os dados passados como último argumento para a função g_signal_connect(), como mostrado acima.
Note que a maneira acima para a declaração de uma função callback é apenas uma forma geral, já que alguns sinais específicos de widgets requerem parâmetros diferentes de chamada.
Outra chamada usada no exemplo helloworld é:
gulong g_signal_connect_swapped( gpointer *object, const gchar *name, GCallback func, gpointer *callback_data );
g_signal_connect_swapped() é o mesmo que g_signal_connect() exceto por um aspecto: a instância na qual o sinal é emitido e os dados (definidos pela chamada) serão invertidos quando o tratador for chamado. Então, quando essa função for usada para conectar sinais, o callback deve ser da forma
void callback_func( gpointer callback_data, ... /* other signal arguments */ GtkWidget *widget);
onde o objeto é normalmente um widget. No entanto, não costumamos definir callbacks para a função g_signal_connect_swapped(). Eles são normalmente usados para chamar, quando um sinal é emitido em algum outro objeto, uma função do GTK que aceita apenas um widget ou objeto como argumento. No exemplo helloworld, conectamos ao sinal "clicked" no botão, mas chamamos gtk_widget_destroy() na janela.
Se seus callbacks precisam de dados adicionais, use g_signal_connect() no lugar de g_signal_connect_swapped().
Eventos
editarAlém do mecanismo de sinais acima descrito, há um conjunto de eventos que refletem o mecanismo de eventos do X. Podem-se também associar callbacks a esses eventos, que são:
- event (evento)
- button_press_event (apertamento de botão)
- button_release_event (soltamento de botão)
- scroll_event (rolagem)
- motion_notify_event (notificação de movimento)
- delete_event (deleção)
- destroy_event (destruição)
- expose_event
- key_press_event (apertamento de tecla)
- key_release_event (soltamento de tecla)
- enter_notify_event (notificação de entrada)
- leave_notify_event (notificação de saída)
- configure_event (configuração)
- focus_in_event (entrada de foco)
- focus_out_event (saída de foco)
- map_event (mapeamento)
- unmap_event (desmapeamento)
- property_notify_event (notificação de propriedade)
- selection_clear_event (limpeza da seleção)
- selection_request_event (requisição de seleção)
- selection_notify_event (notificação de seleção)
- proximity_in_event (proximidade)
- proximity_out_event
- visibility_notify_event (notificação de visibilidade)
- client_event (cliente)
- no_expose_event
- window_state_event (estado da janela)
Para conectar uma função callback a um desses eventos, use a função g_signal_connect(), como descrito acima, usando um dos nomes de eventos acima como parâmetro name. A função callback para eventos tem uma forma ligeiramente diferente da usada para os sinais:
gint callback_func( GtkWidget *widget, GdkEvent *event, gpointer callback_data );
GdkEvent é um union em C, cujo tipo dependerá de qual dos eventos acima ocorreu. Para podermos distinguir que evento foi lançado, cada alternativa possível tem um membro chamado type (tipo) que reflete o evento sendo lançado. Os outros componentes da estrutura dependerão do tipo do evento. São valores possíveis para o tipo:
GDK_NOTHING GDK_DELETE GDK_DESTROY GDK_EXPOSE GDK_MOTION_NOTIFY GDK_BUTTON_PRESS GDK_2BUTTON_PRESS GDK_3BUTTON_PRESS GDK_BUTTON_RELEASE GDK_KEY_PRESS GDK_KEY_RELEASE GDK_ENTER_NOTIFY GDK_LEAVE_NOTIFY GDK_FOCUS_CHANGE GDK_CONFIGURE GDK_MAP GDK_UNMAP GDK_PROPERTY_NOTIFY GDK_SELECTION_CLEAR GDK_SELECTION_REQUEST GDK_SELECTION_NOTIFY GDK_PROXIMITY_IN GDK_PROXIMITY_OUT GDK_DRAG_ENTER GDK_DRAG_LEAVE GDK_DRAG_MOTION GDK_DRAG_STATUS GDK_DROP_START GDK_DROP_FINISHED GDK_CLIENT_EVENT GDK_VISIBILITY_NOTIFY GDK_NO_EXPOSE GDK_SCROLL GDK_WINDOW_STATE GDK_SETTING
Assim, para conectar uma função callback a um desses eventos, escreveríamos algo como:
g_signal_connect (G_OBJECT (button), "button_press_event", G_CALLBACK (button_press_callback), NULL);
Esse código assume que button é um widget Button (botão). Agora, quando o mouse (rato) está sobre esse botão e um botão do mouse for pressionado, a função button_press_callback() será chamada. Essa função pode ser declarada como:
static gboolean button_press_callback( GtkWidget *widget, GdkEventButton *event, gpointer data );
Note que podemos declarar o segundo argumento como do tipo GdkEventButton porque sabemos que tipo de evento ocorrerá para que essa função seja chamada.
O valor retornado nessa função indica se o evento deve ser propagado pelo mecanismo de tratamento de eventos do GTK. Retornar TRUE indica que o evento foi tratado, e que não deve ser mais propagado. Retornar FALSE continua o tratamento normal de eventos. Veja a seção sobre Manipulação avançada de eventos e sinais para obter mais detalhes deste processo de propagação.
Para detalhes nos tipos de dados GdkEvent, veja o apêndice Tipos de evento do GDK.
As APIs de seleção e arrastar-e-soltar do GDK também lançam certos eventos refletidos no GTK pelos sinais. Veja Sinais no widget de origem e Sinais no widget de destino para detalhes da "assinatura" das funções callback para esses sinais:
- selection_received
- selection_get
- drag_begin_event
- drag_end_event
- drag_data_delete
- drag_motion
- drag_drop
- drag_data_get
- drag_data_received
Analisando o Hello World
editarAgora que já conhecemos a teoria por trás disso, vamos tornar as coisas mais claras percorrendo todo o código do exemplo helloworld.
Esta é a função callback que será chamada quando o botão é pressionado ("clicado"). Aqui, ignoramos tanto o widget quanto os dados, mas não é difícil fazer coisas com eles. O próximo exemplo usará o argumento data para nos dizer qual dos botões foi pressionado.
static void hello( GtkWidget *widget, gpointer data ) { g_print ("Hello World\n"); }
O próximo callback é um pouco especial. O evento "delete" ocorre quando o gerenciador de janelas envia esse evento para a aplicação. Podemos escolher o que fazer a respeito desse evento: ignorá-lo, dar algum tipo de resposta (como uma mensagem do tipo "Tem certeza de que deseja sair?") ou simplesmente terminar a aplicação.
O valor que você retornar nesse callback informa ao GTK que ação tomar. Retornando TRUE, informamos que não queremos que o sinal "destroy" seja emitido, para que nossa aplicação continue a executar. Retornando FALSE, pedimos a emissão do sinal "destroy", que, por sua vez, chamará nosso tratador de sinal "destroy".
static gboolean delete_event( GtkWidget *widget, GdkEvent *event, gpointer data ) { g_print ("delete event occurred\n"); return TRUE; }
Aqui está outra função callback que causa o término do programa, chamando gtk_main_quit(). Essa função diz ao GTK que ele deve sair da função gtk_main assim que ele receber o controle de volta.
static void destroy( GtkWidget *widget, gpointer data ) { gtk_main_quit (); }
Assumimos que você conhece a função main()… sim, assim como em outras aplicações, toda aplicação GTK terá também uma função dessa.
int main( int argc, char *argv[] ) {
Esta próxima parte declara ponteiros para uma estrutura do tipo GtkWidget; esses são usados abaixo para criar uma janela e um botão.
GtkWidget *window; GtkWidget *button;
Aqui está nosso gtk_init() novamente. Como antes, essa função inicializa a biblioteca, e interpreta os argumentos de linha de comando encontrados. Os argumentos reconhecidos pelo GTK/GDK são removidos da lista, e argc e argv são modificados, retirando-se quaisquer rastro da existência desses argumentos, o que permitirá que sua aplicação interprete os argumentos restantes corretamente.
gtk_init (&argc, &argv);
O método para criar uma nova janela é bastante direto. Aloca-se memória para a estrutura GtkWidget *window, de modo que o ponteiro agora aponte para uma estrutura válida. A função prepara uma nova janela, que, no entanto, não será mostrada até que chamemos gtk_widget_show(window) pelo final do nosso programa.
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
Aqui estão dois exemplos de como conectar um tratador de sinal a um objeto — neste caso, a janela. Aqui, os sinais "delete_event" e "destroy" são interceptados. O primeiro é emitido quando usamos o gerenciador de janelas para terminar a janela ou quando usamos a chamada gtk_widget_destroy() passando o widget da janela como objeto a ser destruído. O segundo é emitido quando, no tratador de "delete_event", retornamos FALSE. G_OBJECT e G_CALLBACK são macros que realizam conversão (casting) e verificação de tipos, além de ajudar na legibilidade do código.
g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);
Esta próxima função é usada para ajustar um atributo de um objeto do tipo "container". Ela apenas faz com que a janela tenha ao seu redor uma área em branco de 10 pixels de largura onde nenhum widget irá ficar. Há outras funções similares para as quais olharemos na seção Ajustando atributos de controles.
E, novamente, GTK_CONTAINER é uma macro para realizar conversão de tipos.
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
Esta chamada cria um novo botão. Ela aloca espaço na memória para uma nova estrutura GtkWidget, inicializa-a e faz o ponteiro "button" apontar para ela. O botão terá nele a etiqueta "Hello World" quando exibido.
button = gtk_button_new_with_label ("Hello World");
Aqui, mandamos o botão fazer algo de útil. Anexamos a ele um tratador de sinal para que nossa função hello() seja chamada quando ele emitir o sinal "clicked". Os dados são ignorados, então simplesmente passamos NULL para o callback hello(). Obviamente, o sinal "clicked" é emitigo quando o botão é clicado (premido) com o mouse (rato).
g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hello), NULL);
Também vamos usar esse botão para sair do nosso programa. Isso ilustra como o sinal "destroy" pode vir tanto do gerenciador de janelas quanto do nosso programa. Quando o botão é apertado ("clicked"), como no caso de cima, chama-se o callback hello() e depois este, na ordem em que eles foram configurados. Você pode ter tantos callbacks quanto precisar, e todos serão executados na ordem em que você os conectou. Já que a função gtk_widget_destroy() aceita apenas um GtkWidget * como argumento, usamos aqui a função g_signal_connect_swapped() em vez de simplesmente g_signal_connect().
g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (gtk_widget_destroy), G_OBJECT (window));
Esta é uma "chamada de empacotamento", que será explicada a fundo mais adiante, em Empacotando widgets; mas é razoavelmente fácil de ser entendida. Ela simplesmente diz ao GTK que o botão deve ser colocado na janela, onde ele será exibido. Note que um "container" GTK só pode conter um widget; mas há outros widgets — que serão descritos mais tarde — que são desenhados para dispor vários widgets de várias maneiras.
gtk_container_add (GTK_CONTAINER (window), button);
Agora já temos tudo configurado da maneira que queremos que seja. Com todos os tratadores de sinal no lugar, e o botão colocado na janela no lugar que deverá estar, mandamos o GTK mostrar ("show") os widgets na tela. O widget da janela é mostrado por último para que a janela apareça inteira de uma vez — em vez de vermos primeiro a janela aparecer e depois o botão se formar dentro da janela. Claro que, com um exemplo tão simples, você dificilmente (ou nunca) notaria essa diferença.
gtk_widget_show (button); gtk_widget_show (window);
E, é claro, chamamos a função gtk_main(), que aguarda eventos do servidor X e irá coordenar as emissões de sinais quando esses eventos forem detectados.
gtk_main ();
E o retorno final. O controle do programa volta para cá quando gtk_quit() for chamada.
return 0;
Agora, quando clicarmos o botão do mouse num botão do GTK, esse widget emitirá o sinal "clicked". Para podermos usar essa informação, nosso programa cria um tratador de sinal para interceptar esse sinal, despachando a função de nossa escolha. No nosso exemplo, quando o botão que criamos é apertado, a função hello() é chamada com um argumento NULL, e depois o próximo tratador para esse sinal é chamado — a função gtk_widget_destroy(), passando a ela o widget da janela como argumento, e destruindo esse widget. Com isso, a janela emite o sinal "destroy", que é interceptado e faz chamar nossa função callback destroy(), que simplesmente sai do GTK.
Outro caminho é usar o gerenciador de janelas para fechar a janela, o que causará a emissão do sinal "delete_event". Isso chamará nosso tratador de sinal "delete_event". Nele, se retornarmos TRUE, a janela será deixada como está e nada irá acontecer. Retornando FALSE, fazemos o GTK emitir o sinal "destroy" que, é claro, chama o callback "destroy", deixando o GTK.