The K Desktop Environment

Página seguinte Página anterior Índice geral

3. Criar Novas Aplicações

O KAppWizard, ou também denominado Assistente de Aplicações KDE, destina-se a permitir-lhe começar a trabalhar em novos projectos com o KDevelop. Assim, todos os seus projectos são primeiro criados através do assistente; depois você pode começar a construi-los e extender o código do esqueleto já disponibilizado. O KAppWizard também permite escolher entre vários tipos de projectos de acordo com os objectivos do seu projecto:

Neste capítulo veremos como o KAppWizard pode ser invocado e o que tem de ser feito para gerar um projecto de aplicação KDE. Isto irá ser também o passo inicial da nossa cobertura, onde iremos criar a versão inicial do projecto de exemplo. Para todos os outros tipos de projectos os passos são normalmente os mesmos, apenas que poderá não necessitar de ter certas opções disponíveis.

3.1 Invocar o KAppWizard e a Geração do Projecto

Iniciar o KAppWizard e a Primeira Página

Para começar com a sua primeira aplicação KDE, abra o KDevelop. Depois seleccione "Novo..." a partir do menu "Projecto". O KAppWizard inicia-se, e você vê uma árvore na primeira página, contendo os tipos de projectos. Quando um tipo é seleccionado, você vê uma antevisão de como irá funcionar após o processo inicial de construção. Escolha o ramo KDE, tipo Normal. Depois prima o botão "Seguinte" no fundo da primeira página do assistente. Isto mudará para a página seguinte, onde você tem de definir as opções gerais do projecto.

A Página de Geração de Definições

Para a nossa aplicação de exemplo, nos escolhemos o nome de projecto KScribble; pelo que deverá inserir isto no campo "Nome do Projecto". Depois seleccione o directório onde quer que o projecto seja construido; o defeito é o seu directório principal (home). Pode introduzir o caminho manualmente ou pode também premir o botão à direita para seleccionar o directório através de um diálogo.

De seguida, tem de introduzir o número da Versão. Para a primeira versão, defina esta como 0.1. É normal numerar novas aplicações que estão em desenvolvimento para a primeira distribuição abaixo de 1, e como a versão inicial irá apenas conter a estrutura standard, diremos que esta é a versão 0.1.

Finalmente, adicione o seu nome ao campo "Autor" e o seu endereço de e-mail. Pode deixar todas as outras opções nos seus valores de defeito.

Para lhe dar algumas informações sobre outras opções, pode premir o botão direito do rato sobre as opções, e obterá uma janela de ajuda rápida que descreve o objectivo da opção.

Estes são:

Agora vamos para a página seguinte premindo o botão "Seguinte" de novo para definir o modelo para os ficheiros header do seu projecto.

Os Modelos de Header e Código

A página de modelo de header permite-lhe incluir automaticamente um prefácio nos seus ficheiros header, contendo o nome do ficheiro, a data de construção, o ano do copyright, também o seu nome e endereço de e-mail. Você não tem de mudar essas partes em maiusculas por si, pois o KAppWizard faz isto automaticamente e guarda o modelo para este projecto, para que possa ser utilizado de novo mais tarde para criar novos ficheiros.

A segunda parte do modelo de header de defeito contém uma informação de licença. Por defeito, o seu projecto está sob a Licença Pública Geral GNU, que também é incluida no pacote. Esta licença é utilizada para proteger o seu código fonte contra qualquer pessoa que apenas copie o seu código para os seus propósitos. A Licença Pública Geral (GPL) oferece-lhe esta licença gratuitamente e assim protege os seus direitos como autor, e é comum para aplicações de distribuição livre. Para obter mais informação sobre a licença, deverá ler mais tarde o ficheiro COPYING no directório base do seu novo projecto que é uma cópia da GPL e é já enviada com a sua aplicação.

De qualquer modo, você pode escolher outra licença ou outro modelo de header que já esteja a utilizar para os seus projectos. Sendo assim você pode editar directamente o modelo de defeito dado. Para fazer isto, é-lhe dado o modelo numa janela de edição. Para limpar a página de defeito, seleccione "Novo", para utilizar outro modelo, seleccione "Ler...", que lhe permite escolher um ficheiro de modelo.

Quando tiver terminado, vá para a página seguinte premindo "Seguinte". Esta é a página de modelo para os seus ficheiros de código fonte e é geralmente a mesma que a página de modelo de header. A única diferença é que este modelo é utilizado para os seus ficheiros de implementação.

Criar o Projecto

Agora que já definiu todas as opções para o KScribble, seleccione "Seguinte" e prima o botão "Gerar" no fundo da janela do assistente. Se o botão não estiver disponível, você não definiu todas as opções correctamente. Para corrigir quaisquer erros, volte atrás no Assistente com "Regressar".

Depois você verá o que o KAppWizard faz- ele copia todos os modelos para o directório do seu projecto e cria o novo projecto. Após o KAppWizard terminar, o botão "Cancelar" muda para um botão "Sair" para deixar o assistente.

Após este último passo, você terminou a criação do novo projecto. O KDevelop lê-o então e a vista de árvore permite-lhe navegar através ficheiros e classes do projecto.

Na próxima secção, iremos discutir como construir e correr a sua primeira versão do KScribble e como o código fonte está organizado.

3.2 A Primeira Construção

Após o seu projecto ser gerado, iremos primeiro fazer uma viagem através do código fonte para obter um entendimento geral de como funciona o esqueleto da aplicação. Isto não só ajudará a começar como também saberemos onde mudar o quê em passos futuros.

Quando abrimos a página do VLF (Visualizador Lógico de Ficheiros) na vista de árvore, você vê algumas pastas que já ordenam os ficheiros do projecto relevantes para o programador. As duas primeiras pastas são "Header" e "Código". A pasta-Header assim sendo contém logicamente todos os ficheiros header do projecto, a pasta-Código todo o código fonte. Todas as outras pastas não são de interesse por agora, pelo que voltaremos aqui mais tarde para ver o que contêm.

Essas duas pastas contêm então os seguintes ficheiros:

Headers:

Código:

Mas antes de mergulharmos no código, vamos deixar o KDevelop construir e correr a nossa nova aplicação. Para fazer isto, seleccione "Make" a partir do menu "Construir" ou prima o botão correspondente na barra de ferramentas. A janela de output abre na base do KDevelop e deixa-o ver o que o make está a fazer através das mensagens que nos dá:


1   Making all in docs
2   make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
3   Making all in en
4   make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
5   make[2]: Nothing to be done for `all'.
6   make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs/en'
7   make[2]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
8   make[2]: Nothing to be done for `all-am'.
9   make[2]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
10  make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble/docs'
11  make[1]: Entering directory `/home/rnolden/Tutorial/kscribble1/kscribble'
12  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    kscribbleview.cpp
13  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    kscribbledoc.cpp
14  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c                 
    kscribble.cpp
15  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    main.cpp
16  /usr/bin/moc ./kscribble.h -o kscribble.moc.cpp
17  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    kscribble.moc.cpp           
18  /usr/bin/moc ./kscribbledoc.h -o kscribbledoc.moc.cpp
19  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    kscribbledoc.moc.cpp
20  /usr/bin/moc ./kscribbleview.h -o kscribbleview.moc.cpp
21  g++ -DHAVE_CONFIG_H -I. -I. -I.. -I/opt/kde/include -I/usr/lib/qt/include  -I/usr/X11R6/include    -O0 -g -Wall  -c
    kscribbleview.moc.cpp

22  /bin/sh ../libtool --silent --mode=link g++  -O0 -g -Wall   -o kscribble -L/opt/kde/lib  -L/usr/X11R6/lib -rpath /opt/kde/lib
    -rpath /usr/X11R6/lib kscribbleview.o kscribbledoc.o kscribble.o main.o kscribble.moc.o kscribbledoc.moc.o kscribbleview.moc.o
    -lkfile -lkfm -lkdeui -lkdecore -lqt -lXext -lX11

23  make[1]: Leaving directory `/home/rnolden/Tutorial/kscribble1/kscribble'

Como pode ver, numeramos todas as linhas, o que não irá surgir no seu output; apenas torna mais fácil descrever agora o que aconteceu durante a construção. Primeiro que tudo, o make trabalha recursivamente. Isto é, comça no directório em que foi invocado e então vai para os subdirectórios primeiro, regressa e processa o subdirectório seguinte. Finalmente, o directório em que foi iniciado é processado e o make termina. Assim, o make começou primeiro no directório principal do projecto contendo o código. Nas linhas 1 e 2, você vê como o processo make entra no directório docs, depois no subdirectório en. Como não existe nada para fazer, ele deixa estes directórios até regressar ao directório fonte kscribble na linha 11. Depois, o verdadeiro trabalho começa: o make invoca o compilador, aqui o g++ para compilar o ficheiro de código kscribbleview.cpp. A macro -DHAVE_CONFIG_H diz que o ficheiro config.h deverá ser utilizado. Este é um ficheiro que contém macros para a plataforma e aplicação específicas e está localizado no directório principal do projecto. Os comandos -I seguintes adicionam o caminho include onde o g++ pode encontrar os includes de que necessita. O caminho é o directório corrente, o directório principal do projecto (através de -I..) e o caminho include para o KDE, Qt e ficheiros header da biblioteca X11. Os directórios para estes ficheiros include foram determinados pelo script configure e definidos nos Makefiles, pelo que, o compilador sabe onde estes se encontram. Finalmente, -O0 define a optimização para zero (sem optimização), -g permite a depuração, -Wall define os avisos do compilador para todos e -c diz ao compilador para produzir um ficheiro de objecto, ou seja apenas compilar o ficheiro.

Isto é feito também para os outros ficheiros de código do nosso projecto nas linhas 13-15. Obviamente, o nosso código é compilado, mas em vez de ligar os ficheiros objecto do código ao binário final, nós vemos alguns outros comandos. Na linha 16, você vê que o programa "moc" é chamado para processar o ficheiro header kscribble.h, com o seu resultado em kscribble.moc.cpp. Depois, na linha 17, este ficheiro de código é também compilado. O mesmo acontece com os outros ficheiros header do projecto até à linha 21. Agora, como o conjunto de ferramentas Qt contém o mecanismo sinal/espaço, mas continua a ser uma implementação C++, você está a utilizar algumas palavras-chave que não são originalmente linguagem C++, tais como as declarações signals: e slots: nas suas classes. Isto dá-lhe a possibilidade de permitir facilmente comunicação entre objectos para todas as classes de objectos que herdem a classe QObject, pelo que pode evitar os normais ponteiros para funções de chamada (callback). Assim, a aplicação necessita do código que implementa esta funcionalidade, e é por isto que o moc é chamado. Moc é o Compilador de Objectos Meta (Meta Object Compiler) do conjunto de ferramentas Qt e constroi a implementação para mecanismos de sinais e espaços percorrendo os ficheiros header e produzindo um código de resultado que tem de ser compilado no binário. Como os projectos do KDevelop utilizam o automoc para determinar, que ficheiro header necessita de ser processado, você não tem de se preocupar com nenhuma chamada ao moc nem com o compilador C++ no ficheiro moc de resultado. Lembre-se apenas da regra que faz uma classe utilizar os sinais e espaços- hereditariedade da QObject ou qualquer classe que herde em si a QObject, inclusão da macro Q_OBJECT (sem ponto e vírgula !) no início da declaração de classe e a declaração para os sinais e espaços.

Finalmente, o seu binário é construido pelo compilador. O binário de resultado é chamado kscribble, o linkador inclui o caminho para as bibliotecas KDE e X11 e liga o código contra as bibliotecas kfile, kfm, kdeui, kdecore, qt, Xext e X11. Depois tudo está feito e o make termina.

3.3 O Esqueleto de Código

Para obter um conceito de como uma aplicação KDE funciona, teremos primeiro que olhar muito atentamente para o esqueleto de código já disponibilizado pelo Assistente de Aplicação. Como nós já vimos, temos um conjunto de ficheiros de código e header que constroem o código inicial para a aplicação e a tornam pronta-a-correr. Assim, a maneira mais fácil de explicar o código é seguir a implementação linha a linha como é processado durante a execução do programa até entrar no ciclo principal de evento e está pronto a receber interacção do utilizador. Depois, iremos observar a functionalidade que permite a interacção do utilizador e como certas coisas funcionam. Esta é provavelmente a melhor forma de explicar o esqueleto e, como é similar a quase todas as aplicações KDE, irá permitir-lhe ler também o código fonte de outros projectos; adicionalmente, você saberá aqui onde alterar que parte do código para fazer com que as suas aplicações se comportem da forma para que foram desenhadas.

A Função main()

Como a aplicação inicia a sua execução entrando na função main(), este será o ponto de partida do nosso exame ao código. A função main() do KScribble é implementada no ficheiro main.cpp e pode também ser encontrada utilizando o Navegador de Classes seleccionando a pasta "Globais", sub-pasta "Funções":


1  #include "kscribble.h"
2
3  int main(int argc, char* argv[]) {
4    KApplication app(argc,argv,"KScribble");
5
6    if (app.isRestored())
7    {
8       RESTORE(KScribbleApp);
9    }
10   else
11   {
12      KScribbleApp* kscribble = new KScribbleApp;
13      kscribble->show();
14      if(argc > 1){
15        kscribble->openFile(argv[1]);
16      }
17    }
18    return app.exec();
19  }

Agora, o que acontece primeiro é a normal criação do objecto KApplication, que obtém o nome da nossa aplicação KScribble como terceiro parâmetro. Quando se cria um novo KApplication, uma nova instância de KConfig é criada também que é conectada ao ficheiro de configuração em $HOME/.kde/share/config/appname + rc que guarda toda a informação que queremos utilizar quando iniciamos janelas da aplicação. O nome que passamos ao construtor de app será utilizado como o título da janela mais tarde.

Apesar do código de exemplo para tornar a primeira aplicação Qt numa KDE, o código seguinte é ligeiramente diferente. Após o objecto KApplication estar presente, nós testamos se a aplicação é iniciada através do gestor de sessões do kwm ou manualmente através do utilizador. Isto pode ser descoberto quando se chama isRestored() no objecto app, que retorna true (verdade) para gestão de sessão e false (falso) para um início normal.

Como a gestão de sessão é uma característica principal das aplicações KDE e vastamente utilizada pelo esqueleto mas com muito mais para explicar, nós vamos seguir a secção else{} primeiro; depois voltaremos e explicaremos a funcionalidade de sessão num passo seguinte.

Aplicação Iniciada pelo Utilizador

A secção else{} agora cria uma instância da classe KScribbleApp na linha 12. Este objecto é chamado para se mostrar a sí próprio na linha 13 como normal; a linha 14 determina se um argumento de linha de comando foi passado e, como isto é normalmente o nome de um ficheiro, chama o objecto kscribble para o abrir com openFile().

Note que nós não chamamos o método setTopWidget(kscribble) para a nossa aplicação- isto já foi feito pela classe que o KScribbleApp herda. Agora vamos observar o nosso objecto KScribbleApp- o que é e o que é que já disponibiliza? A única coisa que nós sabemos até agora é que tem de ser um Widget para representar o interface do utilizador na janela principal. Vamos ver a implementação da classe de KScribbleApp, que pode ser encontrada no ficheiro kscribble.cpp ou através de um clique no icon da classe no Navegador de Classes. Como a instância é criada pelo construtor. Primeiro que tudo, vemos que herda a classe KTMainWindow, que é parte da biblioteca kdeui. Esta classe em sí herda QWidget, pelo que, como normal, nós temos um widget normal como sendo a janela de topo. O KTMainWindow contém imensas functionalidades de que a classe KScribbleApp tira proveito. Disponibiliza barras de menu, ferramentas, estados e suporte para gestão de sessões. A única coisa que temos que fazer quando a criar sub-classes da KTMainWindow é criar todos os objectos de que necessitamos e criar outro widget que é gerido pela instância KTMainWindow como a vista principal no centro da janela; normalmente este é o local onde o utilizador trabalha tal como numa vista de edição de texto.

O Construtor

Vamos observar o código para o construtor e ver como a instância é criada:


1   KScribbleApp::KScribbleApp()
2   {
3     config=kapp->getConfig();
4       
5
6     ///////////////////////////////////////////////////////////////////
7     // chama inits para invocar todas as outras partes de construção
8     initMenuBar();
9     initToolBar();
10    initStatusBar();
11    initKeyAccel();
12    initDocument();
13    initView();
14
15    readOptions();
16
17    ///////////////////////////////////////////////////////////////////
18    // desactiva itens de menu e barra de ferramentas no arranque
19    disableCommand(ID_FILE_SAVE);
20    disableCommand(ID_FILE_SAVE_AS);
21    disableCommand(ID_FILE_PRINT);
22
23    disableCommand(ID_EDIT_CUT);
24    disableCommand(ID_EDIT_COPY);
25    disableCommand(ID_EDIT_PASTE);
26  }

Nós vemos que a nossa instância de configuração do KConfig aponta agora para a configuração da aplicação, pelo que podemos operar mais tarde com as entradas de configuração do ficheiro.

Depois, todas as partes da aplicação que são necessárias são criadas pela sua correspondente função membro que é especificada na nossa janela principal:

Finalmente, nós desactivamos alguns comandos que o utilizador pode realizar, porque não podem estar disponíveis no estado actual da aplicação. Como temos agora uma visão genérica de como a janela da aplicação é criada, iremos olhar para os detalhes de como os elementos do utilizador são construidos seguindo os métodos acima.

A Barra de Menu

Como mostrado acima, a barra de menu do KScribble é criada pelo método initMenuBar(). Aí, nós criamos um conjunto de QPopupMenus que surgem quando o utilizador selecciona uma entrada de menu. Depois, nós inserimo-las na barra de menu e conectamo-las com as entradas.

Primeiro, nós criamos o nosso recent_file_menu, que irá conter os nomes dos últimos 5 ficheiros abertos. Temos de fazer isto primeiro, porque esta entrada de menu é inserida no file_menu. Quando adicionamos a conecção directamente- apenas obtemos o sinal que é emitido pela entrada de menu com o seu número de entrada e chamamos a slotFileOpenRecent( int ), que então chama o ficheiro correcto a partir da lista de ficheiros recentes para ser aberto.

Depois criamos o nosso menu "Ficheiro". Este será o menu que irá ser visível na barra de menu. As acções standard são então inseridas no menu de popup uma a uma- primeiro os comandos para criar um novo ficheiro, abrir um ficheiro, fechar um ficheiro etc., finalmente "S&ir" para fechar a aplicação. Todas as entradas de menu têm de ser criadas na ordem em que surgem mais tarde, pelo que temos de manter um olho no que queremos ter em que lugar. Como um exemplo, nós olhamos para as seguintes entradas:

file_menu->insertItem(Icon("fileopen.xpm"), i18n("&Open..."), ID_FILE_OPEN );
file_menu->insertItem(i18n("Open &recent"), recent_files_menu, ID_FILE_OPEN_RECENT );

A primeira insere a entrada "Abrir...". Como nós queremos te-la como um icon, nós utilizamos o método insertItem() com o nome do icon. Para entender o processo de leitura do icon, nós necessitamos de saber o que ou onde Icon() é declarada- de facto, é uma macro disponibilizada pela classe KApplication:

#define Icon(x) kapp->getIconLoader()->loadIcon(x)
Adicionalmente, utiliza a seguinte macro internamente para obter acesso ao objecto da aplicação:
#define kapp KApplication::getKApplication()

Isto significa que o objecto KApplication já contém uma instância de um leitor de Icons- nós apenas temos de ter acesso a ela; depois irá ler o icon correspondente. Como os nossos icons são todos das bibliotecas KDE, nós não temos de nos preocupar com mais nada- eles são instalados no sistema automaticamente, pelo que também não temos de os incluir no pacote da nossa aplicação para os utilizar.

Após o parâmetro do icon (que é opcional), nós inserimos o nome da entrada de menu através de i18n("&Abrir..."). Aí, nós temos de observar duas coisas: primeiro, a entrada é inserida com o método i18n(). Tal como a entrada Icon(), é uma macro também definida em kapp.h e chama o objecto KLocale da KApplication para traduzir a entrada para o idioma actualmente utilizado:

#define i18n(X) KApplication::getKApplication()->getLocale()->translate(X)

Chegando aqui, deverá ser mencionado que uma pessoa poderia pensar "Eu não quero utilizar macros"- você pode fazer isso a maior parte dos casos. Mas aqui é imprescindível a utilização da i18n() porque para a internacionalização os ficheiros de idioma respectivos têm de ser construidos. Como este processo de construção depende da string (conjunto de caracteres) i18n, você tem de utilizar a macro.

Como já deve ter adivinhado, o & (i comercial) dentro das entradas de menu é mais tarde interpretado como uma linha por baixo da letra seguinte na entrada de menu. isto permite um acesso rápido ao comando de menu através do teclado quando o utilizador prime a tecla Alt em conjunto com a letra sublinhada.

Finalmente, nós damos um ID (número identificativo) à entrada de menu, que é um número inteiro através do qual podemos encontrar a entrada mais tarde. Para manter um controlo sobre os valores utilizados, estes são definidos através de macros e são coleccionados no ficheiro resource.h dentro do seu projecto. Por consistência, estas macros são todas maiúsculas e começam por ID_, depois o nome do menu seguido pela entrada. Isto torna muito fácil lembramo-nos do sentido de cada entrada em qualquer ponto do código, pelo que não temos de voltar à implementação da barra de menu de novo para observar as entradas.

A segunda entrada de exemplo mostra outra variante do método insertItem(). Aqui, nós acrescentamos o menu de popup recent_files_menu como um item de menu. Isto significa, que a entrada mostra-se a sí própria com a string dada "Abrir recentes", seguida de uma seta para a direita. Na selecção, o menu de popup de ficheiros recentes surge e o utilizador pode escolher o último ficheiro.

Por fim mas não menos importante existem muitas outras formas de inserir items de menu- o esqueleto de aplicação mantém isto o mais simples possível. Mais informação pode ser obtida na documentação Qt sobre a classe QMenuData.

Agora, após termos criado os menus de popup file_menu, edit_menu e view_menu, temos de incluir também um menu "Ajuda". Nós podiamos fazer isto tal como os outros, mas a classe KApplication oferece um método mais rápido e agradável para cobrir isto:

help_menu = kapp->getHelpMenu(true, i18n("KScribble\n" VERSION ));

Isto é tudo o que temos de fazer para obter um menu de ajuda que contenha uma entrada para o conteudo da ajuda com o atalho de teclado F1, uma caixa-sobre para a aplicação e uma caixa-sobre para o KDE (que pode ser desactivada chamando getHelpMenu(false,...);). O conteudo para a caixa-sobre da nossa aplicação é definida de novo com a string i18n() - VERSION fica com a macro que é definida para a numeração de versão do projecto no ficheiro config.h, pelo que não temos de modificar isto manualmente de cada vez que queremos fazer uma nova distribuição. Esteja à vontade para adicionar aqui qualquer informação sobre a sua aplicação, por ex. o seu nome, endereço email, copyright e afins.

Agora apenas temos de inserir os pop-ups na barra de menu. Como o KTMainWindow já constroi uma barra de menu para nós, nós apenas os inserimos chamando menuBar()->insertItem();.

O que resta fazer é conectar as entradas de menu com os métodos que irão executar. Assim, nós conectamos cada menu de popup através do seu sinal activated( int ) a um método commandCallback( int ), que contém uma frase switch que chama o método correspondente para as entradas de menu. Adicionalmente, nós conectamos os pop-ups através do seu sinal highlighted( int ) para disponibilizar ajuda de barra de estados para cada entrada. Sempre que o utilizador move o seu rato ou focus de teclado para uma entrada, a barra de estados mostra então a correspondente mensagem de ajuda.

Após termos terminado com a barra de menu, podemos continuar com a barra de ferramentas na secção seguinte. Lembre-se que uma instância de KTMainWindow apenas pode ter uma barra de menu visível de cada vez; assim se deseja construir diversas barras de menu, você tem de cria-las separadamente com instâncias de KMenuBar e definir uma delas através dos métodos correspondentes de KTMainWindow como sendo a barra de menu actual. Veja a documentação de classes da KMenuBar para informação mais detalhada sobre como extender as características, veja também Configurar Barras de Menu e Ferramentas.

A Barra de Ferramentas

A criação de barras de ferramentas é agora ainda mais simples que a das barras de menu. Como a KTMainWindow já disponibiliza barras de ferramentas, que são criadas pela primeira inserção, você é livre de criar várias. Basta adicionar os botões para as funções que deseja disponibilizar:

toolBar()->insertButton(Icon("filenew.xpm"), ID_FILE_NEW, true, i18n("New File") );

Isto adiciona um botão alinhado à esquerda com o icon "filenew.xpm" com o respectivo ID para a barra de ferramentas. O terceiro parâmetro decide se o botão deverá estar activo ou não; por defeito nós definimos isto para true (verdade), porque o nosso método disableCommand() no final do construtor faz isto por nós automaticamente tanto para as entradas do menu como da barra de ferramentas. Finalmente, o último parâmetro é utilizado como uma "Dica Rápida"- quando o utilizador move o ponteiro do rato sobre o botão de forma a ficar iluminado, uma pequena janela surge que contém uma pequena mensagem de ajuda, cujo conteudo pode ser definido aqui.

Finalmente, todos os botões da barra de ferramentas estão conectados novamente ao nosso método commandCallback() através do seu sinal clicked(). Ao sinal pressed(), nós permitimos ao utilizador receber a mensagem de ajuda correspondente na barra de estados.

Informação Adicional:

Como as barras de ferramentas são criadas utilizando a classe KToolBar, você deverá ler a respectiva documentação. Com a KToolBar, imensas coisas necessárias na barra de ferramentas podem ser realizadas tais como pop-ups demorados se o seu botão deseja fazer surgir um menu quando o botão é mantido premido ou até mesmo widgets como caixas de escolha. Também, por defeito, a barra de ferramentas preenche a largura total da janela, o que a faz ter um aspecto melhor utilizando apenas uma barra. Quando se usa mais do que uma, você deverá também pensar na hipótese de definir o tamanho da barra para terminar a seguir ao botão mais à direita, para que outras barras possam ser apresentadas na mesma linha por baixo da barra de menu. Iremos discutir algumas tecnicas sobre desing e extensão de barras de ferramentas na secção Configurar Barras de Menu e Ferramentas.

A Barra de Estados

A barra de estados é, bem como as outras barras, já disponibilizada pela instância KTMainWindow, pelo que apenas temos de inserir os nossos itens como quisermos. Por defeito, o esqueleto contém apenas uma entrada que mostra a ajuda de barra de estados. Para muitas aplicações isto pode não chegar; então você inserirá as entradas que necessite para mostrar por ex. coordenadas e afins.

Também, uma aplicação pode apenas ter uma barra de estados de cada vez como as barras de menu. Se desejar construir várias, deverá cria-las separadamente e definir a barra corrente através do método correspondente da KTMainWindow. A barra de estados também permite inserir widgets, que podem ser utilizador para produzir bons hábitos de apresentar barras de progresso como o KDevelop faz. Verifique a documentação de classes da KStatusBar.

Atalhos de Teclado

Ao atingir o método initKeyAccel(), já construimos os itens standard da janela principal de uma aplicação- as barras de menu, ferramentas e estados. Na verdade, não definimos nenhuns de teclado através dos quais utilizadores avançados que apenas queiram trabalhar com o teclado possam aceder rapidamente a certos comandos que são utilizados mais frequentemente durante a utilização do programa. Para o fazer, nós podiamos ter inserido as teclas de na inserção dos itens de menu por exemplo, mas o KDE oferece uma boa solução para construir e manter de teclado. Imensos utilizadores querem te-los configuráveis por um lado e por outro os standard deverão ser os mesmos em todas as aplicações. Assim, o Centro de Controlo do KDE permite configurar de teclado standard globalmente através da utilização da classe KAccel. Adicionalmente, as bibliotecas KDE contêm um widget que permite aos utilizadores configurar de teclado específicos da aplicação facilmente. Como o esqueleto da aplicação apenas utiliza itens de menu que têm acções standard tais como "Novo" ou "Sair", estes são definidos pelo método initKeyAccel(). Acções standard apenas têm de ser conectadas, para os valores de teclado específicos da sua aplicação, têm de os inserir primeiro especificando o nome do de teclado e depois conecta-lo. como os nossos estão todos presentes na barra de menu, temos de modificar os para as entradas de popup. Finalmente chamamos readSettings(), que lê as definições correntes a partir da janela principal do KDE contendo as configurações de standard, e depois as definições de específicos do ficheiro de configuração da aplicação. Quando nós aprofundarmos mais o nosso projecto de exemplo, iremos também falar sobre como configurar os específicos da nossa aplicação através de um diálogo de configuração, veja Configurar Barras de Menu e Ferramentas para essa parte do processo de desenvolvimento.

O Modelo de Vista de Documento

As duas próximas chamadas de funções membros, initDocument() e initView(), vão finalmente construir a parte que é suposto as janelas da aplicação disponibilizarem ao utilizador: um interface para trabalhar com os dados que é suposto a aplicação manipular; e essa é também a razão pela qual o esqueleto da aplicação contém três classes, uma classe *App, *View e *Doc. Para entender, porque esta estrutura é útil, iremos olhar um pouco à margem do código e introduzir alguma teoria, depois voltaremos ao programa de novo para ver como o esqueleto feito pelo KDevelop suporta esse modelo.

Basicamente, tudo o que tem sido explicado até agora sobre a estrutura é que necessitamos de uma instância de aplicação que contenha a janela principal. Esta janela é responsável por disponibilizar o interface basico para o utilizador- contém as barras de menu, ferramentas e estados e o controlo de eventos para a interacção do utilizador. Também, contém uma área, que é descrita como uma "vista". Agora, o objecto da visra é genericamente, mostrar os dados que o utilizador pode manipular, por ex. uma parte de um ficheiro de texto. Apesar do ficheiro de texto ser provavelmente maior que a vista é capaz de mostrar no ecrã, disponibiliza ao utilizador a possibilidade de ir para a parte que deseja ver (pelo que é uma vista), e aí o utilizador pode modificar os dados do conteudo do ficheiro. Para dar ao programador uma maneira melhor de separar as partes da aplicação através do código, o Modelo Vista-Documento foi inventado. Apesar de não ser um standard, disponibiliza uma estrutura de como uma aplicação deveria funcionar:

De regresso ao exemplo de trabalhar com um ficheiro de texto- aí, este modelo iria funcionar de forma a que o Documento leria o conteudo do ficheiro e disponibilizar os métodos para modificar os dados bem como para gravar o ficheiro de novo. A Vista então processa os eventos que o utilizador produz através do teclado e rato e utiliza os métodos do objecto Documento para manipular os dados do documento.

Finalmente, o objecto controlador é responsável pela interacção do utilizador disponibilizando os objectos de Vista e de Documento bem como os interfaces para enviar comandos tais como abrir e gravar. Adicionalmente, certos métodos do objecto Vista podem ser disponibilizados por comandos que podem ser acedidos através de de teclado ou pelo rato ou barras de menu e ferramentas.

Este Modelo Vista Documento tem algumas vantagens- separa o código do programa de uma forma mais orientada por objectos e por isso oferece mais flexibilidade em geral, por ex. o mesmo objecto documento poderia ser apresentadps por duas vistas ao mesmo tempo; quer através de uma nova vista numa nova janela ou através de mosaico da janela corrente que contém dois objectos vista que constroem a região de vista da janela actual.

Agora, se você vem dos sistemas MS-Windows poderá teralguma experiência com isso- o MFC já disponibiliza um modelo de documento que está pronto a utilizar. Para as aplicações KDE e Qt, as coisas são ligeiramente diferentes. A Qt é um poderoso conjunto de ferramentas pois disponibiliza as classes mais necessárias, widgets etc. Mas não existiu nenhuma intenção de tomar conta do modelo documento-vista, e como o KDE herda a Qt, também não existiram nenhumas tendências de introduzir este modelo. Isto de certa forma tem a sua justificação no facto de que normalmente as aplicações X não funcionam com um MDI (Interface de Documento Multiplo). Cada janela principal é responsável pelos seus dados e isso reduz a necessidade de um modelo de documento pelo facto de que métodos para trabalhar em documentos são sempre implícitos em widgets. A única excepção a isto actualmente é o projecto KOffice que tenciona disponibilizar um conjunto de aplicações de produtividade completo tais como um processador de texto, uma folha de cálculo etc. Tecnicamente, isto é realizado com duas alterações à utilização normal da Qt e KDE:

Mas como o KDevelop actualmente se foca na utilização das actuasi bibliotecas do KDE 1.1.x e a Qt 1.4x, nós não podemos utilizar este modelo por defeito- isto virá em distribuições futuras de um KDE 2, que irá (esperamos) conter duas modificações principais em relação à situação actual:

  1. um interface MDI para a KTMainWindow
  2. as bibliotecas KOM que disponibilizam um modelo de documento

Assim, a forma actual para os programadores de aplicações pode ser ou implementarem todos os métodos de documentos necessários dentro da sua vista ou tentarem reproduzir um modelo de documento por si. O KDevelop assim contém essa reprodução disponibilizando as classes necessárias e os métodos básicos que são geralmente utilizados para o Modelo Documento-Vista com a estrutura de aplicações para Qt e KDE.

De regresso ao código, você pode agora imaginar o propósito dos dois métodos que mencionamos no início desta secção: as funções initDocument() e initView(). O initDocument() constroi o objecto documento que representa a janela de dados da aplicação e inicializa atributos básicos tais como definir o bit de modificação que indica se os dados correntemente em utilização foram modificados pelo utilizador. Depois, o método initView() constroi o widget *View, conecta-o com o documento e chama o método setView() de KTMainWindow para dizer à janela *App para utilizar o widget *View como a sua vista central.

Para o programador, é importante saber que durante o processo de desenvolvimento ele tem de:

Configuração da Aplicação

Agora, depois de termos criado todas as instâncias da instância KTMainWindow da nossa aplicação para criar a primeira janela, nós temos de inicializar alguns valores que influenciam a aparência do programa. Para isto, chamamos readOptions(), que obtém todos os valores e chama os métodos necessários para definir os atributos correspondentes. A biblioteca KDE-Core contém a classe KConfig que disponibiliza uma boa possibilidade de guardar valores em ficheiros de configuração bem como de os ler novamente. Também, como cada instância KApplication já cria o seu ficheiro de recursos, apenas temos de aceder a este ficheiro e criar os nossos valores. Como o KConfig nos disponibiliza o objecto de ficheiro, temos de utilizar a classe KConfigBase para ler e escrever todas as entradas. Como escreve-lo é muito fácil de fazer com métodos writeEntry(), ler depende do tipo de atributo que queremos inicializar. Geralmente, uma entrada no ficheiro de configuração contém um nome de valor e o valor. Valores que pertençam conjuntamente num contexto podem ser coleccionados em grupos, pelo que temos de definir o nome do grupo antes de acedermos ao seu valor; o grupo tem de ser definido apenas uma vez para ler um conjunto de atributos que estão no mesmo grupo. Vamos observar o que queremos ler:


1   void KScribbleApp::readOptions()
2   {
3
4      config->setGroup("General Options");
5
6      // bar status settings
7      bool bViewToolbar = config->readBoolEntry("Show Toolbar", true);
8      view_menu->setItemChecked(ID_VIEW_TOOLBAR, bViewToolbar);
9      if(!bViewToolbar)
10       enableToolBar(KToolBar::Hide);
11
12     bool bViewStatusbar = config->readBoolEntry("Show Statusbar", true);
13     view_menu->setItemChecked(ID_VIEW_STATUSBAR, bViewStatusbar);
14     if(!bViewStatusbar)
15       enableStatusBar(KStatusBar::Hide);
16
17     // bar position settings
18     KMenuBar::menuPosition menu_bar_pos;
19     menu_bar_pos=(KMenuBar::menuPosition)config->readNumEntry("MenuBar Position", KMenuBar::Top);
20
21     KToolBar::BarPosition tool_bar_pos;
22     tool_bar_pos=(KToolBar::BarPosition)config->readNumEntry("ToolBar Position", KToolBar::Top);
23
24     menuBar()->setMenuBarPos(menu_bar_pos);
25     toolBar()->setBarPos(tool_bar_pos);
26
27     // initialize the recent file list
28     recent_files.setAutoDelete(TRUE);
29     config->readListEntry("Recent Files",recent_files);
30
31     uint i;
32     for ( i =0 ; i < recent_files.count(); i++){
33       recent_files_menu->insertItem(recent_files.at(i));
34     }
35
36     QSize size=config->readSizeEntry("Geometry");
37     if(!size.isEmpty())
38       resize(size);
39   }

Como vimos numa das partes de código acima, a primeira acção que o nosso construtor faz é:

config=kapp->getConfig();

que define o ponteiro config tipp KConfig para a configuração da aplicação. Assim, não temos de nos preocupar com a localização do ficheiro de configuração. Na verdade, o ficheiro está, de acordo com o Standard de Sistema de Ficheiros KDE (KDE FSS), localizado em $HOME/.kde/share/config/; iremos observar mais atentamente o KDE FSS num passo futuro quando estivermos a definir localizações de ficheiros do projecto para instalação. Como o ficheiro de configuração é colocado no directório principal do utilizador, cada utilizador tem a sua aparência personalizada da aplicação excepto para valores que estão localizados num ficheiro de configuração para todo o sistema que pode ser criado opcionalmente e instalado pelo programador no directório do KDE. Mas, apesar de isto poder ajudar nalguns casos, devemos evitar quaiquer dependências da nossa aplicação no sentido da existência de entradas de ficheiro. Assim, todos os métodos de leitura disponibilizados pelo KConfigBase permitem adicionar um valor de defeito a ser utilizado quando a entrada não existe. Outra coisa importante para um programador é que o ficheiro de configuração é guardado em texto simples, e isto é também por algumas razões pois tem de ter em atenção alguns critérios:

Agora que sabemos os básicos, vamos analizar o código. Como dissemos, apenas temos de utilizar o nosso ponteiro config para aceder aos valores. Primeiro, na linha 4, nós definimos o grupo actual para "Opções Gerais". Isto indica que os valores estão de certo modo atributos gerais para a aplicação. Depois nós lemos os valores para a barra de ferramentas e estados- estes têm de ser gravados quando a aplicação fecha para repor os seus estadps de novo quando o utilizador reiniciar o programa. Como as barras apenas podem estar ligadas ou desligadas, nós utilizamos um valor boolean, pelo que, o nosso método é readBoolEntry(). O processo é idêntico para ambas as barras, pelo que apenas temos de olhar para as linhas 7-10 para observar o que está a acontecer para a barra de ferramentas. Primeiro, nós lemos o valor para uma variável temporária bViewToolbar na linha 7. O nome do valor no ficheiro é "Mostrar Barra de Ferramentas" e, se o valor não estiver presente (o que seria o caso na primeira vez que a aplicação iniciasse), o valor de defeito é definido para true (verdade). De seguida, nos definimos a marca de verificação da entrada de menu para (des)activar a barra de ferramentas através deste valor: nós chamamos setItemChecked() no menu ver, entrada ID_VIEW_TOOLBAR com o nosso atributo. Finalmente, definimos a barra de ferramentas para utilizar o valor. Por defeito, a barra de ferramentas é visível, pelo que, apenas temos de fazer algo se bViewToolbar for false (falso). Com enableToolBar() (linha 10) estamos a definir a barra para se esconder automaticamente se for desactivada.

De seguida, temos de ler as posições da barra. Como o utilizador pode ter modificado a posição da barra arrastando-a com o rato para outra área de cista, esta tem de ser gravada também e o seu estado reposto. Olhando para as classes KToolBar e KMenuBar, vemos que as posições da barra podem ser:

enum BarPosition {Top, Left, Bottom, Right, Floating, Flat}
/* {Topo, Esquerda, Baixo, Direita, Flutuante, Fixa} */

Como este valor foi escrito num valor numérico, temos de o ler com readNumEntry() e converte-lo num valor de posição. Com setMenuBarPos() e setBarPos() dizemos às barrass ode aparecerem.

Agora provavelmente notou que o nosso menu "Ficheiro" contém um menu para ficheiros utilizados recentemente. Os nomes de ficheiros são guardados numa lista de strings (conjunto de caracteres), que tem de ser gravado ao fecho da aplicação e agora tem de ser lido para repor o menu. Primeiro, inicializamos a lista com as entradas guardadas utilizando o readListEntry(). Depois, num ciclo for, criamos uma entrada de menu para cada item da lista.

Finalmente, apenas temos de tomar conta da geometria da nossa janela. Nós lemos na aparência através de uma variável QSize contendo um valor x e y para o comprimento e altura da janela. Como a janela é inicializada através da KTMainWindow, não temos de nos preocupar com o valor de defeito e apenas utilizamos resize() se a entrada não estiver vazia.

O que resta explicar na construção da nossa aplicação é que inicialmente temos de desactivar comandos do utilizador disponíveis que o não podem estar no caso de algumas instâncias não corresponderem aos critérios necessários. Estes são gravação de ficheiros e operações que utilizem a área de transferência. Durante o tempo de vida da aplicação, temos de tomar conta destes várias vezes, mas isso é bastante fácil. A estrutura apenas nos dá dois métodos para (des)activar itens de barras de menu e ferramentas com apenas uma chamada de método de cada vez.

Executar

Durante a secção anterior, apenas veriicaos o que acontecia durante a chamada de construção da nossa instância do KScribbleApp que nos disponibiliza a janela principal. Após regressar à função main(), temos de chamar show() para mostrar a janela. O que é diferente de qualquer KApplication ou QApplication aqui é que quando estamos a utilizar KTMainWindow como a instância para o nosso widget principal, não temos de o definir com setMainWidget(). isto é feito pelo próprio KTMainWindow e não temos de nos preocupar com isso. A única coisa que resta então é interpretar a linha de comandos. Obtemos a opção de linha de comandos e perguntamos, se int argc é > 1, o que indica que o utilizador chamou a nossa aplicação com kscribble filename_to_open. A nossa janela recebe então o pedido de abertura do ficheiro pelo se nome e chama openDocumentFile() com o nome do ficheiro.

A última linha da função main() faz o trabalho conhecido: executa a instância da aplicação e o programa entra no ciclo de evento.

Agora, na secção A Função main(), nós começamos a separar o processo de execução através de if( app.isRestored() ) e descrevemos o processo normal de invocação. De seguida daremos uma introdução à gestão de sessão e como a nossa aplicação a utiliza.

Invocação por Gestão de Sessão

Como dissemos, a função main() testa, se a aplicação foi invocada pelo gostor de sessão. O gestor de sessão é responsável por gravar o estado actual de todas as janelas de aplicação abertas no ambiente de trabalho do utilizador e tem de os repor quando o utilizador entrar da próxima vez, o que significa que a aplicação não é iniciada pelo utilizador mas invocada automaticamente. A parte do código executado foi:

6    if (app.isRestored())
7    {
8       RESTORE(KScribbleApp);
9    }

Em A Função main(), nós afirmamos que nós testamos a invocação perguntando à app.isRestored(). Depois a linha 8 é executada. Parece uma afirmação simples, mas de facto irá resultar num processo de execução complexo que queremos seguir nesta secção.

A própria RESTORE() é uma macro disponibilizada pela KTMainWindow. Expande-se para o seguinte código:

if (app.isRestored()){
  int n = 1;
  while (KTMainWindow::canBeRestored(n)){
    (new KScribbleApp)->restore(n);
    n++;
  }
}

Isto irá repor todas as janelas de aplicação da classe KScribbleApp criando as instâncias e chamando restore() para a nova janela. É importante compreender que se a nossa aplicação utiliza vários widgets diferentes que herdam KTMainWindow, você tem de expandir a macro e determinar o tipo de widgets de topo utilizando KTMainWindow::classNameOfToplevel(n) em vez da classe KScribbleApp. O método restore() lê então a parte do ficheiro de sessão que contém a informação sobre a janela. Como a KTMainWindow guarda tudo isto por nós, não temos de nos preocupar com mais nada. Apenas informação que pertence à nossa instância específica do KScribbleApp tem de ser então encontrada. Normalmente isto seria um ficheiro temporário que foi criado para guardar o documento ou outra initialização de que poderiamos necessitar. Para obter esta informação de reposição, nós apenas temos de passar por cima de dois métodos virtuais de KTMainWindow, saveProperties() e readProperties(). A informação que temos de gravar no final da sessão é se o documento correntemente aberto está ou não modificado e o nome do ficheiro. Se o ficheiro estiver modificado, obteremos um nome de ficheiro temporário para o gravar. No início da sessão, esta informação é agora utilizada para repor o conteudo do documento:

void KScribbleApp::readProperties(KConfig*)
{
  QString filename = config->readEntry("filename","");
  bool modified = config->readBoolEntry("modified",false);
  if( modified ){
    bool b_canRecover;
    QString tempname = kapp->checkRecoverFile(filename,b_canRecover);

    if(b_canRecover){
      doc->openDocument(tempname);
      doc->setModified();
      QFileInfo info(filename);
      doc->pathName(info.absFilePath());
      doc->title(info.fileName());
      QFile::remove(tempname);
    }
  }
  else if(!filename.isEmpty()){
  doc->openDocument(filename);
  }
  setCaption(kapp->appName()+": "+doc->getTitle());
}               
Aqui, a linha kapp->checkRecoverFile() parece um pouco estranha, pois o b_canRecover não está inicializado. Isto é feito pelo método que o define para true, se existir um ficheiro de reposição. Como apenas gravamos um documento num ficheiro de recuperação se foi modificado, definimos o bit de modificado directamente para indicar que a informação não foi gravada para o ficheiro correspondente. Também temos de ter em conta que o ficheiro de recuperação tem outro nome de ficheiro diferente do ficheiro original que foi aberto. Assim, temos de colocar o nome de ficheiro e caminho do antigo nome de ficheiro. Finalmente, temos a informação que queriamos recuperar e podemos apagar o ficheiro temporário através do gestor de sessão.

Sumário:

Durante este capítulo, ficou a saber como a aplicação se inicia quer por invocação normal do utilizador ou pelo gestor de sessão. Verificamos todo o código para aprender como as partes do interface visual da aplicação são construidas bem como inicializar os atributos através de entradas em ficheiros de configuração. Agora podemos executar a aplicação esqueleto para testar estas funções e ver como a janela do programa reage.

3.4 Conteudos Adicionais de Projectos KDevelop

Além do código fonte disponibilizado, os projectos KDevelop contêm imensas outras partes adicionais que são do interesse do programador. Estas são:

Excepto a documentação API, estes elementos do projecto serão instalados juntamente com o binário da aplicação. Como o esqueleto do projecto tem de ser o mais aberto possível, tem de adaptar estas partes no sentido dos objectivos do seu projecto. Estas são primeiro editar os icons disponibilizados. Isto dará à sua aplicação um identificador único através do qual o utilizador pode determinar a sua aplicação visualmente em menus de gestão de janelas. O ficheiro .kdelnk é um ficheiro que instala a sua aplicação no kpanel no menu Aplicações. Este tem de ser editado definindo o caminho de instalação que será discutido mais tarde neste manual. Finalmente, a documentação que você irá disponibilizar ao utilizador está escrita em SGML. Isto torna muito fácil criar várias apresentações diferentes para a mesma fonte. Por defeito, o KDevelop pode criar um conjunto de ficheiros HTML a partir desta fonte, para projectos KDE isto irá automaticamente utilizar o programa ksgml2html para adicionar uma aparência e utilização consistente tipo KDE à documentação. Numa secção posterior, iremos ver como a fonte SGML é editada e o que temos de observar na instalação no utilizador final.

Finalmente, a documentação API (Interface de Programação da Aplicação) permite-lhe e a outros programadores entender rapidamente o código e utilizar as classes sem terem de adivinhar qual o objectivo de cada classe. Iremos aprender como expandir a documentação API num passo posterior, por ora basta saber que a documentação é gerada pelo programa KDoc, que processa os ficheiros de cabeçalho e cria o resultado final em HTML, pelo que toda a documentação é colocada nos cabeçalhos.

Página seguinte Página anterior Índice geral