segunda-feira, 28 de dezembro de 2009

IplImage e FXImage: revisitado

Anteriormente, falei aqui como utilizar uma imagem do OpenCV (IplImage) e uma do FOX (FXImage), ambas compartilhando o vetor de pixels. Confira aqui.

Pois bem. Fazendo daquela maneira, há certos problemas: ambos objetos devem permanecer vivos, e o dono dos pixels tem que ser a IplImage.

Isto pode não ser desejado. Por exemplo, vou citar o caso que aconteceu comigo e tive que matar um pouco a cabeça para resolver.

Muitas imagens que eu exibo (com o FOX Toolkit) precisam antes passar por algum processamento (com o OpenCV). Depois disso, eu não preciso mais da IplImage, apenas dos pixels.

Entretanto, seguir os passos explicados no outro tópico levava a um problema seriíssimo, que será explicado no decorrer deste tópico.

Compartilhando pixels: antigamente

Conforme eu havia explicado, criava-se primeiro a IplImage e depois a FXImage, utilizando o vetor de pixels, mais ou menos assim:


IplImage *iplImage = cvCreateImage(cvSize(200, 200), IPL_DEPTH_8U, 4);

FXImage *fxImage = new FXImage(getApp(), iplImage->imageData,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    iplImage->width, iplImage->height);


Lembrando que era necessário um cast horroroso com reinterpret_cast para transformar os pixels da IplImage de char* para FXColor.

Com isso, eu tenho que minha IplImage "doou" seus pixels para a FXImage, que tomou conta deles. Assim, a IplImage poderia ser dispensada (na verdade, somente o cabeçalho da imagem; o OpenCV permite isso) que a FXImage, quando for liberada, libera também o vetor de pixels.

O problema

Isso causa um problema que eu demorei muito para descobrir. E, depois de descoberto, vê-se que é uma coisa tão simples... é sempre um detalhezinho que cega a gente.

O grande problema é que, como o vetor de pixels é alocado pela IplImage, ele é alocado usando malloc. A FXImage, quando tenta liberar, tenta com delete. Aí não dá certo...

A "solução"

Eu criava as imagens independentemente, cada uma com seu vetor de pixels. Depois de processada, eu fazia uma chamada a memcpy para copiar os pixels. Uma tristeza considerando o contexto.

Compartilhando pixels: a solução de fato

Depois de quase desistir da idéia, eu descobri (dia desses...) o problema. E logo cheguei a uma solução elegante (e óbvia).

Basta criar primeiro a FXImage, que vai apenas "emprestar" os pixels para a IplImage. Assim:


FXImage *fxImage = new FXImage(getApp(), 0,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    200, 200);


Percebam que o segundo parâmetro, que seria o vetor de pixels, é nulo. Como os pixels são de responsabilidade da FXImage, ela mesma os cria (isso é informado pela opção IMAGE_OWNED).

É necessário também manter os pixels no lado do cliente (opção IMAGE_KEEP). Sem ela, os pixels são liberados depois da imagem criada, impossibilitando o compartilhamento.

Pois bem, agora vamos "emprestar" os pixels da FXImage para a IplImage:


IplImage *iplImage = cvCreateImageHeader(cvSize(200, 200),
    IPL_DEPTH_8U, 4);
  cvSetData(iplImage, fxImage->getData(), CV_AUTOSTEP);


cvCreateImageHeader() aloca todas as informações de uma imagem, com exceção do vetor de pixels. Com isso, a IplImage, que é temporária, apenas pega emprestado os pixels para trabalhar sobre eles. Ao terminar o processamento, podemos liberar o cabeçalho:


cvReleaseImageHeader(&iplImage);


Uma grande vantagem dessa abordagem é que não precisa nem converter a imagem, conforme eu havia feito anteriormente. A imagem já é criada no formato da FXImage.

Conclusão

Conforme havia dito, é uma coisa tão simples e óbvia que fica invisível.

Espero que essa informação seja útil para quem for usar essas duas bibliotecas juntas.

Abraço e até a próxima.

quarta-feira, 23 de dezembro de 2009

Submenus

Dando continuidade à nossa série sobre Menus, falarei agora sobre como adicionar submenus. Vamos lá.

Submenus são menus como quaisquer outros

Isso mesmo. O processo de criar um submenu é praticamente o mesmo de criar um menu "normal".

Para exemplificar o processo, vamos criar um menu "Exibir", com um submenu "Zoom". Neste caso, temos dois painéis, que serão declarados da seguinte maneira:

FXMenuPane *_viewMenu;
FXMenuPane *_zoomMenu;

Lembrando que eles devem estar no escopo de classe, pois precisam ser liberados manualmente.

Percebam que o submenu "Zoom" nada mais é que um FXMenuPanel como todos os outros que já vimos até aqui. Agora vamos criá-los, novamente recuando o código para indicar a hierarquia.

_viewMenu = new FXMenuPane(this);
  _zoomMenu = new FXMenuPane(this);
    new FXMenuCommand(_zoomMenu, "&Aumentar\tCtrl++");
    new FXMenuCommand(_zoomMenu, "&Normal\tCtrl+0");
    new FXMenuCommand(_zoomMenu, "&Diminuir\tCtrl+-");

    new FXMenuSeparator(_zoomMenu);

    new FXMenuCommand(_zoomMenu, "A&justar\tHome");
  new FXMenuCascade(_viewMenu, "&Zoom", 0, _zoomMenu);
new FXMenuTitle(menuBar, "&Exibir", 0, _viewMenu);

Explicando...

Na primeira linha, aloco o menu "Exibir".

Na segunda, aloco o (sub)menu "Zoom". Chamando atenção para o recuo, indicando que _zoomMenu será posicionado "dentro" de _viewMenu;

Nas linhas seguintes, preencho com alguns comandos e um separador. Nada além do trivial com recuo.

Em seguida, criamos a entrada no menu "Exibir" que vai expandir o menu "Zoom". Repetindo:

new FXMenuCascade(_viewMenu, "&Zoom", 0, _zoomMenu);

Os parâmetros são os mesmos de FXMenuTitle (comparem com a última linha):
  • O FXComposite onde ficará localizado o submenu. Neste caso, adicionamos ao painel "Exibir"
  • O título que vai aparecer no painel, com direito a mnemônico
  • Ícone
  • O painel que será exibido ao mover o mouse sobre o título.

Liberando

Como todo bom painel de menu, deve ser liberado manualmente:


delete _viewMenu;
delete _zoomMenu;

Só uma observação: já tratei esse assunto aqui, então é só um lembrete. Quando eu falo em deletar manualmente, é porque estou usando o FOX 1.6. Na versão atual de desenvolvimento (1.7), existe uma classe chamada FXAutoPtr, que é um "ponteiro inteligente". Resumindo a história, ele guarda um ponteiro e automaticamente o libera quando sai de escopo. Veja a descrição aqui (na verdade, uma dúvida enviada à lista de discussão do FOX que eu traduzi e postei aqui).

Resultado

Eis o resultado do nosso submenu:


segunda-feira, 14 de dezembro de 2009

Menus

Neste tópico, ensinarei como adicionar uma barra de menus (com menus, obviamente) ao programa. Neste momento, colocarei menus simples, apenas com comandos simples. Há outros componentes que podem ser adicionados aos menus, mas serão tratados em tópicos futuros.

Vamos lá.


Barra de menus

Tudo começa com a barra de menus. Naturalmente, é lá que ficam os menus. E, naturalmente, fica no topo da janela:



FXMenuBar *menuBar = new FXMenuBar(this, LAYOUT_FILL_X|FRAME_RAISED);


Percebam que ela pode ser criada localmente, o próprio FOX se encarrega de liberá-la depois. Além disso, lembrem-se que a localização padrão é no topo da janela, portanto não precisei passar LAYOUT_SIDE_TOP.

Isso cria o espaço ideal para os menus. Aqui, criarei apenas dois, "Arquivo" e "Editar", apenas para exemplificar. Não é um processo complicado. Pelo contrário, é bem simples.


Adicionando menus

Os menus precisam ser declarados com escopo de classe. Então, na declaração da classe, temos:



protected:
  FXMenuPane *_fileMenu;
  FXMenuPane *_editMenu;


Agora, vamos criar e preencher os menus. Neste primeiro exemplo, do menu "Arquivo", vou colocar e explicar linha por linha. No segundo, o menu "Editar", vou colocar tudo de uma vez só para explicar a forma como eu gosto de criar os menus.

Menu Arquivo




_fileMenu = new FXMenuPane(this);


Cria o painel onde ficarão os itens do menu. Os dois parâmetros do construtor são a janela pai e as opções, que não é necessário informar.




new FXMenuCommand(_fileMenu, "&Carregar imagem...", 0, 0, 0);


Cria um comando de menu. Os principais parâmetros são:

  • Menu onde vai aparecer. Na verdade, pode ficar em um FXComposite qualquer (e, de fato, eu coloquei um sem querer na própria janela, ficou algo bizarro). Como estamos criando menus, é bom que fique em um FXMenuPanel. Neste caso, no menu "Arquivo".
  • Texto do comando. O caractere & indica que a próxima letra ficará sublinhada, para acesso através de Alt+. Neste caso, Alt+C acionará esse comando. É meio Uíndous, mas muito melhor que em Java (um setMnemonic() horroroso).
  • Ícone. FXMenuCommands podem ter um ícone associado, indique aqui. Já falei sobre ícones aqui.
  • Alvo do evento.
  • ID da mensagem. A série sobre Tratamento de Eventos explica essa parte referente a esses dois últimos parâmetros.

O último parâmetro são as opções, novamente desnecessário informar.

E caso não tenha ficado claro pelo nome da classe: FXMenuCommands lançam eventos do tipo SEL_COMMAND. Isso significa que no mapa de mensagens do tratador (seja a própria janela ou seja lá quem for) vai aparecer algo como:



FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_LOAD_IMAGE,
  FoxTutorialMainWindow::onCmdLoadImage),



Continuando:



new FXMenuSeparator(_fileMenu);


Apenas um separador. Desenha uma linha para separar os comandos em seções.




new FXMenuCommand(_fileMenu, "&Sair\tCtrl+Q\tEncerrar o aplicativo",
        0, getApp(), FXApp::ID_QUIT);


Mais um comando, para fechar o aplicativo. Desta vez fiz com que esse comando lançasse um evento para exemplificar. Já falei sobre como fechar o aplicativo através de um comando aqui.

A única observação adicional é quanto ao texto do comando. Assim como o texto dos botões, este também é dividido em três seções, separadas pelo caractere de tabulação:

  • A primeira é o texto que será exibido. Natural e praticamente obrigatório.
  • A segunda são as teclas de atalho (aceleradores). Neste caso, coloquei Ctrl+Q para fechar o aplicativo. Opcional.
  • A terceira é o texto que aparecerá na barra de status. Falaremos dela no futuro. Opcional.

Bom, isso preenche o nosso menu "Arquivo". Vamos, agora, adicioná-lo à barra de menus (ou vocês achavam que isso já tinha sido feito?):



new FXMenuTitle(menuBar, "&Arquivo", 0, _fileMenu);


Aqui informamos:

  • A barra de menus onde vai ficar. Novamente, pode ficar em um FXComposite qualquer. Mas não há a menor necessidade de uma bizarrice dessas.
  • O título que vai aparecer. Não preciso mais dizer para que serve o &, preciso?
  • Ícone. Eu, particularmente, nunca vi em nenhum programa (escrito em FOX ou não) um menu com ícone. Mas o FOX Toolkit te dá essa liberdade.
  • O painel que será exibido ao acionar o menu.

Nosso menu Arquivo está pronto. Observemos, agora, o menu Editar.


Menu Editar




_editMenu = new FXMenuPane(this);
  new FXMenuCommand(_editMenu, "&Preferências...", 0, 0, 0);
new FXMenuTitle(menuBar, "&Editar", 0, _editMenu);


Bom, esse aqui é só para mostrar a maneira como eu normalmente crio menus (e, de fato, qualquer objeto composto por uma hierarquia).

Na primeira linha eu sigo o recuo normal do código. Esta é, digamos, a linha de criação (cunhei esse termo agora...).

Em seguida, vêm as linhas de preenchimento (de novo...). Nelas, coloco os objetos que vão "dentro" do objeto principal, com um recuo a mais exatamente para deixar claro essa hierarquia.

No final, volto ao recuo da linha de criação. É a linha de realização ( >D ), pois nela dou um sentido ao meu objeto (afinal, não faz sentido criar um painel sem acesso a ele).

Desenvolvi esse estilo quando comecei a trabalhar com o OpenSceneGraph (não confundam com OpenSG) e precisava definir as características de um modelo, ficava mais ou menos assim:
osg::ref_ptr geode = new osg::Geode();
  osg::Geometry *geometry = new osg::Geometry();
    osg::Vec3Array *vertexArray = new osg::Vec3Array();
      /* Preenche a lista de vértices */
    geometry->setVertexArray(vertexArray);

    osg::DrawElementsUInt *primitiveSet = new osg::DrawElementsUInt(GL_POINTS);
      /* Adiciona as primitivas */
    geometry->addPrimitiveSet(primitiveSet);
  geode->addDrawable(geometry);


Recuar os objetos mais internos me ajudou a entender melhor a hierarquia dos objetos do OpenSceneGraph e acabei aplicando essa técnica ao programar com o FOX Toolkit. Não vou explicar porque está fora do escopo do tópico (e do blog como um todo...).

Bom, é apenas uma sugestão.


Liberando os menus

FXMenuPanels são objetos compartilháveis, assim como ícones. Isso serve, por exemplo, para exibir o mesmo painel pelo menu ou clicando com o botão direito sobre algum objeto.

Sendo assim, devem ser liberados "na mãozona":



delete _fileMenu;
delete _editMenu;



Resultado

Depois disso tudo, o menu Arquivo fica conforme a imagem a seguir. Em destaque, a barra de status mostrando o texto do comando de sair.





Considerações finais

Notem o texto dos comandos de "Carregar imagem..." (menu Arquivo) e "Preferências..." (menu Editar). Eles terminam em reticências.

Terminar um texto de exibição (tanto de comandos de menu como também de botões) com reticências informa imediatamente ao usuário que uma nova janela se abrirá, onde ele terá que executar mais ações.


Outra observação importante é onde posicionar o código para criação dos menus. No início, eu colocava tudo no construtor. As coisas foram aumentando de tamanho, e resolvi apenas criar a barra no construtor e chamar uma função, populateMenu(), para fazer essa tarefa.

Agora, as coisas no meu projeto estão ficando grandes demais, e estou pensando seriamente em criar uma nova classe só para isso. Acontece que, na pressa de obter resultados, a gente acaba tocando tudo no mesmo lugar. Nessa brincadeira até o padrão MVC vai para o espaço. Por favor, não façam isso.


Bom, esse é o que tem de mais básico em termos de menus. Ainda vou discutir sobre submenus e outros elementos que podem aparecer em um FXMenuPanel. Um abraço e até lá.

sexta-feira, 25 de setembro de 2009

FXMessageBox: Tratando a resposta do usuário

Neste tutorial, falei sobre as opções de botões que o FXMessageBox fornece ao usuário. Obviamente, cada mensagem requer um conjunto de botões adequado. Mas como saber qual botão foi pressionado?

Hoje, veremos como detectar qual o botão clicado pelo usuário, em resposta à mensagem exibida.


Relembrando o conjunto de botões

O protótipo das funções que exibem mensagens são da seguinte forma (message, aqui, é utilizado como um nome genérico para information, question, warning e error):
static FXuint message(FXWindow* owner, FXuint opts, const char* caption,
        const char* message, ...);

O parâmetro opts é onde se informa o conjunto de botões que vão aparecer na mensagem, através da seguinte enumeração (abstraindo os valores):
enum {
  MBOX_OK,
  MBOX_OK_CANCEL,
  MBOX_YES_NO,
  MBOX_YES_NO_CANCEL,
  MBOX_QUIT_CANCEL,
  MBOX_QUIT_SAVE_CANCEL,
  MBOX_SKIP_SKIPALL_CANCEL,
  MBOX_SAVE_CANCEL_DONTSAVE
};

Percebam, então, que os botões que podem aparecer em uma mensagem são:
  • OK
  • Cancel
  • Yes
  • No
  • Quit
  • Save
  • Don't save
  • Skip
  • Skip All
Tratando a resposta

Voltando ao protótipo da função, vemos que ela retorna um FXint. É justamente esse retorno que indica qual o botão pressionado. Esse valor também está presente em uma enumeração para facilitar (dar nomes a números mágicos é uma boa prática de programação). Essa enumeração é a seguinte:
enum {
  MBOX_CLICKED_YES      = 1,    /// The YES button was clicked
  MBOX_CLICKED_NO       = 2,    /// The NO button was clicked
  MBOX_CLICKED_OK       = 3,    /// The OK button was clicked
  MBOX_CLICKED_CANCEL   = 4,    /// The CANCEL button was clicked
  MBOX_CLICKED_QUIT     = 5,    /// The QUIT button was clicked
  MBOX_CLICKED_SAVE     = 6,    /// The SAVE button was clicked
  MBOX_CLICKED_SKIP     = 7,    /// The SKIP button was clicked
  MBOX_CLICKED_SKIPALL  = 8     /// The SKIP ALL button was clicked
};

Oito valores enumerados para nove botões?

É...

Fuçando no código fonte, descobri algo assim:
new FXButton(buttons, "&Don't Save", NULL, this, ID_CLICKED_NO, ...

Ou seja, o valor retornado quando o usuário clica em "Don't Save" é MBOX_CLICKED_NO. Faz sentido, mas eu acharia melhor se tivesse um nome específico, o valor até poderia ser o mesmo. Mas vai entender...



Exemplo

Vou dar um exemplo simples, até porque aqui não tem mistério nenhum (fora esse de cima...). Suponha que o usuário vai realizar uma operação que não pode ser desfeita. Isso deve ser informado ao usuário, caso ele volte atrás em sua decisão. Nosso tratador fica assim:
long FoxTutorialMainWindow::onCmdEraseAll(FXObject*, FXSelector sel, void*) {
  FXuint answer;

  answer = FXMessageBox::question(this, MBOX_YES_NO, "Apagar tudo",
      "Essa ação não pode ser desfeita.\nDeseja continuar?");

  switch (answer) {
    case MBOX_CLICKED_YES:
      FXMessageBox::information(this, MBOX_OK, "Dados apagados",
          "Todos os dados foram apagados.");
    break;

    case MBOX_CLICKED_NO:
      FXMessageBox::information(this, MBOX_OK, "Dados não apagados",
          "Os dados não foram apagados.");
    break;

    default: break;
  }

  return 1;
}

Resultado




Discussão

Nada de mais aqui. Se o usuário clicou em "Yes", exibe uma mensagem informando que todos os dados foram apagados. Se clicou em "No", informa que não foram apagados. Realmente sem mistérios.


Conclusão

Este tópico foi apenas um complemento de outro anterior (link). Aqui encerro oficialmente a série sobre o FXMessageBox. A partir daqui, começaremos a falar de elementos que compõem uma interface gráfica típica (menus, barras de ferramentas, barra de status etc.). Até lá!

sexta-feira, 18 de setembro de 2009

Tratamento de Eventos: Sem eventos a tratar

Pode parecer contraditório (e talvez realmente seja) o título deste tópico, mas não vejo onde mais encaixá-lo senão na categoria de Tratamento de Eventos.

Acontece que às vezes criamos uma janela que não precisa tratar nenhum evento em especial, portanto não necessita do mapa de mensagens. É sobre isso que falarei neste tópico.

Quando a janela não trata eventos

Imagine que você precisa de uma janela apenas para exibir algumas informações. Nenhuma entrada do usuário é necessária, nenhuma ação do usuário é executada. Essa janela não precisa tratar evento nenhum, então não precisa de um mapa de mensagens.

Ainda assim, precisa chamar a macro FXIMPLEMENT.


Uma pausa para algumas explicações

Depois de tantos tópicos escritos, só agora senti a necessidade de explicar para que serve esse par de macros: FXDECLARE/FXIMPLEMENT. Confesso que eu simplesmente utilizava, mas sem saber qual o real significado delas. Escrevendo este tópico, tive a curiosidade de investigá-las.

A macro FXDECLARE, como o próprio nome sugere, é colocada dentro da declaração da classe. Após expandida, ela toma a seguinte forma (coloquei espaços para melhorar a legibilidade ):
public:
  struct FXMapEntry {
    FX::FXSelector keylo;
    FX::FXSelector keyhi;
    long (classname::* func)(FX::FXObject*,FX::FXSelector,void*);
  };
  
  static const FX::FXMetaClass metaClass;

  static FX::FXObject* manufacture();

  virtual long handle(FX::FXObject* sender,FX::FXSelector sel,void* ptr);

  virtual const FX::FXMetaClass* getMetaClass() const {
    return &metaClass;
  }

  friend FX::FXStream& operator <<(FX::FXStream& store,const classname* obj) {
    return store.saveObject((FX::FXObjectPtr)(obj));
  }

  friend FX::FXStream& operator >>(FX::FXStream& store,classname*& obj) {
    return store.loadObject((FX::FXObjectPtr&)(obj));
  }

private:

Explicando:
  • Ela cria uma struct interna, chamada FXMapEntry, que armazena as entradas do mapa de mensagens.
  • Declara um objeto estático do tipo FXMetaClass, que armazena informações que descrevem um objeto FOX.
  • manufacture() nada mais é que um método fábrica que retorna um novo objeto; por isso o construtor padrão vazio privado é obrigatório.
  • handle() é o método utilizado para executar as ações propriamente ditas e também utilizado para delegar ações a outros objetos.
  • Os demais são irrelevantes no momento.

Pois bem, a outra macro, FXIMPLEMENT, é o complementar de FXDECLARE. É ela quem vai implementar os métodos declarados por FXDECLARE. Por isso ela é obrigatória, mesmo na ausência do mapa de mensagens. Após expandida, o método handle() é implementado da seguinte maneira:
long classname::handle(FX::FXObject* sender, FX::FXSelector sel, void* ptr) {
  const FXMapEntry* me=(const FXMapEntry*)metaClass.search(sel);

  return me ? (this->* me->func)(sender,sel,ptr)
            : baseclassname::handle(sender,sel,ptr);
}

Fuçando no código-fonte, vi que o método search() faz uma busca linear pelo mapa de mensagens, até encontrar uma entrada contenha o FXSelector sel (passado como parâmetro de handle()). Vejam:
const void* FXMetaClass::search(FXSelector key) const {
  register const FXObject::FXMapEntry* lst=(const FXObject::FXMapEntry*)assoc;
  register FXuint n=nassocs;

  while(n--) {
    if (lst->keylo <= key && key <= lst->keyhi)
      return lst;

    lst = (const FXObject::FXMapEntry*) (((const FXchar*)lst)+assocsz);
  }

  return NULL;
}

Ou seja, se encontrar a entrada, executa a função associada; senão, delega para a superclasse. Isto segue o padrão de projeto "Cadeia de Responsabilidade" ("Chain of Responsibility"), em que um objeto vai delegando a ação a seus superiores (digamos assim), até que alguém o trate. Neste caso, o topo da hierarquia é FXObject, cujo método handle() chama onDefault(), que apenas retorna 0.

Voltando...

Depois de toda essa explicação, podemos voltar ao nosso assunto principal. Suponhamos que temos uma janela que não trata nenhum evento. Neste caso, existem duas opções: declarar um mapa vazio, ou simplesmente não declarar mapa nenhum. A primeira opção já foi discutida em um dos primeiros tutoriais, mas vou reproduzi-la aqui:
FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {
};

FXIMPLEMENT(FoxTutorialMainWindow, FXMainWindow,
    FoxTutorialMainWindowMap, ARRAYNUMBER(FoxTutorialMainWindowMap))

Neste caso, a macro ARRAYNUMBER calcula o tamanho do vetor, que é 0 (cuidado: alguns compiladores reclamam). Na hora da busca pela mensagem, o laço while é ignorado e a função retorna NULL, disparando o processo já discutido.

A outra maneira é não declarar mapa nenhum. A chamada a FXIMPLEMENT fica assim:
FXIMPLEMENT(FoxTutorialMainWindow, FXMainWindow, NULL, 0)


Conclusão

Acho que este tópico encerra a discussão sobre o mapa de mensagens. Se eu me lembrar de alguma outra coisa nesse sentido, postarei aqui.

Entretanto, ainda há muito o que falar com relação ao tratamento de mensagens. Essa série será bem longa! Até a próxima!

quinta-feira, 17 de setembro de 2009

Tratamento de Eventos: Uma ação para vários IDs

Quando falei sobre o mapa de mensagens (aqui), eu adicionei quatro mapeamentos. Entretanto, todos eles eram do mesmo tipo e chamavam a mesma ação; vejam:

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_INFORMATION,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_QUESTION,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_WARNING,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_ERROR,
  FoxTutorialMainWindow::onCmdMessage),

Uma situação dessas ocorre quando eu tenho várias ações muito semelhantes: em vez de definir um callback para cada ação, eu defino apenas um; a diferença entre eles é feita dentro do corpo da função.

O FOX Toolkit fornece uma macro auxiliar para facilitar essa tarefa: FXMAPFUNCS. Neste tópico, explicarei como usá-la.


Definindo uma ação para vários IDs

Diante de uma situação dessas, seria bem mais fácil se eu pudesse diminuir o número de linhas a serem escritas. A propósito, o FOX Toolkit tem um slogan que diz: "Cada linha de código não escrita é uma linha correta". Pense no caso de uma calculadora: cada botão executa uma ação bem semelhante um ao outro: adicionar o seu valor à expressão e atualizar a caixa de texto que a exibe.

Imagina se eu tivesse que escrever uma função para cada botão? Seriam 10 botões de dígitos + 5 operações (contanto com a igualdade), isso apenas para uma calculadora básica. 15 funções, 15 entradas no mapa.

Para facilitar, pode-se definir apenas uma função e ligá-las todas aos botões da calculadora. E para evitar as quinze entradas no mapa, utiliza-se a macro FXMAPFUNCS. Para não ficar maçante, e reaproveitar os códigos já escritos, vou voltar ao exemplo do tutorial sobre as mensagens exibidas ao usuário.


Declarando as mensagens

Para poder utilizar a macro FXMAPFUNCS, é necessário que os IDs das mensagens sejam declarados seqüencialmente. Isso porque a macro pega um intervalo de IDs. No nosso exemplo:

enum {
  ID_INFORMATION = FXMainWindow::ID_LAST,
  ID_QUESTION,
  ID_WARNING,
  ID_ERROR,

  ID_LAST,
};

Com isso, basta usar a macro mágica. A sintaxe dela é:

FXMAPFUNCS(tipo, ID_min, ID_max, callback)

No nosso exemplo, o mapa de mensagens fica assim:

FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {
  FXMAPFUNCS(SEL_COMMAND,
             FoxTutorialMainWindow::ID_INFORMATION,
             FoxTutorialMainWindow::ID_ERROR,
             FoxTutorialMainWindow::onCmdMessage),
};

Ou seja, com apenas uma chamada, eu defini a mesma ação para quatro mensagens diferentes.


Diferenciando as mensagens

Já que cada evento vai disparar a mesma ação, é preciso diferenciar os diferentes eventos. Todo callback no FOX Toolkit tem o mesmo formato (com os nomes típicos dos parâmetros):

long onAction(FXObject *sender, FXSelector sel, void *ptr);

Neste caso, o que mais importa é o segundo parâmetro (sempre que um parâmetro não for utilizado, pode permanecer anônimo). Um FXSelector nada mais é que uma mistura entre o tipo de mensagem e o ID da mensagem. Essa mistura é feita automaticamente pela macro FXSEL ao se acrescentar uma entrada no mapa. Apenas adiantando uma informação, essa macro também é utilizada ao se delegar ações; falarei sobre isso no futuro.

Já que o FOX junta essas duas informações, ele pode separá-las: FXSELID recupera o ID da mensagem, e FXSELTYPE recupera o tipo da mensagem. Sendo assim, fica fácil saber quem enviou a mensagem, sem precisar recorrer a ponteiros (como normalmente acontece com uma certa linguagem que dizem não ter ponteiros). Um teste switch resolve o caso:

long FoxTutorialMainWindow::onCmdMessage(
        FXObject*, FXSelector sel, void*) {
  switch (FXSELID(sel)) {
    case ID_INFORMATION:
      FXMessageBox::information(this, MBOX_OK, "Informação",
              "Operação finalizada");
    break;

    case ID_QUESTION:
      FXMessageBox::question(this, MBOX_YES_NO, "Sair",
              "Deseja realmente sair do programa?");
    break;

    case ID_WARNING:
      FXMessageBox::warning(this, MBOX_OK, "Aviso",
              "Valor não especificado.\nAtribuindo padrão 1.");
    break;

    case ID_ERROR:
      FXMessageBox::error(this, MBOX_OK, "Erro",
              "%s: Arquivo não encontrado", filename.text());
    break;

    default: break;
  }

  return 1;
}

Percebam que como eu não uso nem o primeiro nem o último parâmetro, deixei eles anônimos. Dar um nome a eles pode gerar um warning enjoado por parte do compilador.


Conclusão

Neste exemplo, utilizei FXSELID para recuperar o ID da mensagem dentro do FXSelector. Mas eu falei também de FXSELTYPE, para recuperar o tipo. Isso me dá a chance de definir a mesma ação para diferentes tipos e diferenciá-los dentro da função.

Não tenho certeza se dá para fazer algo semelhante no mapa; existem duas macros, FXMAPTYPE e FXMAPTYPES, mas nunca as utilizei. Parece que elas definem um mapeamento de um tipo de mensagem (ou intervalo de tipos, no caso da segunda) para todos os IDs possíveis, chamando a mesma ação. Resumindo: melhor não usar.

Com relação ao mapa de mensagens, acho que só falta falar de quando uma janela não trata nenhum evento. Mas se eu lembrar de mais alguma outra coisa, eu posto aqui. Até a próxima.

terça-feira, 15 de setembro de 2009

Tratamento de Eventos: Tipos de mensagem

No tópico anterior, falei especificamente sobre o mapa de mensagens. Neste tópico, falarei sobre alguns tipos de mensagem que podem ser disparados.

Talvez esse tópico seja um pouco maçante, porque existem diversos tipos. Vou ser breve em cada um deles, pois são mais uma questão de prática que de teoria.

Outra característica deste tópico é que, apesar de no FOX Toolkit não haver nenhuma distinção de categoria de eventos (eventos de mouse, teclado, janela etc), vou dividi-los aqui dessa forma para facilitar a leitura.

Os tipos de mensagem estão definidos em fxdefs.h. Não vou copiá-lo aqui porque são 80 tipos de mensagem (sem contar o primeiro, SEL_NONE, e o último, SEL_LAST). Estão definidos dentro da enumeração FXSelType, e todos têm o prefixo SEL_. Sendo 80, é muito provável que a maioria deles não seja utilizada. Eu, pelo menos, não utilizo muitos. Portanto, vou limitar a minha explicação àqueles que eu já usei, ou que pelo menos sei para que servem.

Vou tentar também seguir a ordem em que são declarados na enumeração. Ainda, vou agrupar eventos muito semelhantes ou complementares. Comecemos então a analisá-los.


Eventos de teclado

SEL_KEYPRESS
SEL_KEYRELEASE


Em se tratando de teclado, há somente esses dois eventos a serem tratados. Uma tecla foi pressionada (SEL_KEYPRESS), uma tecla foi liberada (SEL_KEYRELEASE).

Apenas uma observação: se o programa não se importar com qual foi a tecla pressionada/liberada, não há nada extra. Mas se a tecla for importante, é necessário incluir o arquivo fxkeys.h. Nele estão as definições dos valores de todas as teclas que podem ser pressionadas.


Eventos de mouse

SEL_LEFTBUTTONPRESS
SEL_LEFTBUTTONRELEASE


Botão esquerdo do mouse pressionado/liberado.

SEL_MIDDLEBUTTONPRESS
SEL_MIDDLEBUTTONRELEASE


Botão do meio do mouse pressionado/liberado.

SEL_RIGHTBUTTONPRESS
SEL_RIGHTBUTTONRELEASE


Botão direito do mouse pressionado/liberado.

SEL_MOTION
SEL_ENTER
SEL_LEAVE


Esses três tipos de mensagem referem-se ao movimento do mouse relativo ao objeto que lança o evento. Quando um mouse entra na área do objeto, este lança um evento do tipo SEL_ENTER. Quando o mouse se move dentro de um objeto, este lança um evento do tipo SEL_MOTION. Finalmente, quando o mouse sai da área do objeto, este lança um evento do tipo SEL_LEAVE.

SEL_CLICKED
SEL_DOUBLECLICKED
SEL_TRIPLECLICKED
SEL_MOUSEWHEEL


Clique, duplo clique, triplo clique e movimento da roda do mouse. Não lembro de ter usado nenhum dos três primeiros.

Já o tratamento do evento da roda do mouse é meio esquisito; no momento não me recordo como é feito, mas futuramente falaremos disso.


Eventos de janela

SEL_PAINT

Indica que um objeto deve ser redesenhado. Muito útil quando se tem um canvas: define-se um callback que desenha no canvas, que é executado sempre que este lança um evento do tipo SEL_PAINT.

SEL_CONFIGURE

Este evento é lançado quando a janela é redimensionada. Este aqui é utilizado, por exemplo, quando há uma janela com um canvas OpenGL que, ao ser redimensionada, precisa reajustar a viewport e as matrizes de visualização para evitar distorções na imagem (quem trabalha com OpenGL sabe do que estou falando).

SEL_UPDATE

Este evento é lançado constantemente pelo objetos. Ao capturar um evento destes, é possível alterar o estado do objeto que o lançou. Por exemplo, um botão deve permanecer inativo enquanto não houver nenhum arquivo aberto; a partir do momento em que abre-se um arquivo, este botão deve ser ativado. Isto pode ser feito capturando-se o evento do tipo SEL_UPDATE que ele lança.


Execução de comandos

SEL_COMMAND

Sempre que eu falo desse tipo de mensagem eu dou os exemplos mais básicos possíveis: um botão clicado e um menu acionado. Mas não são somente botões e menus que enviam mensagens deste tipo. Diversos componentes também o fazem, mas isso fica para tópicos mais específicos. Por enquanto, esses dois exemplos são suficientes.

SEL_TIMEOUT

Serve para chamar a ação associada depois de um determinado intervalo de tempo.

SEL_CHORE

Serve para executar a ação associada sempre que não houver nenhum outro evento para tratar.

No início, eu utilizava para redesenhar o canvas o tempo inteiro. Em aplicações simples, isso não proporcionava perda de desempenho perceptível. Quando eu fiz um programa maior, de reconstrução de subestações elétricas, isso deixava lentíssimo. Foi só eu deixar isso de lado e atualizar o canvas somente quando necessário e o programa ficou uma bala. Portanto, evitem usar o chore dessa maneira, a menos que vocês queiram ter certeza que o processador está trabalhando (possivelmente à toa...).

Curiosidade: "chore" significa "tarefa doméstica".


Conclusão

Este tópico foi apenas uma introdução aos tipos de mensagem, mostrando apenas um pouco da teoria. Bem melhor será quando forem vistos na prática. Portanto, escreverei tópicos específicos para explicar com mais detalhes e mostrar exemplos do tratamento de cada tipo aqui apresentado. Até lá.

segunda-feira, 14 de setembro de 2009

Tratamento de Eventos: Mapa de mensagens

O mapa de mensagens é, digamos assim, o cerne do tratamento de eventos no FOX Toolkit. É ele quem determina que callback deve ser chamado em resposta a que tipo de mensagem, enviada por um determinado objeto.

Neste tópico, explicarei com mais detalhes como funciona o mapa. Vamos lá.

Definição do mapa

O mapa de mensagens é definido através da macro FXDEFMAP. Essa macro recebe um parâmetro, que é o nome da classe. Com esta macro, declara-se um vetor onde são armazenados os mapeamentos de mensagens; essa macro expandida nada mais é que uma declaração de uma variável normal. Seguindo o exemplo dos nossos tutoriais, temos o seguinte:
FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {

};

Essa macro (que termina com o parêntese fechado) é expandida na seguinte declaração:
static const FoxTutorialMainWindowMap::FXMapEntry
        FoxTutorialMainWindowMap[] = {

};

Sendo que FXMapEntry é uma struct interna criada automaticamente pela macro FXDECLARE, chamada ao declararmos a classe FoxTutorialMainWindow (lembram?). Ou seja, estamos declarando um vetor de entradas do mapa de mensagem.

Percebam o nome do vetor de mapeamento. Aqui temos um padrão de nomenclatura: o nome do vetor é composto pelo nome da classe com o sufixo "Map".

Dentro deste vetor, colocamos os mapeamentos em si. Isto é feito com auxílio da macro FXMAPFUNC, que veremos em seguida.

Adicionando mapeamentos

A macro FXMAPFUNC recebe três parâmetros: o tipo da mensagem, o ID da mensagem e um ponteiro para o callback. Tomando como exemplo este tutorial, adicionamos os seguintes mapeamentos:
FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_INFORMATION,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_QUESTION,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_WARNING,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_ERROR,
    FoxTutorialMainWindow::onCmdMessage),

Tipo de mensagem

Informa qual o tipo de evento que foi disparado: um comando, um clique do mouse, uma tecla pressionada, uma janela redimensionada etc.

Teremos um tópico exclusivo (ou vários tópicos...) para discutir os diferentes tipos de mensagem.

ID da mensagem

De certa maneira, identifica quem enviou a mensagem. Junto com o tipo da mensagem, tem-se uma combinação suficiente para dizer que ação (callback) deve ser executada. Essa combinação deve ser única, portanto uma declaração como a seguinte não é válida:
FXMAPFUNC(SEL_COMMAND, ID_ACTION, onCmdAction1),
FXMAPFUNC(SEL_COMMAND, ID_ACTION, onCmdAction2),

Neste caso, tentamos fazer com que a mesma mensagem ative duas ações, o que na realidade não ocorre. É possível, entretanto, combinar a mesma mensagem com diferentes tipos, podendo ou não executar a mesma ação. Por exemplo, eu tenho um canvas (área de desenho) que deve executar uma determinada ação quando o mouse for clicado e outra quando for redimensionado. Como é um objeto só, ele vai ter apenas um ID, diga-se ID_CANVAS. No mapa, teremos (para efeito de exemplo, vou omitir o escopo dos identificadores; compare com os exemplos reais acima):
FXMAPFUNC(SEL_CLICKED, ID_CANVAS, onCanvasClick),
FXMAPFUNC(SEL_CONFIGURE, ID_CANVAS, onCanvasConfigure),

É possível, ainda, que um mesmo ID, com diferentes tipos de mensagem, execute a mesma ação. Digamos que nosso canvas trate igualmente o clique com o botão esquerdo e direito do mouse:
FXMAPFUNC(SEL_LEFTBUTTONPRESS, ID_CANVAS, onCanvasClick),
FXMAPFUNC(SEL_RIGHTBUTTONPRESS, ID_CANVAS, onCanvasClick),

Resumindo, apenas a combinação <tipo de mensagem, id da mensagem> deve ser única.

Ponteiro para o callback

É exatamente isso: um ponteiro para a função membro, que foi declarada na classe (e, naturalmente, deve estar devidamente implementada). Realmente não há muito o que dizer aqui.

Observações finais

Nos exemplos didáticos, eu removi o escopo dos identificadores (id e ponteiros de função) por motivos de espaço. É necessário fornecê-los pelo seguinte motivo: a declaração do mapa de mensagens, na verdade, declara um vetor global, ou seja, fora do escopo da classe. Estando fora do escopo da classe, é necessário informá-lo, qualificando os identificadores através do operador de escopo ( :: ).

Outro motivo, talvez menos comum, é que é possível que um objeto de uma classe execute uma ação de outro objeto de outra classe. Isso acontece, por exemplo, quando um botão de uma caixa de diálogo altera o estado de um componente da janela principal, ou seja, o comando deste botão dispara uma ação da janela principal.

Estes dois motivos explicam, também, porque tanto as IDs das mensagens quanto as ações devem estar localizadas na interface pública da classe.

Conclusão

Neste tópico, apresentei alguns detalhes referentes ao mapa de mensagens. É muito importante entendê-lo, já que ele informa que ações devem ser executadas em resposta às diversas ações do usuário.

Existem ainda alguns outros detalhes, mas que deixarei para outros tópicos, pois acho que este já está extenso e cobriu bem o básico. Até lá.

quarta-feira, 15 de julho de 2009

Liberando memória

Foi postada na lista de e-mails do FOX Toolkit uma dúvida que achei bem interessante para colocar aqui, pois é um assunto básico: liberação de memória.

A dúvida foi enviada por John Selverian, e apresento-a aqui, junto com a resposta dada pelo Jeroen van der Zijp. Ela tem alguns conceitos que ainda não apresentei aqui, mas serve como um norte para o futuro.

Já falei rapidamente sobre isso em tópicos anteriores, mas creio que postando essa dúvida aqui o assunto fique mais sólido, além de apresentar um aspecto bem interessante a esse respeito. Vamos lá.

Liberando memória
Eu tenho uma pergunta bem básica. Eu tenho várias janelas FXDialogBox. Nos arquivos de cabeçalho eu declaro ponteiros e no construtor eu aloco a memória. No método de sair de algumas dessas FXDialogBox eu deleto a memória e em algumas não (sloppy programming, eu acho). Não vejo diferença na alocação de memória ou vazamento. Estou fazendo algo errado ou a memória é automaticamente liberada pelo FOX? Também, muitas vezes meu destrutor não é chamado... isso faz sentido?

Obrigado,

John S



Resposta
O FOX tenta liberar a memória, sim. Mas não há contagem de referência ou coleta de lixo, então ele só deleta as coisas que sabe que pode deletar:

  1. Janelas pais liberam widgets filhos.
  2. A árvore de widgets inteira é liberada pelo FXApp.

Mas:

  1. Recursos compartilhados como ícones, fontes NÃO são deletados, já que várias referências podem existir (exceção: stock icons e cursores que são criados pelo FXApp).

Uma regra simples é deletar, no destrutor do diálogo, todos os ícones/fontes/etc. que você criar em seu construtor do diálogo.

No FOX 1.7, agora tem FXAutoPtr, que faz isso bem fácil:
class MyDialog : public FXDialog {
FXAutoPtr<FXIcon> buttonIcon;
};

A idéia do FXAutoPtr é comportar-se como um ponteiro normal mas agir como o único dono do objeto para o qual aponta, e então quando o FXAutoPtr é destruído então o objeto que ele aponta também é destruído.

Então declarar suas variáveis de ícones como FXAutoPtr em vez de FXIcon* vai eliminar a necessidade de escrever explicitamente comandos delete em seus destrutores.

Espero que ajude,
- Jeroen


---
Retirado da lista de e-mails Foxgui-users. Veja o original aqui.

segunda-feira, 25 de maio de 2009

FXMessageBox: Opções de botões

No tópico anterior, apresentei as mensagens que o FOX Toolkit fornece ao usuário através da classe FXMessageBox. Percebam que as mensagens de informação, aviso e erro possuíam apenas o botão OK, enquanto a pergunta possuía dois botões, Sim e Não.

Neste tópico, mostrarei as opções de botões disponíveis para as caixas de mensagem. Novamente, mostrarei apenas os trechos de código e o resultado, pois são bem auto-explicativos.

Os botões que devem aparecer na caixa de mensagem são passados como segundo parâmetro, e são definidos em termos de uma enumeração declarada em FXMessageBox.h:
enum {
MBOX_OK = 0x10000000,
MBOX_OK_CANCEL = 0x20000000,
MBOX_YES_NO = 0x30000000,
MBOX_YES_NO_CANCEL = 0x40000000,
MBOX_QUIT_CANCEL = 0x50000000,
MBOX_QUIT_SAVE_CANCEL = 0x60000000,
MBOX_SKIP_SKIPALL_CANCEL = 0x70000000,
MBOX_SAVE_CANCEL_DONTSAVE = 0x80000000
};


MBOX_OK
FXMessageBox::information(&app, MBOX_OK, "Informação",
"Operação finalizada");



MBOX_OK_CANCEL
FXMessageBox::question(&app, MBOX_OK_CANCEL, "Apagar tudo",
"Essa operação não pode ser desfeita.\nDeseja continuar?");



MBOX_YES_NO
FXMessageBox::question(&app, MBOX_YES_NO, "Sair",
"Sair do programa?");



MBOX_YES_NO_CANCEL
FXMessageBox::question(&app, MBOX_YES_NO_CANCEL, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



MBOX_QUIT_CANCEL
FXMessageBox::question(&app, MBOX_QUIT_CANCEL, "Processo em execução",
"Terminar o aplicativo encerrará um processo em andamento."
"\nDeseja mesmo sair?"
);



MBOX_QUIT_SAVE_CANCEL
FXMessageBox::question(&app, MBOX_QUIT_SAVE_CANCEL, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



MBOX_SKIP_SKIPALL_CANCEL
FXMessageBox::error(&app, MBOX_SKIP_SKIPALL_CANCEL, "Entrada inválida",
"Entrada inválida.");



MBOX_SAVE_CANCEL_DONTSAVE
FXMessageBox::question(&app, MBOX_SAVE_CANCEL_DONTSAVE, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



Em inglês?
É... open-source: quem quiser em português, tem que ir no código-fonte, traduzir e recompilar.


No próximo tópico, mostrarei como tratar a opção selecionada pelo usuário.

Até lá.