Nokia lança Qt 4.5 e Qt Creator 1.0 – Qt Extended vai pro saco

March 6, 2009 · Posted in Qt · 1 Comment 

A notícia é velha, mas ainda está quentinha. Na última terça-feira dia 03 de março, a Nokia anunciou o lançamento da tão aguardada versão 4.5 do Qt, deu um fim no Qt Extended e ainda liberou o primeiro release do Qt Creator.

Read more

Instalando e integrando Eclipse, CDT, Qt, Subversion, Perl, Vim e Web Tools somente com o mouse

February 21, 2009 · Posted in C/C++, Dicas, Eee, Perl, Qt, Subversion, VIM, Web · Comment 

Há muito tempo atrás em um post nem tão longe daqui, nós mostramos como instalar o eclipse com plugins para desenvolvimento em Qt e integrando com o Subversion. Mas o tempo passa, as versões mudam e tudo fica diferente, e desta vez vamos mostrar como fazer isso apenas com cliques e num Eee Pc.

Read more

Eclipse + Qt + svn

May 9, 2008 · Posted in C/C++, Qt, Subversion · 1 Comment 

Mais como log pessoal do que um tutorial, senti a necessidade de deixar num local de fácil acesso, as etapas de configuração do eclipse para meu próprio uso, ou seja, a instalação do eclipse e dos plugins que eu normalmente uso (svn e qt).

Os comandos que afetam diretórios fora de seu home, precisarão de permissão de superusuário.

Se você resolveu instalar o eclipse apartir dos pacotes disponíveis em sua distribuição, pode pular direto para o passo #2.

Passo #0: Instalação do Java

Para que o eclipse funcione, vc precisa do Java Runtime Environment (JRE) instalado *E* configurado.

O JRE pode ser baixado direto do site da Sun http://java.sun.com/javase/downloads/index.jsp. Instruções completas de instalação e configuração podem ser encontradas em http://java.sun.com/javase/6/webnotes/install/index.html, para o caso do JRE 6.

Passo #1: Donwload e instalação do eclipse

O eclipse pode ser facilmente baixado a partir da área de downloads no seu site oficial: http://www.eclipse.org/downloads. Após o download, basta descompactá-lo e sair usando. No meu caso eu usei:

user@host$ tar -xvzf eclipse-cpp-europa-winter-linux-gtk.tar.gz -C /usr/local

Se tudo deu certo o executável do eclipse estará em /usr/local/eclipse/eclipse, daí é só criar um link/atalho/whatever no seu ambiente gráfico favorito.

Passo #2: Download e instalação do plugin de integração com o QT


Qt Eclipse Integration
Qt Eclipse Configuration

O plugin que eu atualmente uso é o fornecido pela própria Trolltech. Nele você pode gerenciar os seus arquivos de projeto a partir de um pequeno editor gráfico, e ainda se preferir, tem acesso direto ao arquivo .pro.

O download pode ser feito a partir de http://trolltech.com/developer/downloads/qt/eclipse-integration-download, e a instalação cujas instruções completas podem ser encontradas em http://trolltech.com/developer/downloads/qt/qteclipse-installmanual, é complicadíssima:

user@host$ tar -xvzf qt-eclipse-integration-linux.x86-gcc3.3-1.4.0.tar.gz -C /usr/local

Se tudo deu certo, os arquivos do plugin foram copiados para o diretório /usr/local/eclipse/plugins.

Nota: Para quem optou pela instalação do eclipse através dos pacotes da distribuição, atenção!!! Dentro do arquivo compactado, há o diretório eclipse/plugins/, e dento dele os arquivos do plugin, que devem ser copiados para o diretório de plugins da instalação do seu eclipse, normalmente /usr/lib/eclipse/plugins.

Após a instalação, inicie o eclipse com o comando:

user@host$ /usr/local/eclipse/eclipse -clean

Agora, vá em Window>Preferences>Qt e ajuste a versão e os “pathes”, de acordo com a sua instalação do Qt.

Para ficar mais cômodo, se você invoca o eclipse direto da linha de comando, adicione o diretório do seu executável na variável de ambiente $PATH, dentro de algum dos scripts de inicialização (.bash_profile)

export PATH=$PATH:/usr/local/eclipse

Passo #3: Subversion

No eclipse vá em Help>Softwares Updates>Find and Install. Marke a opção Search for new features to install e clique em Next. Agora adicione os sites remotos:

Buckminster

http://download.eclipse.org/tools/buckminster/updates

SubClipse

http://subclipse.tigris.org/update_1.2.x

Marque os respectivos checkboxes, e clique em Finish. Depois de uma pequena consulta à internet, é só marcar o plugin e dependências e correr pro abraço.

Iniciando no QT, parte III – qmake e .pro

May 7, 2008 · Posted in C/C++, Qt · Comment 

Neste terceiro post sobre QT vamos falar do utilitário qmake e dos arquivos de projeto *.pro. Vamos entender para que serve o qmake e como configurar diferentes tipos de projetos.

O qmake

O qmake é um utilitário que acompanha o framework QT. Sua função é parsear um arquivo de projeto (*.pro) e gerar um Makefile já com as regras do moc, uic e opções do QT embutidas. Sem ele por exemplo, teríamos que chamar o moc explicitamente para criar os arquivos moc_* e passar explicitamente para o compilador e linker, as opções corretas para incluir o QT aos nossos projetos.

A documentação oficial sobre o qmake e arquivos de projeto, pode ser encontrada aqui.

Arquivos de projeto *.pro

Para criar um arquivo de projeto simples pela linha de comando, entramos no diretório do projeto e digitamos:

user@host$ qmake -project

Feito isso, será criado um arquivo com o mesmo nome do diretório corrente, seguido da extensão .pro. Caso já existam nesse diretório arquivos reconhecidos pelo qmake, como arquivos de códigos fontes (.h, .cpp), forms (.ui), etc, eles serão automaticamente adicionados ao arquivo de projeto.

Criado o arquivo .pro, ao executarmos qmake sem argumentos ele tentará parsear um arquivo .pro com o mesmo nome do diretório corrente. Você ainda pode especificar um arquivo de projeto alternativo como argumento para o qmake. Se tudo deu certo, um arquivo Makefile foi criado, e com um simples make, podemos compilar o projeto.

Os arquivos de projeto são arquivos de texto normais, contendo macros e diretivas que serão interpretadas pelo qmake para criar o Makefile. A lista completa de opções pode ser encontrada na documentação online. As mais comuns são:

TEMPLATE: Indicam o tipo de projeto. Use ‘app’ para proramas executáveis ou ‘lib’ para criar bibliotecas.

CONFIG: Adicionam opções diversas ao projeto. Entre elas, ‘debug’ para adicionar informações de depuração, ’staticlib’ em conjunto com o template ‘lib’, para que abiblioteca criada seja estática (.a no linux).

TARGET: O nome e a localização do alvo, ou seja, do aplicativo ou biblioteca.

MOC_DIR: Diretório onde serão grados os arquivos moc_*. Útil para não poluir o diretório de códigos fontes.

OBJECTS_DIR: Complementar à opção anterior, indica o diretório onde serão gerados os aquivos de código objeto (*.o).

INCLUDEPATH: Diretórios externos onde existem headers que serão utilizados no projeto, como headers de bibliotecas externas.

DEPENDPATH: Diretórios de códigos fontes externos que serão utilizados pelo projeto.

HEADERS: Os arquivos de cabeçalho do projeto (*.h).

FORMS: arquivos de interface gerados com o QtDesigner (*.ui).

SOURCES: Os arquivos de implementação de código fonte do projeto (*.cpp).

LIBS: Bibliotecas externas utilizadas pelo projeto. -L indica o path para a biblioteca, e -l diz o nome da biblioteca.

QT: Módulos do QT que devem ser adicionados/excluidos do projeto. Se for passado ‘QT =’ (QT igual vazio), nenhum módulo QT será utilizado no projeto.

SUBDIRS: Utilizado em conjunto com o template ’subdirs’, indica os subdiretórios ons o qmake deve procurar por outros arquivos de projeto.

Com isso em mãos podemos criar algums projetos simples.

Um aplicativo simples:

# O primeiro caracter desta linha cria um comentário
# O nome deste arquivo é 'app.pro'
TEMPLATE     =  app            # Nosso template é um aplicativo chamado
TARGET       =  bin/myapp.bin  # myapp.bin, dentro do dir ./bin
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
HEADERS      += myclass.h      # Header da classe MyClass
SOURCES      += main.cpp      # Utilize  para organizar os arquivos em
                myclass.cpp    # várias linhas.

Uma biblioteca simples:

# O nome deste arquivo é 'lib.pro'
TEMPLATE     =  lib            # Nosso template é uma biblioteca
CONFIG       += dll            # dinâmica, chamada
TARGET       =  lib/mylib      # mylib, dentro do dir ./lib
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
HEADERS      += myclass.h      # Header da classe MyClass
SOURCES      += myclass.cpp    # Implementação da classe MyClass

Um aplicativo que usa uma biblioteca externa:

# O nome deste arquivo é 'mixed.pro'
TEMPLATE     =  app            # Nosso template é um aplicativo chamado
TARGET       =  bin/myapp.lkd  # myapp.lkd, dentro do dir ./bin
MOC_DIR      =  tmp/moc        # Diretório para mocs, opcional
OBJECTS_DIR  =  tmp/obj        # Diretório para código objeto, opcional
INCLUDEPATH  += .              # Dir onde estão os Headers da lib externa
SOURCES      += main.cpp       # Implementação do aplicativo.

Diretórios aninhados:

Para utilizarmos diretórios aninhados, precisamos de um arquivo de projeto no diretório atual, e outro no subdiretório.

No diretório atual, utilizamos as opções:

TEMPLATE = subdirs
SUBDIRS  = [lista de subdirs para serem compilados em sequência]

Caso seja fornecido apenas o nome do diretório, ele precisa conter uma arquivo de projeto com o seu próprio nome. É possível no entando, informar diretamente na lista de subdiretórios, um diretório seguido por um arquivo de projeto com quaquer nome.

O código fonte completo dos exemplos abordados neste tutorial pode ser encontrado aqui.

Concluindo

O qmake é uma ferramenta simples e muito poderosa que facilita enormemente a vida de quem tem de gerenciar aŕvores de projeto complexas, mesmo que o projeto não utilize QT (basta acrescentar no final a opção ‘QT = ‘). Adicione a isso o fato de os arquivos de projeto terem uma sintaxe bastante simples e até certo ponto intuitiva. Se você precisar de ajustes mais finos, a documentação online lhe dará uma dezena de opções para se divertir.

Com QT ou sem QT, o qmake é sempre uma boa opção para gerenciamento de árvore de build.

Iniciando no QT, parte II – Sinais, Slots e Timers

February 1, 2008 · Posted in C/C++, Qt · 6 Comments 

Neste post vou seguindo com a brincadeira de fazer pequenos tutoriais sobre o QT para iniciantes. Desta vez vou mostrar como criar nossos próprios sinais e slots e como manipular um timer.

O código fonte pode ser encontrado neste link.

O programa de exemplo será um cronômetro que vai utilizar a classe QTimer, uma das principais classes do QT, e vai mostrar o tempo utilizando um display estilo LCD de calculadora com a classe QLCDNumber.

QT e os Timers

O suporte mais básico a timers no QT é disponibilizado pela classe QObject, que fornece os métodos QObject::startTimer() e QObject::killTimer(). O primeiro método retorna um ID único de timer e o segundo finaliza o timer através desse ID.

Para que isso funcione, no entanto, o trecho de código que utiliza esse mecanismo precisa estar dentro de um “event loop”. A partir do momento que o timer for iniciado ele de tempos em tempos (timeout) faz com que a aplicação dispate um QTimerEvent, que interrompe o fluxo normal do programa até que o evento seja processado.

O tempo máximo de timeout não é delimitado, sendo possível criar timers com timeout de anos, porém, o tempo mínimo pode variar de sistema para sistema. No windows Vista o timer mínimo é de 10 ms enquanto no Linux 2.6.x isso é configurável (o default é 4 ms). O QT vai tentar entregar todos os eventos, conforme pedidos, mas caso o sistema não permita ele vai descartar os “excedentes”.

Timers também podem ser utilizados em threads, porém deve-se respeitar a condição de estar dentro de um event loop. Threads merecem um artigo à parte, como sempre…

A classe QTimer implementa timers de mais alto nível, possibilitando algumas funcionalidades. Uma delas é o QTimer::singleShot(), que dispara um evento uma única vez.

Leia a extensa e repetitiva documentação, é chato, mas acredite, vai te poupar de muita QDorDeCabeca…

Mais sobre Sinais e Slots

Sinais e slots são utilizados para comunicação entre objetos, sendo uma das peças fundamentais do QT. Nesse sistema ao invés de se implementar callbacks para tratar eventos, utiliza-se o conceito de conectar sinais a slots, tornando a programação mais intuitiva.

Os sinais e slots são métodos de classe que são tratados pelo Meta-Object Compiler (moc) antes do código fonte ser compilado. Eles têm declarações especiais que não são parte do padrão C++, por isso o moc fazum parsing e gera o código fonte compilável.

Ao executar o comando moc sobre qualquer classe que implemente sinais e slots, uma certa quantidade de código fonte é gerada. Com o uso do programa qmake, a chamada ao programa moc fica a cargo do Makefile, tornando-a transparente para o programador.

Para que uma classe possa implementar sinais e slots, ela precisa ter acesso ao Meta-Object System. Por isso ela deve herdar da classe QObject ou suas subclasses, e precisa ter a macro Q_OBJECT na sua área de declaração privada. Terminados os preparativos, sinais são declarados em uma seção “signals:” e slots em seções “[public|protected|private] slots:“.

Um slot, depois de declarado, é então definido como qualquer outro método comum, podendo ser explicitamente chamado como qualquer outro. Já os sinais, são um pouco mais delicados, e são apenas declarados e jamais definidos pelo programador. A razão? Esta mensagem no final da tentativa de compilação:

tmp/moc_mydisplay.o: In function `MyDisplay::signalPlay()':
~/stopwatch/tmp/moc_mydisplay.cpp:89: multiple definition of `MyDisplay::signalPlay()'
tmp/mydisplay.o:~/stopwatch/mydisplay.cpp:169: first defined here
collect2: ld returned 1 exit status
make: ** [stopwatch] Erro 1

Dentro do arquivo moc_mydisplay.cpp é gerado o seguinte código:

// SIGNAL 0
void MyDisplay::signalPlay()
{
    QMetaObject::activate(this, &staticMetaObject, 0, 0);
}

Sim, o moc gera a definição dos sinais com chamadas a meta-métodos do QT. Portanto, definir o corpo de um sinal é ilegal no QT e vai gerar um erro de compilação por redefinição de método. Deixe que o moc cuida da implementação do sinal pra você.

Conecte-se

Não adianta apenas definir sinais e slots. É preciso definir como eles vão interagir. A maneira de fazer isso é conectando-os através do método connect() presente em todo herdeiro de QObject. Isso fará o moc implementar o sinal de forma que uma chamada a ele resulte numa chamada ao(s) slot(s) conectado(s), com os mesmos parâmetros.

Dependendo da forma de conexão, um sinal pode até retornar o mesmo valor que o último slot chamado retornar. Note que isso não é muito seguro para sinais conectados a vários slots, pois, nada garante a ordem de chamada.

Um dado sinal pode ser conectado a um segundo sinal diretamente. Um emit() no primeiro sinal é então equivalente a um emit() no segundo sinal, visto que após a etapa de criação dos mocs, a chamada ao primeiro resulta na chamada ao segundo, que resulta na chamada de outro método conectado e assim sucessivamente.

Formas de conexão

Existem basicamente três formas de conectar sinais e slots e se não usadas adequadamente podem ser fonte de bugs terrivelmente difíceis de se descobrir. Essas formas de conexão são passadas como parâmetros extras para o método connect(). São elas:

Conexão Direta: O slot é chamado imediatamente ao sinal ter sido emitido, na thread onde o sinal foi emitido. Isso funciona como uma chamada direta ao slot.

Conexão Enfileirada: O sinal é emitido e a chamada ao slot vai para uma lista interna do QT, e o sinal retorna imediatamente, independentemente do slot ter sido chamado ou não. O evente loop vai então processando essa lista e só mais tarde o slot será invocado na thread onde o objeto do slot reside.

Conexão Automática: Este é o tipo default, utilizado quando não se especifica o tipo de conexão. É uma das “Sementes do Mal”, pois apresenta dois comportamentos distintos: se o sinal e o slot residirem em uma mesma thread, funciona como conexão direta, mas caso contrário, funciona como conexão enfileirada.

Conexão enfileirada Bloqueante: Ué!? não eram só três? Well, essa é uma das vantagens de ler esse artigo: te salva de documentação inconsitente! Essa conexão é semelhante à conexão enfileirada, exceto pelo fato de que a thread do sinal fica bloqueada até o sinal ser devidamente executado. Note que só deve se utilizada com muito cuidado, e para sinais e slots em threads diferentes. O mal uso disso poderá causar deadlocks. Você vai saber quando vir algo assim:

user@host:~/stopwatch$ ./stopwatch
Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is
QPushButton(0x807e2d8), receiver is MyDisplay(0x8076ac0)

Para sanar as dúvidas a respeito do que significa a expressão “thread onde um objeto reside” consulte a documentação do QT arespeito de threads.

Tome sempre muito cuidado com sinais, slots, timers e threads. Esses são recursos fundamentais do QT, mas o seu mal uso pode acarretar em bugs extremamente difíceis de serem localizados. Consulte toda a documentação à respeito.

Descendo à fonte

O nosso exemplo se compões de duas classes MyDisplay e StopWatch e um arquivo main comum. O main apenas cria uma QApplication e um objeto StopWatch. A classe MyDisplay cuida da parte visual do programa, consistindo em um diálogo com botões e um display no estilo LCD. A classe StopWatch vai criar um widget de display e implementar o timer. Note que essa arquitetura não é a mais elegente, sendo mais interessante fazer a classe StopWatch herdar de MyDisplay. Porém, isso estragaria a brincadeira praticamente eliminando a necessidade do uso de sinais e slots.

O uso de sinais e slots fica mais claro quando queremos que objetos se comuniquem com o seu exterior. Em geral, as classes não conhecem o que há fora delas. Passar ponteiros de objetos externos para dentro delas, fere o encapsulamento e torna o código menos genérico. Em nosso exemplo, o uso mais interessante para sinais e slots é para o objeto myDisplay (interno a StopWatch) comunicar eventos de/para o objeto stopwatch (externo a Mydisplay), sem que para isso se perca em generalidade ou encapsulamento.

Com a ajuda do QT Designer (num outro artigo eu falarei dele), criei o diálogo, botões e LCD. Depois simplifiquei o código para se adequar aos nossos propósitos. Como myDisplay herda de QDialog e este herda indiretamente de QObject, para usarmos sinais e slots bastou acrescentar a macro Q_OBJECT na seção privada da declaração da classe. Após isso, acrescentei entre outros auxiliares, os métodos:

public slots:
    void slotDisplayValue( long );
 
private slots:
    void slotPlay();
    void slotStop();
 
signals:
    void signalPlay();
    void signalPause();
    void signalStop();
    void signalReset();

Os sinais comunicarão ao exterior os eventos relacionados aos cliques nos botões. Já os slots privados farão um pré-processamento interno, enquanto o público receberá o valor de ticks vindo do timer externo. Dessa forma a classe envia informações ao exterior e recebe informações de fora, sem conhecer o que está do outro lado. A boa e velha interface por contrato.

public slots:
    void slotPlay();
    void slotPause();
    void slotStop();
    void slotReset();
 
private slots:
    void slotTick();
 
signals:
    void signalTicks( long );

Análoga e inversamente a classe StopWatch implementa os slots que receberão os sinais da classe MyDisplay, e declara o sinal que enviará informações para o seu slot público.

Ela também implementa um QTimer que vai disparar a cada 10 milissegundos (para que os nosso amigos lerdinhos do Vista possam acompanhar!), incrementando um contador. Esse contador é enviado de volta ao display que o formata e exibe.

O cronômetro pode ser parado, pausado e reiniciado a qualquer momento, clicando nos botões correspondentes.

Os Finalmentes

O exemplo em si não tem muito código, mas ilustra o uso mais básico de timers e a construção de sinais e slots customizados. Uma atenção toda especial, no entanto, deve ser dada aos detalhes que envolvem o uso desses recursos, pois eles podem se tornar armadilhas letais.

Links importantes

Sinais e Slots
QTimer
Threads no QT
Documentação on-line do QT

Iniciando no QT, parte I

January 26, 2008 · Posted in C/C++, Qt · 2 Comments 

Atendendo a pedidos, resolvi fazer breve artigo mostrando com começar a se aventurar com o QT.

Em apanas 20 linhas, contando com as de espaço (sim, o espaço é seu amigo, nunca o abandone) e os cabeçalhos extras, podemos fazer um “Hello World” gráfico, que já utiliza as principais funcionalidades do framework.

Como eu não tenho como testar em outras plataformas, estou assumindo que a plataforma utilizada é algum *nix, e que as bibliotecas de desenvolvimento do QT já estão instaladas, bem como as ferramentas de compilação padrão. Caso o QT não esteja instalado, é hora de dar uma olhada neste link.

O código fonte completo do exemplo pode ser baixado aqui, mas se preferir fazer tudo na mão, crie um diretório qthello num lugar qualquer e dentro dele um arquivo chamado main.cpp com o seguinte conteúdo:

 

01 #include <qapplication>
02 #include <qwidget>
03 #include <qpushButton>
04
05 int main( int argc , char** argv )
06 {
07     QApplication app( argc , argv );
08     QWidget window;
09     QPushButton button( "Hello World!" , &window );
10
11     window.resize( 300 , 200 );
12     button.setGeometry( 100 , 85 , 100 , 30 );
13
14     QObject::connect( &button , SIGNAL( clicked() ) ,
                         &app    , SLOT( quit() ) );
15
16     window.show();
17     return app.exec();
19 }
20

Agora acesse através de um terminal o diretório criado e digite qmake -project.

Se tudo estiver instalado direitinho, um arquivo chamado qthello.pro deve ter sido criado. Se não houver nenhum qthello.pro no diretório, então revise os passo anteriores.

Com tudo acertado digite no terminal qmake && make. Isso irá compilar o exemplo e se tudo deu certo, um arquivo executável chamado qthello foi criado.

Execute-o com ./qthello e uma janela com um botão deve aparecer.

Ok, vamos agora à parte divertida: o código fonte.

As três primeiras linhas apenas incluem os cabeçalhos referentes às classes do QT que vamos utilizar.

A linha 7 declara uma aplicação QT e passa pra ela os parâmetros recebidos do shell pelo programa. A classe QApplication gerencia o fluxo principal do programa e suas configurações. Ela contém o loop principal de eventos, onde todos os eventos vindos tanto da interface quanto de outras fontes são processados e despachados. Ela também gerencia a inicialização e finalização do programa entre outras coisinhas.

A linha 8 declara um widget que será o nosso elemento gráfico principal.

A linha 9 declara um botão com o texto “Hello World!” e torna o mesmo um “filho” do nosso widget principal. Isso fará com que o botão apareca dentro do widget principal.

As linhas 11 e 12 configuram os componentes quanto à forma e posicionamento.

A linha 14 mostra uma das principais funcionalidades do framework, o Sistema Sinal-Slot.

Sinais e slots são utilizados para realizar a comunicação entre os objetos. Através desse sistema um objeto pode disparar (emit) um evento (sinal) que pode ser capturado e processado por um (ou mais) objeto(s). Sinais e slots são métodos especiais da classe. Basicamente, a emissão de um sinal é traduzida para a chamada ao slot correspondente através do moc (meta-object compiler), que pré-processa o código fonte antes da compilação. Para cada classe que implementar sinais e slots, é gerado um arquivo chamado moc_nomedaclasse.cpp, que depois é compilado junto com o restante do código. Isso tudo é feito de forma transparente e o programador, em geral, não precisa se preocupar com esses detalhes. Em termos práticos, emitir um sinal signigica que o slot associado a ele através de um connect será chamado de alguma forma, com os mesmos parâmetros passados ao sinal. Maiores informações podem ser conferidas na documentação on-line.

No nosso exemplo, o connect associa o sinal clicked() do objeto botão, com o slot quit() do objeto aplicação. Isso significa que ao clicar no botão, um sinal é disparado e posteriormente capturado pelo objeto aplicação, que o processa e então finaliza.

Na linha 16, chamamos o método show() do widget, para que ele se torne visível.

Na linha 17, finalmente passamos o controle do programa para o QT. O método exec() vai disparar o loop principal e todos os seus mecanismos, e só retornará quando a aplicação terminar, no nosso caso, quando app.quit() for chamado através do sistema sinal-slot.

Este simples “Hello World” pode parecer bobo, mas é suficiente para demonstrar algumas das principais funcionalidades do QT, como o main loop, a criação de objetos gráficos e o uso de sinais e slots.

Num próximo post eu vou mostrar com criar nossos próprios sinais e slots e o uso de temporizadores.

Referências:
QApplication
QWidget
QPushButton
Sinais e Slots

Construindo um gerenciador de interface simples para sistemas multi-janelas com QT

January 24, 2008 · Posted in C/C++, Qt · 6 Comments 

Em diversas ocasiões acabei tendo que dar manutenção em interfaces de sistemas com muitas telas, sejam elas gráficas ou em modo texto. Muitas vezes, a quantidade de telas foi crescendo junto com o sistema sem um planejamento prévio, o que sempre levava a um emaranhado de chamadas obscuras, #ifndefs, switches, entre outros monstrinhos.

A primeira vez que eu me deparei com um problema do gênero, foi quando eu estava fazendo a primeira versão de um jogo parecido com o saudoso Elifoot para uma feira no CEFETES. Na época eu me deparei com um conjunto de funções onde elas chamavam umas às outras e só retornavam quando acontecia o gol. Resultado: haja pilha!!! (Não se preocupem, na nova versão isso foi corrigido!)

No caso das interfaces, com várias janelas, e em sistemas onde o planejamento ocorre on-the-fly, é comum termos janelas que chamam outras janelas que fecham as janelas anteriores e assim por diante, deixando pra trás um rastro de destruição e terror. Acredite, você não vai querer nunca dar manutenção num treco desses.

Do pó ao pó…

No meu caso, resovi o problema criando um gerenciador de interface. Um objeto centralizador que controla a criação e destruição dos objetos de interface. Cada objeto, ao terminar suas tarefas, sinaliza para o gerenciador e ele cuida de limpar a sujeira. Assim, um objeto sempre nasce, realiza suas tarefas e morre sem passar o controle do programa para frente, ele sempre volta para o gerenciador.

Note que eu não disse que o gerenciador vai destruir o objeto, mas sim que ele vai fazer a limpeza. Cada objeto tem que ser construído de forma a avisar ao gerenciador toda vez que ele terminar, seja com sucesso ou falha. Muitas vezes o próprio objeto é capaz de “se matar” sozinho e isso é muito útil, tirando um pouco do trabalho do gerenciador, mas não o controle.

Before starting

Eu não tenho a pretensão que esse simples post cubra todos os recursos do QT ou todos os aspectos do gerenciamento de interfaces. Eu apenas vou falar de como EU resolvi um problema pelo qual eu passei. Esse post também é uma homenagem a um amigo paulista-carioca que já teve que descascar um abacaxi desses!

Mão na massa

Eu sou, na maioria das vezes, um programador bottom-up, porém um analista top-down. Isso pode parecer confuso, mas não há razão para pânico, ou quase…

Os objetos precisam trocar mensagens. Para isso o sistema de sinal-slot do QT vai ser de grande ajuda. Os objetos vão mandar sinais ( emit(signal) ) e o gerenciador vai escutá-los ( slots() ).

Por incrível que pareça, o construtor do gerenciador, daqui pra frente chamado de UIM, não possui uma linha de código sequer. Ele apenas inicializa os ponteiro com 0, apenas por paranóia (ou não)…

O UIM implementa slots para criação dos objetos e limpeza da bagunça. Ao criar um objeto, o UIM conecta esse objeto ao seu respectivo slot de limpeza. Quando o objeto morre, ele ativa o slot de limpeza no UIM. Os slots de criação serão ligados aos menus do objeto gráfico principal, neste exemplo, uma janela.

Os outros objetos são janelas menores, dialogs, sendo um para configuração do aplicativo, e dois pra falar de si mesmo (about), afinal, marketing é importante…

Todos os objetos gráficos eu cliquei e arrastei no Qt Designer e depois ajustei pra ficar do meu jeito. Nada de ficar alinhando pixel na mão, afinal não é uma obra de arte, é só uma prova de conceito.

Os Objetos Gráficos

A tela de configuração é um diálogo comum com um combo-box onde está uma lista de parâmetros a serem passados para o widget principal. Os detalhes que eu acrescentei ao código gerado pelo Qt Designer foram um destrutor, e um par sinal-slot para enviar os dados selecionados de volta para o gerenciador. Eu não me preocupei em guardar o estado da configuração atual. Isso é papo pra outro post.

Os abouts ficaram basicamente com o mesmo código, acrescidos de destrutores.

A janela principal, possui um menu que chama as outras janelas e um widget maluco cheio de quadrados que ficam piscando. A quantidade de quadrados é a informação que será alterada pela janela de configuração.

The Magic QT

Todos o objetos gráficos utilizados, são também QWidgets, pois suas classes herdam da classe QWidget, e eu utilizo em cada construtor o seguinte método herdado:

this->setAttribute( Qt::WA_DeleteOnClose , true );

De acordo com a documentação on-line, isto diz para o QT que ele deve deletar o widget quando ele for fechado. Do contrário, ele será apenas escondido.

“…When a widget accepts the close event, it is hidden…”

“…If you want the widget to be deleted when it is closed, create it with the Qt::WA_DeleteOnClose flag. This is very useful for independent top-level windows in a multi-window application…”

As rotinas que fazem os objetos terminarem com falha, como clicar em Cancelar ou Fechar, estão diretamente conectadas aos seus próprios slots close(). Isso dispara uma série de eventos que culminam com a destruição do objeto.

As rotinas que fazem o objeto terminar com sucesso, como clicar em ok e enviar os dados, foram direcionadas a voltar para o UIM, para que ele possa roteá-las. Depois de pegar os dados, o UIM chama diretamente o slot close do objeto().

Quando um objeto é destruído, ele emite seu último suspiro, digo, sinal que é o destroyed(). Literalmente ele grita “FUI!!!”.

Do “lado de fora” do objeto, o UIM está ouvindo, conectando este sinal à rotina de limpeza, e TADÃM!!!! prontinho.

No exemplo que eu usei, a quantidade de quadrados na tela chega de um objeto MyConf, passa pelo UIM e é roteada para a MyApplication. Esta por sua vez, repassa para o widget maluco interno.

Pra ficar ainda mais interessante, enquanto uma janela menor está aberta, o widget pára de piscar, retornando quando a janelinha for fechada. Parece bobo, mas isso foi só pra mostrar que o UIM tem controle total sobreo o que acontece em cada janela, estando ela em foco, ou não.

Conclusão

O framework QT é imenso e fornece uma gama enorme funcionalidades. Uma delas é poder controlar como os objetos são destruídos. Utilizando isso junto com um pouco de planejamento e aliado ao sistema de sinal-slot podemos construir uma interface bastante complexa, sem transformá-la num Tarrasque na hora de dar manutenção.

Fonte:

http://doc.trolltech.com

P.S.: O Código fonte completo do exemplo encontra-se sob a GPL e pode ser baixado por este link. Um dia eu ponho comentários nele…

BUGFIX

O infeliz do inseto se escondia na falta de inicialização de propriedades no construtor da classe MySquares. O bichinho, apesar de pequeno era venenoso, visto que causava uma ‘Segmentation Fault’. Ao iniciar o programa, se eu fosse em qualquer about e o fechasse, isso disparava uma sequência de eventos que partia do pressuposto que a tela já estava devidamente configurada. O código corrigido encontra-se no link acima, e lembre-se:

“Sempre inicialize suas variáveis”