quinta-feira, 30 de abril de 2009

Layout Managers: Packing Styles

Neste tópico aqui, falei das dicas de layout (layout hints). Essas dicas são passadas para quem vai ficar dentro do gerenciador de layout. Por exemplo, quando eu pus um botão na janela (veja aqui), eu passei LAYOUT_FILL em suas opções, para que ocupasse a janela inteira.

Existem ainda outras opções que definem o layout, mas que devem ser passadas para o próprio gerenciador de layout. Essas opções determinam a forma de empacotamento dos filhos, e também são definidas em FXWindow.h. São elas:
enum {
PACK_NORMAL = 0,
PACK_UNIFORM_HEIGHT = 0x00008000,
PACK_UNIFORM_WIDTH = 0x00010000
};

PACK_NORMAL

Opção padrão: cada widget com seu tamanho. Nem precisa passar se for o caso (novamente, a não ser que você quiser maior clareza no código...).


PACK_UNIFORM_HEIGHT

Primeiramente, o gerenciador calcula o filho com maior altura. Depois, utiliza esse valor para todos os outros filhos, deixando todo mundo com a mesma altura.


PACK_UNIFORM_WIDTH

Da mesma forma, deixa todos os filhos com a mesma largura.


Essas duas últimas opções sobrepõem opções individuais dos filhos, como LAYOUT_FIX_WIDTH.

À medida que avançarmos em nossos tutoriais sobre gerenciadores de layout, mostrarei essas opções funcionando.

terça-feira, 28 de abril de 2009

Tratamento de Eventos: Introdução

Neste tutorial, coloquei um botão simples na janela. Esse botão não possuía ação nenhuma. Inútil.

Agora, vou adicionar um evento a este botão: fechar o aplicativo (embora o texto do botão seja "Hello FOX"...).

Para isso, basta informar quem é o alvo do evento e qual a mensagem:
22   new FXButton(this, "Hello, &FOX!", NULL, getApp(), FXApp::ID_QUIT,
23 BUTTON_NORMAL|LAYOUT_FILL);

Neste caso, o alvo do meu evento é o próprio aplicativo, que pode ser recuperado chamando getApp(), presente em todas as classes derivadas de FXId (na prática, quase todas as classes FOX são; veja aqui a hierarquia de FXId).

A bem da verdade, nem seria necessário chamar esse método, já que estamos no construtor da janela, cujo primeiro parâmetro é a aplicação; bastava eu passar o ponteiro. Utilizei essa chamada aqui apenas para ilustrar.

O próximo parâmetro é o identificador da mensagem. Por convenção, sempre começa com ID_. Aqui eu passei ID_QUIT, que pertence à classe FXApp.

É só isso. Assim, eu conectei o botão à aplicação. Quando o botão envia um evento, a aplicação responde. Basicamente, é assim que os objetos se comunicam no FOX Toolkit.

Mas o que acontece lá dentro?

Bom, existem alguns detalhes a respeito disso. Com este exemplo, só posso dizer que o botão envia para a aplicação uma mensagem do tipo ID_QUIT, e a aplicação responde com a ação correspondente, ou seja, terminar a aplicação.

Futuramente, entrarei em detalhes e explicarei como funciona o mecanismo de comunicação entre os objetos FOX.

Só um detalhe, para o qual eu apanhei no começo e levei um certo tempo para descobrir o que era: eu criei um botão para fechar o aplicativo, mas em vez de passar o aplicativo, eu passei a janela. Resultado: a janela fechava, mas o aplicativo continuava executando... aí eu tinha que matar o processo.

segunda-feira, 27 de abril de 2009

Layout Managers: Layout Hints

As Layout Hints são opções passadas pelos widgets para os gerenciadores de layout que os contêm para informar algo a respeito de sua posição e tamanho. Assim como as opções de decoração (aqui), essas são apenas dicas; entretanto, os gerenciadores de layout farão o possível para atender aos pedidos.

A falta de algumas dicas quer dizer que o comportamento padrão deve ser utilizado, portanto muitas vezes não é preciso informá-las: as dicas do FOX Toolkit foram projetadas para que as situações mais comuns requeiram menos dicas. Por exemplo, normalmente os gerenciadores organizam seus filhos da esquerda para a direita, de cima para baixo, ou seja, não é preciso informar essas opções se esse for o caso.

Essas dicas estão definidas em FXWindow.h:
enum {
LAYOUT_NORMAL = 0,
LAYOUT_SIDE_TOP = 0,
LAYOUT_SIDE_BOTTOM = 0x00000001,
LAYOUT_SIDE_LEFT = 0x00000002,
LAYOUT_SIDE_RIGHT = LAYOUT_SIDE_LEFT|LAYOUT_SIDE_BOTTOM,
LAYOUT_FILL_COLUMN = 0x00000001,
LAYOUT_FILL_ROW = 0x00000002,
LAYOUT_LEFT = 0,
LAYOUT_RIGHT = 0x00000004,
LAYOUT_CENTER_X = 0x00000008,
LAYOUT_FIX_X = LAYOUT_RIGHT|LAYOUT_CENTER_X,
LAYOUT_TOP = 0,
LAYOUT_BOTTOM = 0x00000010,
LAYOUT_CENTER_Y = 0x00000020,
LAYOUT_FIX_Y = LAYOUT_BOTTOM|LAYOUT_CENTER_Y,
LAYOUT_DOCK_SAME = 0,
LAYOUT_DOCK_NEXT = 0x00000040,
LAYOUT_RESERVED_1 = 0x00000080,
LAYOUT_FIX_WIDTH = 0x00000100,
LAYOUT_FIX_HEIGHT = 0x00000200,
LAYOUT_MIN_WIDTH = 0,
LAYOUT_MIN_HEIGHT = 0,
LAYOUT_FILL_X = 0x00000400,
LAYOUT_FILL_Y = 0x00000800,
LAYOUT_FILL = LAYOUT_FILL_X|LAYOUT_FILL_Y,
LAYOUT_EXPLICIT = LAYOUT_FIX_X|LAYOUT_FIX_Y |
LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT
};

Percebam que as opções default têm valor 0.

A seguir, a explicação de cada opção. A menos quando informado o contrário, essas opções valem para todos os gerenciadores de layout.


LAYOUT_NORMAL

Modo de layout default.


LAYOUT_SIDE_TOP (default)
LAYOUT_SIDE_BOTTOM
LAYOUT_SIDE_LEFT
LAYOUT_SIDE_RIGHT

Utilizados somente quando o gerenciador de layout for do tipo FXPacker, FXGroupBox ou FXTopWindow. Posiciona o widget junto à borda superior, inferior, esquerda ou direita, respectivamente.

LAYOUT_SIDE_TOP e LAYOUT_SIDE_BOTTOM reduzem a altura do espaço disponível, enquanto LAYOUT_SIDE_LEFT e LAYOUT_SIDE_RIGHT reduzem a largura.

Essas opções não têm efeito nenhum se utilizadas em outros gerenciadores de layout.


LAYOUT_FILL_COLUMN
LAYOUT_FILL_ROW

Utilizados somente dentro de um FXMatrix.

Se todos os widgets de uma mesma coluna especificarem LAYOUT_FILL_COLUMN, então a coluna inteira é redimensionada se a matriz for redimensionada horizontalmente.

Da mesma forma, se todos o widgets de uma mesma linha especificarem LAYOUT_FILL_ROW, então a linha inteira é redimensionada se a matriz for redimensionada verticalmente.


LAYOUT_LEFT (default)
LAYOUT_RIGHT

Posiciona o widget no lado esquerdo ou direito do seu contêiner, respectivamente. Quando utilizado em um FXPacker, FXGroupBox ou FXTopWindow, só fazem sentido se em conjunto com LAYOUT_SIDE_TOP ou LAYOUT_SIDE_BOTTOM.


LAYOUT_TOP (default)
LAYOUT_BOTTOM

Posiciona o widget na parte de cima ou de baixo do seu contêiner, respectivamente. Quanto utilizado em um FXPacker, FXGroupBox ou FXTopWindow, só fazem sentido se em conjunto com LAYOUT_SIDE_RIGHT ou LAYOUT_SIDE_BOTTOM.


LAYOUT_FIX_X
LAYOUT_FIX_Y

Podem ser usados nenhum, um deles ou ambos. Instruem o gerenciador a posicionar o widget exatamente na posição informada pelo construtor do objeto. (Veja este tópico)


LAYOUT_FIX_WIDTH
LAYOUT_FIX_HEIGHT

Instruem o gerenciador a exibir o widget no tamanho especificado no construtor. (Veja este tópico)


LAYOUT_MIN_WIDTH (default)
LAYOUT_MIN_HEIGHT (default)

Estes não precisam ser especificados nunca (a não ser para enfatizar o fato durante a leitura do código), pois este é o comportamento padrão. Faz com que o gerenciador exiba o widget com seu tamanho mínimo.


LAYOUT_CENTER_X
LAYOUT_CENTER_Y

Centraliza o widget no gerenciador na direção especificada, adicionando espaço extra ao redor dele. O tamanho do widget será o padrão (o tamanho mínimo), a menos que seja informado explicitamente.


LAYOUT_FILL_X
LAYOUT_FILL_Y

Faz com que o widget ocupe todo o espaço disponível no contêiner na direção especificada. Se um ou mais widgets forem posicionados no mesmo gerenciador com essas opções, o espaço é dividido proporcionalmente aos seus tamanhos mínimos.

Obs.: Essa regrinha acima não funciona para dois gerenciadores de layout lado-a-lado.


LAYOUT_DOCK_SAME
LAYOUT_DOCK_NEXT
LAYOUT_RESERVED_1

Confesso que não sei para que servem, e a documentação não fala nada a respeito.


LAYOUT_FILL

Apenas uma combinação de LAYOUT_FILL_X e LAYOUT_FILL_Y para conveniência.


LAYOUT_EXPLICIT

Outra opção de conveniência: combinação de LAYOUT_FIX_X, LAYOUT_FIX_Y, LAYOUT_FIX_WIDTH e LAYOUT_FIX_HEIGHT, ou seja, te dá controle total sobre a posição e o tamanho do widget.

sexta-feira, 24 de abril de 2009

Widgets

Este tópico discute um pequeno detalhe a respeito do construtor dos widgets FOX. Serve para mostrar um dos pontos fortes do FOX Toolkit, a consistência, e também alguns aspectos que serão mostrados aqui servirão para tópicos futuros.

Bom, os widgets FOX têm um construtor similar:
FXWidget(FXComposite *p,
...
FXObject* tgt,
FXSelector sel,
FXuint opts,
FXint x, FXint y,
FXint w, FXint h,
FXint pl, FXint pr,
FXint pt, FXint pb
FXint hs, FXint vs
);

Alguns dele já foram explicados em tópicos anteriores, mas vou falar de todos aqui.


FXComposite *p

O componente que será o pai do widget criado. Serve para montar a hierarquia dos objetos FOX sem a necessidade guardar em um ponteiro e chamar algo do tipo p->add() (sem querer falar mal de outras linguagens...).

Normalmente é um gerenciador de layout.


Parâmetros específicos

Depois do dono do widget, vêm alguns parâmetros específicos. Por exemplo, para um botão (FXButton) esses parâmetros são o texto do botão e o ícone.

Naturalmente, nem todos os widgets terão parâmetros específicos, e a quantidade varia de widget para widget. Mas eles sempre vêm depois do dono e antes do alvo dos eventos (exceto para gerenciadores de layout).


FXObject *tgt

FXObject é o topo da hierarquia de classes do FOX Toolkit. Esse parâmetro indica que objeto irá receber os eventos disparados.

Se esse parâmetro é um FXObject, então qualquer objeto FOX pode receber eventos?

Sim. Não só receber como também enviar.

E só aproveitando o ensejo, aqui outra grande característica do FOX se revela: a capacidade de conectar os objetos entre si. Aqui essa conexão é estabelecida.


FXSelector sel

Além do alvo do evento, é preciso dizer qual é a mensagem que este widget envia. FXSelector nada mais é que um inteiro sem sinal.

Quando falarmos de tratamento de evento, esses dois parâmetros serão discutidos em mais detalhes.

Obs.: não estão presentes em gerenciadores de layout. Simplesmente porque eles não enviam eventos que o usuário queira tratar por conta própria.


FXuint opts

As opções do widget.

Alguns widgets possuem opções específicas. Por exemplo, um botão tem a opção BUTTON_TOOLBAR (entre outras, naturalmente) que faz com que ele tenha aparência achatada, levantando as bordas ao se passar com o mouse por cima (como o próprio nome diz, um botão da barra de ferramentas).

Aqui são passadas também opções de borda e de layout. No tutorial sobre criar um botão (aqui), eu criei um botão normal (ou seja, com borda levantada, grossa, com o texto centralizado e o ícone antes do texto) e que se expande pela janela inteira (uma opção de layout).

Todas essas opções são definidas por meio de enumerações e podem ser combinadas com o operador OU sobre bits ( | ).


FXint x, FXint y

Posição. O único detalhe é que essas coordenadas são em relação à janela pai (mãe?). Mas como os gerenciadores de layout se encarregam de posicionar seus filhos, raramente é necessário especificá-los manualmente.


FXint w, FXint h

Largura e altura do widget. Novamente, isso é controlado pelos gerenciadores de layout. Mas é mais comum precisar controlar o tamanho que a posição.

Normalmente não é necessário porque a maioria dos widgets sabe seu tamanho mínimo: um botão, por padrão, sempre vai ter um tamanho suficiente para exibir seu texto e ícone. Entretanto, alguns widgets não têm um tamanho mínimo: um FXCanvas, por exemplo, não tem nada dentro dele para calcular seu tamanho mínimo, então tem que dizer o tamanho dele (ou deixar que o gerenciador de layout o diga).

FXint pl, FXint pr, FXint pt, FXint pb

Bem, esses parâmetros servem para informar a quantidade de espaço entre sua borda e outros widgets. Se for um gerenciador de layout, define o espaço entre sua borda interna e os widgets (conforme já explicado aqui). Se for um widget folha (ou seja, não recebe nenhum filho na hierarquia), indica o espaço entre sua borda externa e outros widgets, conforme a figura a seguir:


Esses aqui é que são raros de especificar. Os valores padrões que eles apresentam dão praticamente sempre bons resultados.


FXint hs, FXint vs

Estes só estão disponíveis para gerenciadores de layout. Informam o espaço entre os seus filhos; também foi explicado aqui. E seus valores padrões também dão um resultado agradável.

quinta-feira, 23 de abril de 2009

Errata: Ícones

No tutorial sobre ícones, eu falei que o ícone deve saber quem é seu aplicativo. Isto é correto.

Entretanto, logo em seguida eu disse que isso era para que o ícone fosse criado junto com o aplicativo. Não é bem assim.

Um aplicativo FOX (FXApp) não tem conhecimento dos ícones ou imagens. Então quem tem a responsabilidade de criá-los é exatamente quem vai utilizá-los, seja uma janela, um botão ou qualquer outro objeto que exiba um ícone/imagem.

Ou seja, se a minha janela tem um ícone, o create() da janela vai chamar o create() de seu ícone. E como ícones e imagens podem ser compartilhados, a eles é permitido chamar create() mais de uma vez.

Observe que os objetos FOX são organizados em uma hierarquia em árvore, em que os pais criam os filhos. Isso quer dizer que se eu instancio uma imagem e dou um dono para ela antes que esse dono tenha sido criado, essa imagem será criada junto com seu dono.

Se eu instanciar uma imagem depois que seu dono foi criado, é preciso chamar create() na imagem manualmente.

Bom, o objetivo desse tópico era apenas apresentar essa correção. No fundo, no fundo, as imagens são criadas junto com o aplicativo, que é a raiz da hierarquia. Só que as imagens não são filhas do aplicativo, como eu imaginei, sendo criadas automaticamente se, e somente se, tiverem algum widget que seja dono delas.

Botões: Botão simples

Retornando aos tutoriais básicos, falarei agora rapidamente sobre criar um botão na interface. Também os detalhes são diversos aqui, portanto este também é o primeiro tópico de uma série sobre os botões que o FOX Toolkit disponibiliza.

Neste primeiro tópico, criarei apenas um botão que se expande pela janela inteira e não está associado a nenhum evento (falarei mais tarde sobre tratamento de eventos).

Vamos lá, então.

FoxTutorialMainWindow.cpp

22   new FXButton(this, "Hello, &FOX!", NULL, NULL, 0,
23 BUTTON_NORMAL|LAYOUT_FILL);

Pronto, o botão está criado.

Discussão

FXButton é a classe FOX para um botão simples, que você pressiona, solta, e ele executa uma ação. Aqui eu instancio um novo objeto dessa classe. Novamente, sem a necessidade de atribuir a um ponteiro, pois a própria janela se encarregará de liberá-lo.

Os parâmetros que foram passados:
  • this: significa que o botão será colocado dentro da própria janela principal.
  • "Hello, &FOX!": texto que aparecerá no botão. O & significa a letra que servirá de mnemônico, no caso F. Assim, Alt+F é o atalho para pressionar o botão. Existem outros detalhes dentro do texto do botão, que serão discutidos mais adiante.
  • NULL: o ícone do botão. Aqui é criado um botão somente com texto, então fica nulo mesmo.
  • NULL: o alvo da mensagem. Como ele não lança nenhum evento, nulo.
  • 0: a mensagem que o botão envia. Novamente, ele não lança nenhum evento: zero.
  • BUTTON_NORMAL|LAYOUT_FILL: opções do botão. BUTTON_NORMAL significa um botão com borda grossa e elevada, com o texto centralizado e o ícone à esquerda. LAYOUT_FILL significa que ele será expandido para ocupar a janela inteira. Esses detalhes serão explicados em seu devido tempo.

As demais opções referem-se à posição, tamanho e espaçamentos do botão, e normalmente não é necessário informá-los, pois têm valores padrões.

Resultado


Lembrando que FXMainWindow é um gerenciador de layout, redimensionar a janela fará com que o botão seja automaticamente redimensionado.

---
Código-fonte para este tutorial.

sexta-feira, 17 de abril de 2009

Layout Managers: Espaçamentos

Todos os gerenciadores de layout do FOX Toolkit têm um preenchimento (padding) interior, que afasta seus widgets filhos das suas bordas de cima, baixo, esquerda e direita por uma certa distância em pixels.

Além do preenchimento, há também um espaçamento (spacing), que é a distância (em pixels) entre dois widgets dentro do mesmo gerenciador de layout.

Bom, acho que com um desenho fica mais fácil de entender:


  • PL - espaço entre os widgets e a borda esquerda do gerenciador
  • PR - espaço entre os widgets e a borda direita
  • PT - espaço entre os widgets e a borda superior
  • PB - espaço entre os widgets e a borda inferior
  • HS - espaço horizontal entre um widget e outro
  • VS - espaço vertical entre um widget e outro

Esses parâmetros (nessa ordem) são tipicamente os seis últimos parâmetros do construtor de um gerenciador de layout. Porém, são fornecidos valores padrões para esses parâmetros, ou seja, na maioria dos casos não será necessário informá-los.

Como exemplo, o construtor de FXHorizontalFrame:

FXHorizontalFrame(FXComposite *p,
FXuint opts = 0,
FXint x = 0, FXint y = 0,
FXint w = 0, FXint h = 0,
FXint pl = DEFAULT_SPACING,
FXint pr = DEFAULT_SPACING,
FXint pt = DEFAULT_SPACING,
FXint pb = DEFAULT_SPACING,
FXint hs = DEFAULT_SPACING,
FXint vs = DEFAULT_SPACING
);

DEFAULT_SPACING é uma enumeração definida em FXPacker.h (FXHorizontalFrame deriva diretamente de FXPacker):

enum { DEFAULT_SPACING = 4 };

Ou seja, ao se colocar widgets dentro de um FXHorizontalFrame, estes estarão distantes das bordas por quatro pixels, e distantes entre si também por quatro pixels (isto, claro, se os valores não forem informados, aceitando os padrões).

Layout Managers: Introdução

Em uma interface gráfica de usuário, é muito importante que layout seja bem atrativo. Informar manualmente as posições e dimensões de cada objeto dá ao programador total controle sobre a interface. Entretanto, essa abordagem tem duas desvantagens seriíssimas:
  • é uma tarefa tediosa e que consome muito tempo
  • se o tamanho da tela mudar, os objetos devem se adaptar a essas mudanças; se suas posições e dimensões forem absolutas, eles não se modificarão junto com a tela
Devido a isso, a maneira mais recomendada de se posicionarem objetos é utilizando os chamados Gerenciadores de Layout (Layout Managers). A tarefa principal de um gerenciador de layout é arrumar os objetos que estão dentro dele de alguma maneira específica. Existem diferentes gerenciadores, que organizam seus objetos de cima para baixo, da esquerda para direita, em forma de grade e até um por cima do outro. A maioria deles permite, também, que as posições e dimensões sejam informadas explicitamente.

As vantagens de se utilizarem os gerenciadores de layout incluem:
  1. Evita a tarefa chata de posicionar os controles
  2. Os controles são rearrumados automaticamente quando há mudanças no layout (por exemplo, o usuário aumenta o tamanho da fonte)
  3. O layout é recalculado automática e inteligentemente quando o usuário redimensiona a janela
  4. Fica mais fácil adicionar controles que são criados automaticamente dependendo do controle do programa, como por exemplo interfaces criadas com base em bancos de dados

Todos os gerenciadores de layout do FOX Toolkit são derivados de FXComposite. Isso significa que é possível aninhá-los arbitrariamente. Esse aninhamento dos gerenciadores adequados, junto com as opções de layout passadas tanto para os próprios gerenciadores quanto para os objetos que ficarão dentro deles é que definirá o arranjo da interface.

Gerenciadores de layout básicos

O FOX Toolkit vem com alguns gerenciadores de layout de propósito geral. Os principais são:
  • FXPacker: posiciona os objetos dentro de seu retângulo, grudandos-os a um dos lados (esquerdo, direito, superior ou inferior) e reduzindo o tamanho disponível de acordo. Por exemplo, se um objeto é posicionado à esquerda ou direita, a largura é reduzida; se for posicionado em cima ou embaixo, a altura é reduzida.
  • FXTopWindow: opera da mesma forma que o FXPacker; entretanto, não é utilizado diretamente, pois não tem um construtor público. É utilizado indiretamente através de FXMainWindow e FXDialogBox (duas subclasses diretas). Resumindo, toda janela que for criada (seja a principal ou uma caixa de diálogo) vai ter esse comportamento.
  • FXHorizontalFrame: organiza os filhos horizontalmente da esquera para direita (padrão) ou da direita para esquerda.
  • FXVerticalFrame: organiza os filhos verticalmente de cima para baixo (padrão) ou de baixo para cima.
  • FXMatrix: organiza os filhos em forma de grade, com linhas e colunas.
  • FXSwitcher: posiciona os filhos exatamente um em cima do outro, deixando apenas um deles visível
  • FXGroupBox: provê a mesma funcionalidade do FXPacker, além de adicionar bordas ao redor do conteúdo e um rótulo (label) opcional.
  • FXSplitter: divide a tela horizontal ou verticalmente em duas áreas; a divisão pode ser movida pelo usuário.
  • FX4Splitter: divide a tela em quatro partes; da mesma forma, o usuário pode modificar as divisões.
  • FXSpring: tipicamente colocada dentro de um FXHorizontalFrame ou FXVerticalFrame. A grande diferença do FXSpring para outros gerenciadores é que ele permite uma divisão da tela em proporções fixas, por exemplo, dividir uma janela em três partes nas proporções 1:2:1 (a janela do meio sempre será o dobro das outras duas).

Continua...

Esta é apenas a primeira parte de uma séries de tópicos relacionados aos gerenciadores de layout. Este é um assunto muito extenso, que ainda tem muito a ser discutido. Existem vários detalhes a respeito deles, e haverá tópicos específicos para cada gerenciador mostrado acima.

quinta-feira, 16 de abril de 2009

Exibir imagem do OpenCV (IplImage)

Além do FOX Toolkit, eu utilizo a biblioteca OpenCV (Open Source Computer Vision Library), pois trabalho com Processamento de Imagens. Então surgiu a necessidade de juntar os dois.

O OpenCV até fornece alguns recursos de interface gráfica, mas são muito limitados: apenas janelas, trackbars e tratamento de eventos de mouse e teclado. A próxima figura mostra uma janela com uma trackbar.


Quebrando, então, a seqüência de tutoriais básicos, vou ensinar como exibir uma imagem do OpenCV (IplImage) em um aplicativo FOX.

Para isso, criarei um aplicativo que abre uma imagem pelo OpenCV, exibe e aplica o filtro da média. Não vou falar da criação da interface e do tratamento de eventos, apenas mostrar como se faz a "conversão" de IplImage para FXImage.

Na declaração da classe, eu apenas declaro os ponteiros para as imagens:

19 FXImageView *imageView;
20
21 FXImage *fxImage;
22 IplImage *iplImage;

No construtor, eu apenas atribuo nulo a ambas, já que o usuário é quem vai escolher a imagem.

32 fxImage = 0;
33 iplImage = 0;

A "conversão" é feita ao carregar a imagem:

52 long MainWindow::onCmdOpen(FXObject*, FXSelector, void*) {
53 FXFileDialog dialog(this, "Abrir imagem");
54
55 if (!dialog.execute(PLACEMENT_OWNER))
56 return 1;
57
58 if (fxImage)
59 delete fxImage;
60
61 if (iplImage)
62 cvReleaseImage(&iplImage);
63
64 FXString filename = dialog.getFilename();
65
66 IplImage *temp = cvLoadImage(filename.text());
67 iplImage = cvCreateImage(cvGetSize(temp), IPL_DEPTH_8U, 4);
68
69 cvCvtColor(temp, iplImage, CV_BGR2RGBA);
70
71 cvReleaseImage(&temp);
72
73 FXColor *colorData = reinterpret_cast<FXColor*> (iplImage->imageData);
74
75 fxImage = new FXImage(getApp(), colorData,
76 IMAGE_SHMP|IMAGE_SHMI|IMAGE_KEEP,
77 iplImage->width, iplImage->height);
78
79 fxImage->create();
80
81 imageView->setImage(fxImage);
82
83 return 1;
84 }

Aqui, eu crio uma imagem temporária só para carregar a imagem do arquivo. Neste caso, ele abre uma imagem colorida com três canais de 8 bits.

A representação interna do IplImage é um vetor de caracteres e os pixels são armazenados em BGR.

A representação interna do FXImage é um vetor de FXColor (equivalente a um inteiro sem sinal). Em cada inteiro são armazenados quatro canais (um byte para cada): RGBA.

Bom, aí já temos uma semelhança: ambos armazenam em um vetor. Isso que dizer que eu posso aproveitar o vetor de pixels do IplImage e jogar dentro do FXImage. Basta converter a representação interna.

Então, cria-se uma IplImage com quatro canais e profundidade de 8 bits sem sinal. Esta é a que foi declarada em nossa classe.

67 iplImage = cvCreateImage(cvGetSize(temp), IPL_DEPTH_8U, 4);

Basta então converter nossa imagem temporária de BGR para RGBA:

69 cvCvtColor(temp, iplImage, CV_BGR2RGBA);

Assim, já temos uma IplImage com representação interna parecida com a de FXImage. É preciso ainda converter de char* para FXColor*. Essa não é uma conversão padrão, portanto é preciso usar um operador especial para isso.

Eu acho até dolorido usá-lo, mas não vi outra alternativa.

73 FXColor *colorData = reinterpret_cast<FXColor*> (iplImage->imageData);

Com um vetor de pixels no formato interno do FXImage, é só criar nossa imagem:

75 fxImage = new FXImage(getApp(), colorData,
76 IMAGE_SHMP|IMAGE_SHMI|IMAGE_KEEP,
77 iplImage->width, iplImage->height);
78
79 fxImage->create();

Como a imagem foi criada depois do aplicativo, é preciso chamar create() manualmente.

Manipulando a imagem

Agora, temos um FXImage com o vetor de pixels compartilhado com o IplImage. Isso significa que qualquer operação executada sobre a imagem Ipl refletirá na imagem FX.

Isso é demonstrado aqui, onde eu aplico o filtro da média para suavizar a imagem:

88 long MainWindow::onCmdSmooth(FXObject*, FXSelector, void*) {
89 cvSmooth(iplImage, iplImage, CV_BLUR, 3, 3);
90
91 fxImage->render();
92
93 imageView->update();
94
95 return 1;
96 }

Os únicos detalhes é que é preciso chamar render() de FXImage e atualizar o imageView.

O render() é necessário porque há duas representações do FXImage: uma do lado do cliente, que neste caso é o vetor compartilhado entre as duas imagens, e o lado do servidor gráfico. render() atualiza o lado do servidor gráfico, de forma que seja exibida a imagem modificada.

E é preciso atualizar o imageView porque ele não detecta automaticamente alterações na imagem, então é preciso informar manualmente que ele deve se redesenhar.

Trabalhando com vídeos

O OpenCV, além de imagens estáticas, também trabalha com vídeos, que podem ser capturados de câmeras ou lidos de arquivos.

Cada quadro do vídeo é recuperado em um IplImage. Isso significa que dá para usar a técnica descrita acima. O problema é que a imagem que representa cada quadro não pode ser deletada nem modificada. Isso significa que para cada quadro do vídeo, é preciso fazer a conversão para uma outra imagem.

Seria mais simples se desse para modificar a imagem retornada, assim dava para compartilhar os pixels sem a necessidade de uma imagem extra.

Infelizmente, não tenho nenhum exemplo a mostrar com vídeos.

Resultado



Créditos

Imagem retirada do Eu Podia Tá Matando

---
Código-fonte deste tutorial.
Baixar vídeo.

quarta-feira, 15 de abril de 2009

FXTopWindow: Decorações e Posicionamento de Janelas

Quando falei sobre criar a janela principal (aqui), citei alguns aspectos relativos às decorações da janela e seu posicionamento, informando que isto era definido em FXTopWindow.h, onde está definida a superclasse direta de FXMainWindow.

Falarei agora especificamente desses dois assuntos.

Decorações

As decorações são itens "extras" que o seu decorador de janelas acrescenta. São as bordas da janela, barra de título, botões de minimizar, maximizar, fechar, menu da janela etc. FXTopWindow.h define a seguinte enumeração:

enum {
DECOR_NONE = 0,
DECOR_TITLE = 0x00020000,
DECOR_MINIMIZE = 0x00040000,
DECOR_MAXIMIZE = 0x00080000,
DECOR_CLOSE = 0x00100000,
DECOR_BORDER = 0x00200000,
DECOR_SHRINKABLE = 0x00400000,
DECOR_STRETCHABLE = 0x00800000,
DECOR_RESIZE = DECOR_SHRINKABLE|DECOR_STRETCHABLE,
DECOR_MENU = 0x01000000,
DECOR_ALL = (DECOR_TITLE |
DECOR_MINIMIZE |
DECOR_MAXIMIZE |
DECOR_CLOSE |
DECOR_BORDER |
DECOR_SHRINKABLE |
DECOR_STRETCHABLE |
DECOR_MENU
)
};

Apesar de serem bem intuitivos os nomes, vou explicá-los:
  • DECOR_NONE: Janela sem bordas, sem nada, só com o conteúdo. Se parecer inútil, pense em uma janela em modo tela cheia.
  • DECOR_TITLE: Título da janela.
  • DECOR_MINIMIZE: Adiciona a capacidade de minimizar a janela.
  • DECOR_MAXIMIZE: Adiciona a capacidade de maximizar a janela.
  • DECOR_CLOSE: Adiciona a capacidade de fechar a janela.
  • DECOR_BORDER: Adiciona bordas à janela.
  • DECOR_SHRINKABLE: Possibilita diminuir o tamanho da janela.
  • DECOR_STRETCHABLE: Possibilita aumentar o tamanho da janela.
  • DECOR_RESIZE: Permite aumentar ou diminuir a janela. Perceba que é apenas uma combinação das duas anteriores para facilitar.
  • DECOR_MENU: Habilita o menu da janela.
  • DECOR_ALL: Já conhecido, habilita todo mundo.

Agora a pergunta: se é tão intuitivo, porque explicar cada um? Simples. Essas são apenas sugestões para o decorador de janelas. Não são regras.

Isso quer dizer que, se o decorador achar melhor desenhar um item que você não especificou, ele desenha. Por isso eu não falei "Desenha isso", "Desenha aquilo", mas sim "Adiciona a capacidade", "Habilita". Por exemplo, para exibir o botão de fechar, tem que ter a barra de título (e com certeza as bordas vêm de brinde). Acontece também de se especificarem somente os botões de minimizar e fechar, mas o de maximizar aparece junto, desabilitado.

O que eu quero dizer é que cada decorador tem suas regras próprias, e que essas são apenas sugestões. Mas tenha certeza que o que você especificar ele faz. E se não especificar, também não faz; ou seja, se você não quer que a janela aumente de tamanho nem maximize, ela não irá, embora o botão de maximizar esteja lá (desabilitado, claro).

Não quero dizer, por outro lado, que você deva confiar sempre nos itens que vêm de "brinde". Especifique sempre os que você quer ter certeza que estejam disponíveis.

Posicionamento

Para mostrar a janela, chamei o método show(), passando PLACEMENT_SCREEN como parâmetro, exibindo no meio da tela. As opções de posicionamento são:
enum {
PLACEMENT_DEFAULT,
PLACEMENT_VISIBLE,
PLACEMENT_CURSOR,
PLACEMENT_OWNER,
PLACEMENT_SCREEN,
PLACEMENT_MAXIMIZED
};

  • PLACEMENT_DEFAULT: No tamanho e posição que foram especificados no construtor.
  • PLACEMENT_VISIBLE: Posiciona a janela para ficar totalmente visível. Confesso que nunca usei...
  • PLACEMENT_CURSOR: Posiciona onde está o cursor do mouse.
  • PLACEMENT_OWNER: Posiciona no centro da janela dona desta. Quando adicionarmos mais janelas, esta opção será utilizada.
  • PLACEMENT_SCREEN: Já conhecido, posiciona no centro da tela.
  • PLACEMENT_MAXIMIZED: Deixa a janela do tamanho da tela. Mas tem uma ressalva.

O PLACEMENT_MAXIMIZED realmente deixa a janela do tamanho da tela. Porém, dependendo do decorador (sempre ele...) as bordas vão todas embora. Dos que eu já usei (foram só três, mas tudo bem), só o Emerald (sempre ele...) preservou as bordas, deixando a janela do jeito que se imagina que uma janela maximizada deva ser. O decorador do Windows (não sei o nome) e o gtk-window-decorator (decorador padrão do GNOME) sumiram com as bordas.

E se eu quiser que a janela já inicie maximizada, com borda e tudo?

Chame maximize() em vez de show().

terça-feira, 14 de abril de 2009

Tutorial: Ícones

O que seria uma interface gráfica sem eles? Neste tutorial ensinarei a colocar ícones na janela (tanto o pequeno quanto o grande). Claro que ícones no FOX não servem apenas para isso, mas como eu só falei da janela principal, é lá que vamos colocá-los.

Um breve histórico

Acrescentar um ícone na interface FOX era bem trabalhoso. Primeiro, era preciso converter a imagem em um vetor (sim, um vetor estilo C) de pixels utilizando um programa chamado reswrap, distribuído junto com o FOX Toolkit. Sua sintaxe era:

reswrap [ options ] [ -o[a] arquivo_de_saida ] arquivos ...

Opções:
  • -o – Cria ou sobrescreve arquivo_de_saida
  • -oa – Acrescenta ao arquivo_de_saida
  • -e – Gera declaração de referência externa
  • -i – Gera um arquivo de cabeçalho

Assim, criavam-se dois arquivos, um icons.h com a declaração dos vetores, e um icons.cpp com o conteúdo deles. Era uma coisa monstruosa.

Todo este trabalho foi aliviado com a chegada de uma classe mágica: FXIconSource. Mas vamos ao código. Vou exibir aqui apenas as modificações relativas ao tutorial anterior.

FoxTutorialMainWindow.h
21 private:
22 void loadIcons();
23
24 void destroyIcons();
25
26 protected:
27 FXIcon *windowIcon;
28 FXIcon *windowMiniIcon;

Discussão

22/24: Métodos para carregar/destruir os ícones. Apenas por motivos de organização, deixando a tarefa centralizada.

27/28: Os dois ícones da janela.

O restante do arquivo permanece igual.

FoxTutorialMainWindow.cpp
18 FoxTutorialMainWindow::FoxTutorialMainWindow(FXApp *a)
19 : FXMainWindow(a, "Tutorial FOX", NULL, NULL, DECOR_ALL, 0, 0, 200, 200) {
20 loadIcons();
21
22 setIcon(windowIcon);
23 setMiniIcon(windowMiniIcon);
24 }

...

32 FoxTutorialMainWindow::~FoxTutorialMainWindow() {
33 destroyIcons();
34 }
35
36 /******************************************************************************/
37
38 void FoxTutorialMainWindow::loadIcons() {
39 FXIconSource iconSource(getApp());
40
41 windowIcon = iconSource.loadIconFile("icons/folders.gif");
42 windowMiniIcon = iconSource.loadScaledIconFile("icons/folders.gif", 16);
43 }
44
45 void FoxTutorialMainWindow::destroyIcons() {
46 delete windowIcon;
47 delete windowMiniIcon;
48 }

Discussão

20: Carrega os ícones. Descrição do método mais adiante.

22/23: Configura os ícones para a janela.

33: Ícones podem ser compartilhados (utilizados por mais de um widget por vez), portanto devem ser destruídos manualmente.

38-43: Nosso método para carregar os ícones.

39: Cria um objeto da classe FXIconSource. Apesar do nome, serve também para carregar imagens. Mas vamos com calma.
Cada ícone deve saber quem é sua aplicação, portanto o IconSource já deve vir com essa informação. Serve para que eles sejam criados junto com a aplicação.

41: Carrega um ícone do arquivo, retornando um ponteiro para o ícone.

42
: Idem, mas além de carregar, escala o ícone para que nenhuma de suas dimensões exceda o valor estabelecido (no caso, 16 pixels).

45-48: Nosso destrutor.
Conforme dito acima, precisamos destruir manualmente nossos ícones. delete é o suficiente para isso.

Resultado



Considerações finais

Por que GIF?

Apenas dois motivos: é suportado nativamente pelo FOX Toolkit e suporta transparência. Mas nada impede de usar outros formatos.

Créditos

---
Código-fonte deste tutorial.

segunda-feira, 13 de abril de 2009

FXMainWindow

Já que nosso último tutorial foi sobre criar uma janela vazia, usando FXMainWindow, falarei agora especificamente sobre essa classe.

Descrição
A MainWindow é normalmente a janela central de uma aplicação. Aplicações podem ter qualquer número de janelas principais.

Quando uma MainWindow é fechada, ela envia uma mensagem SEL_CLOSE para o seu alvo; o alvo deve retornar 0 se não houver nenhuma objeção contra o fechamento, e 1 caso contrário.

Depois que a mensagem SEL_CLOSE for enviada e nenhuma objeção for levantada, a janela principal deletará a si mesma.

Declaração

1 class FXAPI FXMainWindow : public FXTopWindow {
2 FXDECLARE(FXMainWindow)
3 protected:
4 FXMainWindow(){}
5 private:
6 FXMainWindow(const FXMainWindow&);
7 FXMainWindow &operator=(const FXMainWindow&);
8 public:
9
10 /// Constrói uma janela principal
11 FXMainWindow(FXApp* a, const FXString& name,
12 FXIcon *ic = NULL, FXIcon *mi = NULL,
13 FXuint opts = DECOR_ALL,
14 FXint x = 0, FXint y = 0,
15 FXint w = 0, FXint h = 0,
16 FXint pl = 0, FXint pr = 0,
17 FXint pt = 0, FXint pb = 0,
18 FXint hs = 0, FXint vs = 0
19 );
20
21 /// Cria os recursos do servidor (server-side)
22 virtual void create();
23
24 /// Destrutor
25 virtual ~FXMainWindow();
26 };

Sim, ela é só isso.

Discussão

1: A classe FXMainWindow é derivada de FXTopWindow. Por isso ela é pequena desse jeito, toda a funcionalidade dela é herdada das superclasses. E a hierarquia não é pequena:



Obs.: Em tópicos deste tipo, tratarei de apenas uma classe, ou seja, vou me limitar ao que a declaração da classe apresenta.

11-19: Construtor. Recebe 15 parâmetros:
  • FXApp *a - Ponteiro para a aplicação.
  • FXString &name - Título da janela.
  • FXIcon *ic, FXIcon *mi - Ícones da janela.
    • Ícone grande, que aparece quando se troca de janela com Alt+Tab
    • Ícone pequeno, que aparece no canto superior esquerdo da janela

  • FXuint opts - Opções da janela.
    Essas opções referem-se às decorações da janela, e são definidas em FXTopWindow.h. Chegaremos lá.

  • FXint x, FXint y - Posição inicial da janela.
  • FXint w, FXint h - Largura e altura da janela.
  • FXint pl, FXint pr, FXint pt, FXint pb - Espaçamento entre os componentes e a borda da janela.
    Uma MainWindow é um gerenciador de layout (layout manager), portanto aceita esses parâmetros. Quando falarmos de gerenciadores de layout, explicarei melhor.

  • FXint hs, FXint vs - Espaçamento entre um componente e outro.
    Idem.

Percebam que apenas a aplicação e o título da janela são obrigatórios. Todos os outros parâmetros têm valores default, ou seja, podem ser omitidos. Outra vantagem do FOX Toolkit: os parâmetros default asseguram um layout agradável aos olhos.

Observações
  1. A descrição da classe foi retirada da própria documentação de referência.
  2. O gráfico de herança foi desenhado com o programa Dia.

Links