Posts de Janeiro, 2009

Desenvolvimento para Plataforma GNOME: Conceitos Básicos (Mini Tutorial GTK)

Janeiro 31, 2009

Olá a todos

Sinto demorar tanto a dar continuidade aos artigos, a vida anda meio agitada, mas, aqui estou, e vamos continuar com a nossa série de artigos sobre desenvolvimento para platafoma GNOME.

Nesse artigo, falaremos sobre os conceitos mais básicos de desenvolvimento usando GTK. Apesar de ser um assunto relativamente simples(ao menos, para quem ja programou utilizando alguma biblioteca orientada a eventos), é de extrema importância, ja que o que é discutido aqui, vai ser utilizado em todo e qualquer programa GTK que voce fizer.

Como ja tinha mencionado em um pequeno post anterior, vou me restringir a mostrar os exemplos de programa utilizando PyGTK, mas, lembrem que, os mesmos conceitos se aprendem a todas as bindings da biblioteca GTK, logo, uma rapida lida no manual de alguma outra binding, e voce ja vai conseguir aplicar as mesmas ideias. Para terminar, vai uma pequena dica que esqueci de mencionar no artigo em que instalamos e configuramos o eclipse para desenvolvimento python. Acontece que, as bindings do python podem ser separadas em duas partes: a parte implementada em python, e a parte nativa(codigo C). A engine de autocomlete do eclipse nao consegue entender as widgets implementadas na parte nativa( ou seja, quase todas as widgets) porque não consegue fazer instrospecção no código. Acontece, que há uma forma muito simples de resolver isso: abra Window->Preferences->PyDev->Interpreter – Python A ultima lista da tela se chama Forced Builtin libs. Essa lista contem uma lista de bibliotecas que devem ser carregadas pelo PyDEV sempre. Adicione o módulo _gtk à lista e pronto, o autocomplete vai funcionar para todas as widgets do GTK. Sò para curiosidade, caso algum de voces esteja usando Emacs + Ropemacs(a um outro post no blog falando sobre essa dupla), o ropemacs tem o mesmo problema com autocomplete para GTK, e a solução é a mesma(adicione o modulo _gtk à lista extension_modules no arquivo config.py, dentro do .ropeproject do seu projeto). Lembre-se sempre dessa dica quando estiver utilizando alguma biblioteca em python, e o autocomplete não estiver funcionando. Geralmente, o problema é algum módulo nativo carregado internamente.

Agora, com tudo configurado, mãos a obra: vamos falar sobre conceitos. O GTK é uma biblioteca orientada a eventos, ou seja, as ações ocorrem em conseguencia de alguma outra ação, geralmente do usuário (como o clique de um botão na interface). Para quem nunca viu isso, a idéia é simples: Quando o programa começa, são executadas todas as rotinas de construção da interface(funções que constroem a janela, dizem qual tamanho deve ter, o que ela tem dentro, ou seja, a parte burocrática da história. Depois de todos os elementos da interface construídos, a aplicação entra no que chamamos mainloop. O mainloop é um ponto onde a aplicação fica parada(em loop), aguardando alguma coisa. Mas, o que seria essa coisa? Essa coisa, é algum evento(por isso, programação orientada a eventos ;) ). A definição de um evento, depende da biblioteca que se esta usando. Por exemplo, quando se desenvolve uma aplicação utilizando Win32API(a API do windows), ou até mesmo MFC, um evento é uma mensagem (não vou entrar muito no conceito para nao complicar demais a história) enviada para a aplicação. Quando uma mensagem é recebida, a aplicação sai do loop, executa o que tiver de executar(de acordo com o tipo de mensagem que recebeu), e voltar para o mainloop, aguardando a próxima mensagem. Complicado? Seguem um pequeno exemplo (fictício):

janela = Nova_Janela()
botao = Novo_Botao()

#isso é um mainloop, bem tosco
while(True):
	mensagem = aguarda_mensagem() #fica parado aqui enquanto uma mensagem nao chega

#opa, saiu, chegou uma mensagem
if mensagem == Constantes.MensagemSair:
	finalizar_programa()
elif mensagem == Constantes.MensagemCliqueBotao:
	funcao_a_executar_quando_clicar_no_botao()
elif mensagem == ...

...

Entenderam? A aplicação só sai do loop, quando receber uma mensagem do tipo MensagemSair. Enquanto isso, ela fica sempre la, esperando por novas mensagens, executando o que o usuário pedir, até que ele peça para fechar a aplicação. Simples não? Pois então, cada biblioteca utiliza um conceito diferente para funcionar como evento. No caso da Gtk(e, apenas para saciar os curiosos, da Qt também), um evento, é chamado de sinal. Ou seja, a aplicação fica no mainloop, aguardando sinais, até que receba um sinal que mande ela terminar a execução do programa. Cada componente do Gtk (os componentes chamam-se widgets, e vamos usar o nome correto de agora em diante) suporte um conjunto de sinais, ou seja, eu não posso mandar qualquer sinal para qualquer widget. Os sinais suportados por cada widget estão documentados na documentação da Gtk. Alias, outra aplicação maravilhosa que esqueci de mencionar no artigo Preparando o Ambiente, é o devhelp. O DevHelp é um navegador de documentação. Toda a documentação da Gtk(e de várias de suas bibliotecas e bindings) está disponível para consulta offline no devhelp. Sugiro fortemente que o instalem.

Bom, creio que ja pegaram a idéia da programação orientada a eventos, certo. Porém, creio que alguns ja devem ser se feito a pergunta: eu vou ter um if else if gigante na aplicação com todos os possiveis sinais? :O . Bom, se voce estivesse programando para Win32 pura (em C, não em C++), sim, voce teria que ter um switch case gigante. No nosso caso, a vida é muito mais feliz. O Gtk utiliza um conceito diferente. Nós conectamos um sinal, ao uma função de callback.

HEIN!!??HÃÃ??

Calma, calma. É simples. Cada widget possui um conjunto de sinals que ele pode receber. Para ensinar ao widget o que fazer quando ele recebe um sinal, nós dizemos a ele: “Botão, sempre que você receber o sinal que se chama clicked, você vai executar a funcao, xyz, que eu vou criar especialmente para isso”. Entenderam? Basicamente, você vai criar uma função qualquer, que contêm a lógica que você deseja que seja executada quando se clicar no botão. Depois, quando você estiver criando o botão(lembrem que eu disse que, antes de entrar no mainloop, a aplicação criar todos os widgets que compoem a tela), você vai dizer “Botão, quando receber o sinal ‘clicked’, execute esta função“. O processo de “ensinar” o widget qual função deve ser executada quando receber determinado sinal, chama-se conectar, ou seja, nós conectamos um sinal, ao uma função. Quando uma função foi criada para ser conectada a um sinal, nós a chamamos de “função de callback“, ou, para encurtar a história, apenas de “callback“. Vamos revisitar a minha frase monstruosa anterior:

“Nós conectamos um sinal, ao uma função de callback

Muito mais fácil de entender agora, certo? Ou seja, ao invés de um switch case gigante, depois que você construir todos os widgets da sua tela e conectar todos os sinais que você deseja, o gtk vai possuir um catalogo de sinais que devem ser tratados(o gtk vai tratar apenas os sinals que você determinou que devem ser tratados), e qual a callback que deve ser executada para cada um dos sinais. Só para finalizar o conceito, cada widget que é criado, possui um id próprio, ou seja, dois botões, na mesma tela, possuem ids diferentes. Quando conectamos um sinal, nós conectamos para um botão especifico. Dessa forma, botões diferentes, vão ter callbacks diferentes, para, por exemplo, o sinal “clicked”. Simples não. Quando a Gtk recebe um evento(sinal), ela verifica quem mandou o sinal(id do widget que mandou), qual o sinal(nome do sinal, como, por exemplo, “clicked”), e qual a callback associada a esse sinal. Se ele encontrar uma callback cadastrada, ele executa a função. Quando a função terminar de executar, o controle da aplicação volta para o mainloop, que vai ficar aguardando um novo sinal. Viram, é bem simples de entender.

Esse é o fundamento mais básico da programação em gtk. É sempre apenas uma questão de criar widgets, conectar sinais, e soltar a imaginação :) . Então, depois de toda essa teoria, vamos enfiar a mão na massa. Vamos construir a nossa primeira aplicação GTK. De início, vamos criar uma aplicação bem simples: 1 janela, 1 botão e 1 label. Ao clicar no botão, vamos alterar o texto do label. Ao clicar no “X” da janela, nos finalizamos o programa. Bom,vamos la: No Eclipse, abra a “PyDev Perspective”. É ai que o eclipse habilita todos os recursos de programação python. Vamos criar um projeto novo, chamado GtkExemplo1.

Siga File->New->PyDev Project

gtkconceitosbasicos-figura1

Next->Finish

Pronto, o projeto está criado. Nossos arquivos fonte ficarão dentro do diretório src, que já foi criado pelo assistente. Agora, vamos criar um arquivo fonte novo:

Botão direito em cima de src->New->PyDev Module

gtkconceitosbasicos-figura2

Finish

Pronto, temos o nosso arquivo pronto para programar. Vamos agora, criar a aplicação, passo a passo. O primeiro passo, é criar todas as widgets que vamos usar na tela. Como eu disse anteriormente, nós teremos uma janela, um botão, e um label. Então, vamos cria-los:

# _*_ coding: utf-8
import gtk

# CONSTRUINDO AS WIDGETS

#criando a janela. TOPLEVEL é para informa que essa janela é uma janela principal
#não é filha de nenhuma outra janela. Mais tarde falaremos sobre isso
janela = gtk.Window(gtk.WINDOW_TOPLEVEL)

#aqui, estamos definindo um botão, já com o texto
botao = gtk.Button(">Aqui<")

#creio que o label dispense comentários ;)
label = gtk.Label("Pressione o botão")

Até aqui, tudo bem? Construimos todas as widgets que vamos usar na nossa tela. Agora, conectas as callbacks. vale lembrar que, em um programa, nós temos que crias as callbacks antes de conectadas, ja que será necessário passar o nome de uma função existente como callback ao conectar o sinal. Vamos Criar duas callbacks, uma para ser executada quando o botão receber um clique, outra quando a janela receber um clique no X. Vamos defini-las no começo do arquivo, antes do nosso bloco principal.

def on_botao_clicked(button):
	label.set_text("Obrigado") # a funcao set_text altera o texto do label

Pronto, temos uma função de callback. Vocês notaram os paramêtros? Então, toda função de callback deve seguir um esquema rígido de parametros. Quem determina quais os parâmetros que uma callback deve esperar, é o sinal que ela está tratanto. Ou seja, no nosso caso aqui, quem dita os argumentos obrigatórios da nossa callback, é o sinal clicked. Para saber quais os parametros que você deve definir na sua callback, basta consultar a documetação. Por exemplo, nesse link, nós temos a documentação da widget gtk.Button. Se você rolar a tela, encontrará uma seção chamada gtk.Button Signal Prototypes. Essa seção descreve qual deve ser o protótipo(ou seja, o formato) da função que pode ser usada como callback para cada sinal que o widget suporte. Se olharmos para o sinal “clicked”, veremos que o protótipo é def callback(button, user_param1, ...) . Bom, como ler isso. Clique no link do sinal, e ele vai abrir a seção correspondente. A definição diz que button é a widget que recebeu o sinal(no nosso caso, como estamos em python, é o objeto botao que criamos la no bloco principal.). user_param1, ... são quaisquer parametros (opcionais) que podem ser passados para a função. A única regra, é que a quantidade de parâmetros definidos na função deve ser a mesma quantidade de argumentos passados ao conectar o sinal à callback. Complicado, não se assutos, veremos logo adiante como funciona isso.

Agora, vamos criar a callback que deve ser executada quando clicarmos no X da janela:

def on_janela_hide(widget):
	print "Saindo..."
	gtk.main_quit()

Definimos mais uma callback. Quando se clica no X da janela, é enviado um sinal “hide” para a janela. Se consultarmos a documentação do gtk.Window, vamos ver que hide não esta listado nos sinais. MAS COMO!??!. Calma, Calma. Veja que há mais 3 links. Esses links representam todas as classes pai de gtk.Window. O GTK é uma biblioteca orientada a objetos, logo, por herança, quando se cria uma subclasse de um widget, herda-se todos os sinais da classe pai. Você vai encontrar o sinal “hide” definido em gtk.Widget Signal Prototypes. Ficou claro? Bom, procurem a definição de “hide”, e vocês verão que é def callback(widget, user_param1,.... Opa, ja vi esse protótipo, é igual ao do “clicked”. Sim, é verdade, a maioria dos sinais segue esse mesmo protótipo(mas, isso não é regra, então, leia a documentação). Ai, voce pode me perguntar: como eu sei qual o sinal que eu devo tratar? Bom, tudo está documentado, então, basta ler a documentação. E, caso você não consiga encontrar, voce poder perguntar em alguma mailing list. A gtk-list@gnome.org é a principal mailing list para pergunta de usuários, ou seja, é o local correto para se fazer perguntas como essa(que dizem respeito a como usar a gtk). Você tambem pode perguntar na lista específica do binding que voce está usando. Por exemplo, nós estamos usando PyGTK aqui. Se você abrir a página do PyGTK, e clicar em Support, verá que há uma mailing list para tirar dúvidas. O endereço é http://www.daa.com.au/mailman/listinfo/pygtk. Se voce for utilizar alguma outra binding, basta checar na página da mesma qual a lista correta para fazer perguntas sobre como usar. :) . Para fechar a explicaçao, gtk.main_quit() é a função que faz o gtk sair do mainloop(que geralmente é a ultima linha da aplicação) e, por consequencia, finalizar a aplicação

Bom, construimos os widgets, criamos as callbacks, agora, é hora de conectar os sinais.

#conectando os sinais
botao.connect("clicked", on_botao_clicked)
janela.connect("hide", on_janela_hide)

Pronto. Essa parte é bem facil de enteder, não? Todo gobject(que é a classe pai de todos os widgets) tem uma função connect. Os argumentos para essa função são sempre os mesmos: “sinal”, callback, user_params(opcional) . Opa, opa, o que esse user_params está fazendo ai. Simples, lembra que eu comentei do user_params acima, no protótipo da função. Acontece que user_param não é um parametro obrigatório. Se quisessemos passar algum parâmetro adicional a uma callback toda vez que ela for chamada, devemos alterar a definição da callback e a conexao do sinal da seguinte forma:

def on_botao_clicked(button, parametro1):
	print parametro1

...

botao.connect("clicked", on_botao_clicked, "isso é o parametro 1")

Entenderam? Se eu adicionar 1 parametro na hora da conexão, eu devo adicionar um parametro na lista de parâmetros da callback. Se eu adicionar 2, adiciono 2 na callback, e por ai vai …

Bom, estamos quase no final. Agora, basta adicionarmos os widgets à janela, e rodar a aplicação, certo? Bom, mais ou menos. Acontece que, se voce olhar a documentação, verá que gtk.Window, é subclasse de um widget chamado gtk.Bin. Esse widget é um container, que aceita apenas 1 widget filho. HEIN!??!. Pois é, a idéia, é a seguinte: todo widget, que pode conter outros widgets, são chamados containers. Quando adicionamos um widget dentro de outro, estabeleçemos uma relação pai/filho. O container torna-se o pai, e o widget que foi adicionado, torna-se o filho.Containers, podem permitir 1 ou mais widgets filho. Isso é interessante pois, ao agruparmos diversos widgets dentro de um container, podemos progagar alguns sinals para todos os filhos, enviando apenas para o widget pai. O container se encarrega de enviar o sinal para todos os seus filhos. Ja ja veremos um exemplo disso. Mas, voltando ao assunto, nao podemos adicionar o botão e o label dentro da janela, pois, como ja vimos, janela permite apenas 1 widget filho. Isso nos dá a ponta para tocarmos no último assunto teórico: Layout Containers.

Layout Containers são widgets cuja funcionalidade é organizar os seus filhos na tela. É o Layout Container que vai controlar se os seus widgets devem ser alinhados na horizontal, na vertical, se deve ser redimensionados, se devem ter uma posição fixa, etc, etc,etc. Existem diversos layout containers diferentes, cara um com uma característica, e nós iremos ve-los no decorrer dos artigos. Nesse primeiro momento, vou lhes aprensentar um LayoutManager simples e útil: VBox. O gtk.VBox alinha todos os seus filhos verticalmente, na ordem em que forem inseridos no container. Como vimos anteriormente, gtk.Window aceita apenas 1 widget filho, porém, nada impede que esse widget seja um outro container que aceita diversos widgets ;) . Na verdade, teríamos problemas para montar uma interface se isso nao fosse possivel, hehehe. Bom, então, nós iremos criar um container VBox. Dentro desse container, armazenaremos nosso label e nosso botão, e , para finalizar, armazenaremos a vbox na janela. Vamos la então:

# Empacotando os widgets
vbox = gtk.VBox()
vbox.add(label)
vbox.add(botao)

janela.add(vbox)

Muito simples, não. Exatamente como descrito acima. Creio que não exige mais explicações. Agora, Estamos a dois passos do final. Todo widget possui um propriedade interna que diz se ele deve ou não ser mostrado na tela (ou seja, informa se ele é visível). Acontece que, todo widget, logo que é criado, é invisível. Para que ele apareça na tela, é necessário avisa-lo que ele deve aparecer. Por um acaso, essa função se chama show (e esta definida em gtk.Widget, a classe pai de todos os widgets). Ou seja, temos que dizer a todos os widgets do programa, que eles devem aparecer (invocar a função show em todos eles). É nessa hora que utilizaremos o recurso que mencionei acima, quando expliquei de containers progagando sinais. Por que nao eu dizer para a minha widget pai principal, ou seja, minha janela, para mandar todos os seus filhso aparecerem. Mas janela nao é pai de apenas um widget? Sim, ele pai do nosso vbox, que é outro container. Ou seja, a janela vai aparecer e dizer ao vbox que ele deve aparecer. O vbox, por sua vez, vai dizer a todos os seus filhos(o botao e o label), que eles devem aparecer. Dessa forma, eu propagei o sinal por todos os widgets, usando apenas uma chamada ;) . Ótimo não. Nesse caso, tudo o que temos a fazer, é chamar a função show_all na janela, e a mágica esta feita.

Agora sim, construímos todos os nossos widgets, empacotamos(o processo de adicionar um widget a um container se chama empacotar, ou packing)todos eles no containers corretos, e informamoes que todos devem ser visíveis. Agora, basta dizer à aplicação que ela esta pronta para rodar, ou seja, ativar o mainloop. Para isso, adicionamos a última linha:

gtk.main()

Pronto, nossa aplicação está prontinha, basta executar o programa. Para isso, clique com o botao direito no arquivo main.py -> Run As -> Python Run .

gtkconceitosbasicos-figura3

Pronto, ai esta nossa primeira aplicação em python. Cansativo? Bom, realmente, há muitos conceitos a serem explicados, e eu tentei deixar tudo o mais leve possível. Agora, você ja tem toda a base necessária para programar em gtk. Lembrando que, apesar dos exemplos serem em python, a mesma ideia se aplica a todos as bindings. Com isso em mente, basta pegar a documentação do Gtk, do Gtkmm, ou qualquer outra binding, que voce vai encontrar os mesmos elementos, as mesmas idéias. Apenas a linguagem de programação muda. Eu tentei usar o mínimo possível dos truques que o python permite, esse programa poderia ser mais enxuto e versátil, porém, a idéia aqui, é mostrar o Gtk, e os conceitos pertinentes ao Gtk, e não python, logo, tentei manter o mais legível possivel, para que mesmo que nunca viu python possa entender o que acontece. Segue o código final da nossa aplicação:

# _*_ coding: utf-8
import gtk

def on_botao_clicked(button):
	label.set_text("Obrigado") # a funcao set_text altera o texto do label

def on_janela_hide(widget):
	print "Saindo..."
	gtk.main_quit()

# CONSTRUINDO AS WIDGETS

#criando a janela. TOPLEVEL é para informa que essa janela é uma janela principal
#não é filha de nenhuma outra janela. Mais tarde falaremos sobre isso
janela = gtk.Window(gtk.WINDOW_TOPLEVEL)

#aqui, estamos definindo um botão, já com o texto
botao = gtk.Button(">Aqui<")

#creio que o label dispense comentários ;)
label = gtk.Label("Pressione o botão")

#conectando os sinais
botao.connect("clicked", on_botao_clicked)
janela.connect("hide", on_janela_hide)

# Empacotando os widgets
vbox = gtk.VBox()
vbox.add(label)
vbox.add(botao)

janela.add(vbox)

janela.show_all()

gtk.main()

Depois de toda essa ginastica vem a notícia: voce geralmente nao vai fazer a maior parte disso na mão. HÃ, O QUE!!?! HAHAHAHAHA. Pois é. Ao menos, a parte de desenhar e organizar os widgets, geralmente não se faz manualmente. Nós usamos duas ferramentas ótimas para tornar o processo melhor e mais rápido: O glade, e a libglade. O glade é um construtor de interface visual. Ele permite que voce desenhe toda a aplicação na ferramenta(quem ja viu delphi ou vb alguma vez na vida, sabe do que estou falando). Depois, ele guarda as informações da interface em um arquivo XML. Por fim, basta a aplicação ler o arquivo xml e construir a interface automaticamente. Isso é feito pela libglade, uma biblioteca que le o conteudo do arquivo XML gerado pelo glade, e constroe toda a interface para voce. Ai, fica para voce o trabalho de colocar a lógica, que no fim, é o que importa. Economiza muito tempo não? Pois é, o Glade é o assunto do nosso próximo artigo, então, até la. Como sempre, dúvidas e sugestões, use os comentários :) .=”"

Demora no artigo e mudanças

Janeiro 9, 2009

Venho de novo pedir desculpas pela demora na continuação dos artigos de programação para gnome.
Excesso de trabalho e festas de final de ano acabaram tomando bastante meu tempo, logo, não tive tempo para terminar o artigo. Preferi esperar mais um pouco a publicar um artigo pela metade.

Estarei também fazendo uma alteração no estilo dos artigos: eu iria publicar exemplos de código em java e python, porém, a cobertura do java-gnome não é tão extensa quanto do pygtk, gtkmm e do gtk , logo, por agora, estarei escrevendo os exemplos de código em python(pygtk). Farei isso porque pretendo escrever artigos que cubram o máximo possível da plataforma gnome, inclusive uso do gstreamer, libcamberra e bluez (para integração com bluetooth). Logo, o Pygtk possui o máximo de cobertura (tanto quanto gtkmm e gtk), porém se mantém simples.

Bom, é isso, espero não ter decepcionado muito as pessoas que esperavam ver mais exemplos do Java-gnome(são bem dificeis de se achar na internet). Eu ainda pretendo escrever alguns artigos sobre java-gnome no futuro.

Bom, obrigado, aguardo os comentários, e, sim, o próximo artigo não vai demorar muito mais.