]> Introdução à Arquitectura do KDE Bernd Gehrmann
bernd@tdevelop.org
2001 2002 Bernd Gehrmann &FDLNotice; Esta documentação dá uma ideia geral sobre a Plataforma de Desenvolvimento do KDE KDE arquitectura desenvolvimento programação
Estrutura da biblioteca Bibliotecas por nome tdecore A biblioteca 'tdecore' é a plataforma aplicacional básica para todos os programa baseados no KDE. Ela fornece o acesso ao sistema de configuração, ao tratamento da linha de comandos, o carregamento e manipulação de ícones, alguns itens básicos de comunicação entre processos, tratamento de ficheiros e muitos outros utilitários. tdeui A biblioteca tdeui fornece vários elementos gráficos e janelas que o Qt não tem ou que tem mas com menos funcionalidades. Inclui também vários elementos itens que são sub-classes das versões do Qt e que se integram melhor com o ambiente do KDE no que respeita às preferências do utilizador. kio A biblioteca kio contém funcionalidades para E/S assíncronas e transparentes na rede, assim como o tratamento de tipos MIME. Também contém a janela de ficheiros do KDE e as suas classes auxiliares. kjs A biblioteca kjs contém uma implementação de JavaScript. khtml A biblioteca khtml contém a componente KHTML, um elemento de navegação em HTML, com uma API e um processador de DOM e que inclui interfaces para Java e JavaScript. Classes agrupadas Esqueleto de uma aplicação básica - classes necessárias por quase todas as aplicações. <ulink url="kdeapi:tdecore/TDEApplication" >TDEApplication</ulink > Inicializa e controla uma aplicação do KDE. <ulink url="kdeapi:tdecore/KUniqueApplication" >KUniqueApplication</ulink > Certifica-se que só uma instância de uma determinada aplicação poderá correr em simultâneo. <ulink url="kdeapi:tdecore/TDEAboutData" >TDEAboutData</ulink > Contém as informações da janela Acerca. <ulink url="kdeapi:tdecore/TDECmdLineArgs" >TDECmdLineArgs</ulink > Processamento de argumentos da linha de comandos. Gestão de configurações - acesso à base de dados hierárquica de configuração do KDE, as configurações globais e os recursos da aplicação. <ulink url="kdeapi:tdecore/TDEConfig" >TDEConfig</ulink > Fornece o acesso à base de dados de configuração do KDE. <ulink url="kdeapi:tdecore/KSimpleConfig" >KSimpleConfig</ulink > Acesso a ficheiros de configuração simples, não hierárquicos. <ulink url="kdeapi:tdecore/KDesktopFile" >KDesktopFile</ulink > Acesso a ficheiros .desktop. <ulink url="kdeapi:tdecore/TDEGlobalSettings" >TDEGlobalSettings</ulink > Um acesso conveniente a configurações não-específicas de uma aplicação. Tratamento de ficheiros e URLs - descodificação de URLs, ficheiros temporário, etc. <ulink url="kdeapi:tdecore/KURL" >KURL</ulink > Representa e processa os URLs. <ulink url="kdeapi:tdecore/KTempFile" >KTempFile</ulink > Cria ficheiros únicos para dados temporários. <ulink url="kdeapi:tdecore/KSaveFile" >KSaveFile</ulink > Permite gravar os ficheiros de forma atómica. Comunicação inter-processos - classes auxiliares de DCOP e invocação de sub-processos. <ulink url="kdeapi:tdecore/TDEProcess" >TDEProcess</ulink > Invoca e controla os processos-filhos. <ulink url="kdeapi:tdecore/KShellProcess" >KShellProcess</ulink > Invoca os processos-filhos através de uma linha de comandos. <ulink url="kdeapi:tdesu/PtyProcess" >PtyProcess</ulink > Comunicação com os processos-filhos através de um pseudo-terminal. <ulink url="kdeapi:tdecore/KIPC" >KIPC</ulink > Mecanismo simples de IPC com ClientMessages do X11. <ulink url="kdeapi:dcop/DCOPClient" >DCOPClient</ulink > Troca de mensagens DCOP. <ulink url="kdeapi:tdecore/KDCOPPropertyProxy" >KDCOPPropertyProxy</ulink > Uma classe 'proxy' que publica as propriedades do Qt através do DCOP. <ulink url="kdeapi:tdeui/KDCOPActionProxy" >KDCOPActionProxy</ulink > Uma classe 'proxy' que publica uma interface de DCOP para as acções. Classes utilitárias - gestão de memória, expressões regulares, manipulação de cadeias de caracteres, números aleatórios <ulink url="kdeapi:tdecore/KRegExp" >KRegExp</ulink > Expressões regulares POSIX. <ulink url="kdeapi:tdecore/KStringHandler" >KStringHandler</ulink > Uma interface extravagante para manipular cadeias de caracteres. <ulink url="kdeapi:tdecore/KZoneAllocator" >KZoneAllocator</ulink > Um alocador de memória eficiente para grandes grupos de objectos pequenos. <ulink url="kdeapi:tdecore/KRandomSequence" >KRandomSequence</ulink > Um gerador de números pseudo-aleatórios. Aceleradores de teclado - classes que ajudam a estabelecer associações de teclas consistentes em todo o ambiente de trabalho. <ulink url="kdeapi:tdecore/KAccel" >KAccel</ulink > Uma colecção de atalhos de teclado. <ulink url="kdeapi:tdecore/KStdAccel" >KStdAccel</ulink > Um acesso fácil aos atalhos de teclado comuns. <ulink url="kdeapi:tdecore/TDEGlobalAccel" ></ulink > Uma colecção de atalhos de teclado ao nível do sistema. Processamento de imagens - leitura e manipulação de ícones. <ulink url="kdeapi:tdecore/KIconLoader" >KIconLoader</ulink > Carrega os ícones de uma forma dependente do tema. <ulink url="kdeapi:tdecore/KIconTheme" >KIconTheme</ulink > Classes auxiliares para o KIconLoader. <ulink url="kdeapi:tdecore/KPixmap" >KPixmap</ulink > Uma classe de imagens com capacidades de gestão de tons extendidas. <ulink url="kdeapi:tdeui/KPixmapEffect" >KPixmapEffect</ulink > Efeitos de imagens como gradientes e padrões. <ulink url="kdeapi:tdeui/KPixmapIO" >KPixmapIO</ulink > Uma conversão rápida de QImage para QPixmap. 'Drag and Drop' - arrastar objectos de cores e URLs. <ulink url="kdeapi:tdecore/KURLDrag" >KURLDrag</ulink > Um objecto de arrastamento de URLs. <ulink url="kdeapi:tdeui/KColorDrag" >KColorDrag</ulink > Um objecto de arrastamento para cores. <ulink url="kdeapi:tdecore/KMultipleDrag" >KMultipleDrag</ulink > Permite construir objectos de arrastamento a partir de vários outros objectos. Completação Automática <ulink url="kdeapi:tdecore/KCompletion" >KCompletion</ulink > Auto-completação genérica de cadeias de caracteres. <ulink url="kdeapi:kio/KURLCompletion" >KURLCompletion</ulink > Completação automática de URLs. <ulink url="kdeapi:kio/KShellCompletion" >KShellCompletion</ulink > Completação automática de executáveis. Elementos gráficos - classes de listas, réguas, selecção de cores, etc. <ulink url="kdeapi:tdeui/KListView" >KListView</ulink > Uma variante da QListView que usa as configurações de sistema do KDE. <ulink url="kdeapi:tdeui/KListView" >KListBox</ulink > Uma variante da QListBox que usa as configurações de sistema do KDE. <ulink url="kdeapi:tdeui/KListView" >KIconView</ulink > Uma variante da QIconView que usa as configurações de sistema do KDE. <ulink url="kdeapi:tdeui/KListView" >KLineEdit</ulink > Uma variante da QLineEdit com o suporte de completação. <ulink url="kdeapi:tdeui/KComboBox" >KComboBox</ulink > Uma variante da QComboBox com o suporte de completação. <ulink url="kdeapi:tdeui/KFontCombo" >KFontCombo</ulink > Uma lista para seleccionar tipos de letra. <ulink url="kdeapi:tdeui/KColorCombo" >KColorCombo</ulink > Uma lista para seleccionar cores. <ulink url="kdeapi:tdeui/KColorButton" >KColorButton</ulink > Um botão para seleccionar cores. <ulink url="kdeapi:tdeui/KURLCombo" >KURLCombo</ulink > Uma lista para seleccionar nomes de ficheiros e URLs. <ulink url="kdeapi:kfile/KURLRequester" >KURLRequester</ulink > Um campo de texto para seleccionar nomes e URLs de ficheiros. <ulink url="kdeapi:tdeui/KRuler" >KRuler</ulink > Uma régua. <ulink url="kdeapi:tdeui/KAnimWidget" >KAnimWidget</ulink > animações. <ulink url="kdeapi:tdeui/KNumInput" >KNumInput</ulink > Um item para introduzir números. <ulink url="kdeapi:tdeui/KPasswordEdit" >KPasswordEdit</ulink > Um item para introduzir senhas. Janelas - janelas completas para seleccionar ficheiros, cores e tipos de letra. <ulink url="kdeapi:kfile/KFileDialog" >KFileDialog</ulink > Uma janela de selecção de ficheiros. <ulink url="kdeapi:tdeui/KColorDialog" >KColorDialog</ulink > Uma janela de selecção de cores. <ulink url="kdeapi:tdeui/KFontDialog" >KFontDialog</ulink > Uma janela de selecção de tipos de letra. <ulink url="kdeapi:kfile/KIconDialog" >KIconDialog</ulink > Uma janela de selecção de ícones. <ulink url="kdeapi:tdeui/KKeyDialog" >KKeyDialog</ulink > Uma janela para editar combinações de teclas. <ulink url="kdeapi:tdeui/KEditToolBar" >KEditToolBar</ulink > Um diálogo para editar barras de ferramentas. <ulink url="kdeapi:tdeui/KTipDialog" >KTipDialog</ulink > Uma janela de Dica-do-Dia. <ulink url="kdeapi:tdeui/KAboutDialog" >KAboutDialog</ulink > Uma janela Acerca. <ulink url="kdeapi:tdeui/KLineEditDlg" >KLineEditDlg</ulink > Uma janela simples para introduzir texto. <ulink url="kdeapi:kfile/KURLRequesterDlg" >KURLRequesterDlg</ulink > Uma janela simples para introduzir URLs. <ulink url="kdeapi:tdeui/KMessageBox" >KMessageBox</ulink > Uma janela para assinalar erros e avisos. <ulink url="kdeapi:tdeui/KPasswordDialog" >KPasswordDialog</ulink > Uma janela para introduzir senhas. Acções e GUI em XML <ulink url="kdeapi:tdeui/KAction" >KAction</ulink > Uma abstracção de uma acção que poderá ser associada a menus e barras de ferramentas. <ulink url="kdeapi:tdeui/KActionCollection" >KActionCollection</ulink > Um conjunto de acções. <ulink url="kdeapi:tdeui/KXMLGUIClient" >KXMLGUIClient</ulink > Um fragmento gráfico que consiste numa colecção de acções e uma árvore de DOM que representa a posição delas na GUI. <ulink url="kdeapi:kparts/KPartManager" >KPartManager</ulink > Faz a gestão da activação dos clientes XMLGUI. 'Plugins' e Componentes <ulink url="kdeapi:tdecore/KLibrary" >KLibrary</ulink > Representa uma biblioteca carregada dinamicamente. <ulink url="kdeapi:tdecore/KLibrary" >KLibLoader</ulink > Carregamento de bibliotecas dinâmicas. <ulink url="kdeapi:tdecore/KLibFactory" >KLibFactory</ulink > Uma fábrica de objectos para 'plugins'. <ulink url="kdeapi:kio/KServiceType" >KServiceType</ulink > Representa um tipo de serviço. <ulink url="kdeapi:kio/KService" >KService</ulink > Representa um serviço. <ulink url="kdeapi:kio/KMimeType" >KMimeType</ulink > Representa um tipo MIME. <ulink url="kdeapi:kio/KServiceTypeProfile" >KServiceTypeProfile</ulink > Preferências do utilizador para os mapeamentos dos tipos MIME. <ulink url="kdeapi:kio/KServiceTypeProfile" >KTrader</ulink > Pesquisa de serviços. Gráficos Gráficos de baixo nível com o QPainter Desenhar no QPainter O modelo de imagens de baixo nível do Qt é baseado nas capacidades oferecidas pelo X11 e por outros sistemas de janelas para os quais o Qt foi implementado. Mas também as extende, implementando funcionalidades adicionais como as transformações arbitrárias por afinidade para texto e imagens. A classe gráfica central para o desenho 2D com o Qt é a QPainter. Ela poderá desenhar num QPaintDevice. Existem três dispositivos de pintura implementados: um é o QWidget que representa um elemento gráfico no ecrã. A outra é o QPrinter que representa uma impressora e que produz o resultado em &PostScript;. A terceira é a QPicture que guarda os comandos de desenho e que poderá gravá-los em disco e reproduzi-los depois. Uma formato possível para os comandos de desenho é a norma SVG da W3C. Como tal, é possível reaproveitar o código de desenho que você usa para mostrar num item gráfico ou para imprimir, usando as mesmas funcionalidades suportadas. Claro que, na prática, o código é usado num contexto ligeiramente diferente. Desenhar num item gráfico é quase exclusivamente feito no método paintEvent() da classe de um elemento gráfico. void ElementoXPTO::paintEvent() { QPainter p(this); // Configurar o pintor // Usar o pintor } Ao desenhar numa impressora, você terá de se certificar que usa o QPrinter::newPage() para terminar uma página e começar uma nova - algo que naturalmente não é relevante ao desenhar elementos gráficos. Também, durante a impressão, você poderá querer usar a classe de métricas do dispositivo para poder calcular as coordenadas. Transformações Por omissão, ao usar o QPainter, ele desenha no sistema de coordenadas natural do dispositivo usado. Isto significa que, se você desenhar uma linha horizontal no eixo horizontal com um tamanho de 10 unidades, ele será pintado ao longo do ecrã com um tamanho de 10 pixels. Contudo, o QPainter pode aplicar várias transformações antes de desenhar propriamente as formas e as curvas. Uma transformação por afinidade mapeia as coordenadas X e Y linearmente em X' e Y' de acordo com A matriz 3x3 desta equação poderá ser configurada com o método QPainter::setWorldMatrix() e é do tipo QWMatrix. Normalmente, esta é a matriz identidade, isto é, o m11 e o m22 are one, e os outros parâmetros são zero. Existem basicamente três grupos diferentes de transformações: Translações Estas movem todos os pontos de um objecto de uma determinada quantidade numa dada direcção. A matriz de translação poderá ser obtida, invocando o método 'm.translate(dx, dy)' para uma QWMatrix. Isto corresponde à matriz Redimensionamento Esta matriz aumenta ou encolhe as coordenadas de um objecto, tornando-o maior ou menor sem o distorcer. Uma transformação de escala poderá ser aplicada a uma QWMatrix se invocar o m.scale(sx, sy). Isto corresponde à matriz Configurando um dos parâmetros como negativo, uma pessoa poderá obter um espelho do sistema de coordenadas. Inclinação Uma distorção do sistema de coordenadas com dois parâmetros. Uma transformação por inclinação poderá ser aplicada se chamar m.shear(sh, sv), correspondendo à matriz Rotação Isto roda um objecto. Uma transformação por rotação poderá ser aplicada se chamar m.rotate(alfa). Lembre-se que o ângulo tem de ser dado em graus, não como um ângulo matemático! A matriz correspondente é Repare que uma rotação é equivalente a uma combinação de uma escala com uma inclinação. Aqui estão algumas imagens que mostram o efeito das transformações elementares à nossa mascote: a) Normal b) Rodado em 30 graus c) inclinado em 0,4 d) Espelhado As transformações podem ser combinadas, multiplicando as matrizes elementares. Repare que as operações com matrizes não comutativas de um modo geral, como tal o efeito combinado de uma concatenação depende da ordem pela qual as matrizes são multiplicadas. Definir os atributos dos traços O desenho das linhas, curvas e contornos dos polígonos pode ser modificado se aplicar um traço especial com o QPainter::setPen(). O argumento desta função é um objecto QPen. As propriedades gravadas nele são o estilo, a cor, o estilo da junção e o estilo dos extremos. O estilo do traço é um membro do tipo enumerado Qt::PenStyle e poderá ter um dos seguintes valores: O estilo da junção é um membro do tipo enumerado Qt::PenJoinStyle. Ele indica como é que a junção entre várias linhas anexadas umas às outras é desenhada. Ela poderá ter um dos seguintes valores: a) MiterJoin c) BevelJoin b) RoundJoin O estilo dos extremos é um membro do tipo enumerado Qt::PenCapStyle e corresponde à forma como os extremos das linhas são desenhados. Poderá ser igual a um dos valores da seguinte tabela: a) FlatCap b) SquareCap c) RoundCap Definir os atributos do preenchimento O estilo de preenchimento dos polígonos, dos círculos e dos rectângulo poderá ser modificado se definir um pincel especial com o QPainter::setBrush(). Esta função recebe um objecto QBrush como argumento. Os pincéis podem ser construídos de quatro formas diferentes: QBrush::QBrush() - Isto cria um pincel que não preenche as formas geométricas. QBrush::QBrush(BrushStyle) - Isto cria um pincel preto com um dos padrões predefinidos que são mostrados em baixo. QBrush::QBrush(const QColor &, BrushStyle) - Isto cria um pincel colorido com um dos seguinte padrões mostrados em baixo. QBrush::QBrush(const QColor &, const QPixmap) - Isto cria um pincel colorido com o padrão personalizado que você passar como segundo parâmetro. Um estilo de pincel predefinido pertence ao tipo enumerado Qt::BrushStyle. Aqui está uma imagem com todos os padrões predefinidos: Uma outra forma de personalizar o comportamento do pincel é usando a função QPainter::setBrushOrigin(). Cor As cores têm um papel activo, quer a traçar as curvas, quer a preencher as formas geométricas. No Qt, as cores são representadas pela classe QColor. O Qt não suporta as funcionalidades gráficas avançadas, como os perfis de cores ICC e a correcção de cores. As cores são normalmente definidas, indicando os valores das componentes vermelha, verde e azul, dado que o modelo RGB é a forma como os pixels são compostos num monitor. É também possível usar o matiz, a saturação e o valor. Esta representação HSV é a que você usa na janela de cores do Gtk, p.ex. no GIMP. Aí, o matiz corresponde ao ângulo na roda de cores, enquanto a saturação corresponde à distância ao centro do círculo. O valor pode ser escolhido com uma barra em separado. Outras configurações Normalmente, quando você desenha num dispositivo de pintura, os pixels que você desenho substituem os que lá estavam anteriormente. Isto significa que, se você pintar uma dada região com uma cor vermelho e pintar a mesma região com uma cor azul depois, só a cor azul ficará visível. O modelo de imagens do Qt não suporta a transparência, i.e., uma forma de misturar o fundo preenchido e os desenhos. Contudo, existe uma forma simples de combinar o desenho e o fundo com operadores booleanos. O método QPainter::setRasterOp() define o operador usado, que vem do tipo enumerado RasterOp. O valor por omissão é o CopyROP que ignora o fundo. Outra escolha normal é a XorROP. Se você pintar uma linha a preto com este operador numa imagem colorida, então a área coberta será invertida. Este efeito é usado, por exemplo, para criar as selecções tracejadas dos programas de manipulação de imagens que são conhecidas por "formigas a marchar". Desenhar primitivas gráficas Na secção seguinte iremos listar os elementos gráficos primitivos suportados pelo QPainter. A maioria deles existem em várias versões alternativas que recebem um conjunto diferente de argumentos. Por exemplo, os métodos que lidam com rectângulos normalmente recebem um QRect como argumento, ou então um conjunto de quatro inteiros. Desenhar um único ponto - drawPoint(). Desenhar linhas - drawLine(), drawLineSegments() e drawPolyLine(). Desenhar e preencher rectângulos - drawRect(), drawRoundRect(), fillRect() e eraseRect(). Desenhar e preencher círculos, elipses ou partes deles - drawEllipse(), drawArc(), drawPie e drawChord(). Desenhar e preencher polígonos gerais - drawPolygon(). Desenhar curvas Bezier - drawQuadBezier() [drawCubicBezier no Qt 3.0]. Desenhar imagens O Qt oferece duas classes muito diferentes para representar as imagens. A QPixmap corresponde directamente aos objectos das imagens no X11. As imagens são objectos do lado do servidor e podem - numa placa gráfica moderna - até mesmo ser gravadas directamente na memória da placa. Isto torna bastante eficiente a transferência de imagens para o ecrã. As imagens também funcionam como um equivalente, fora do ecrã , dos elementos gráficos - a classe QPixmap é uma subclasse da QPaintDevice, por isso você poderá desenhar nela com um QPainter. As operações elementares de desenho são normalmente aceleradas pelos dispositivos gráficos modernos. Daí, um padrão de uso normal é usar as imagens para fazer duplo-'buffering'. Isto significa que, em vez de desenhar directamente num elemento gráfico, você desenha num objecto temporário de imagem e usa a função bitBlt para transferir a imagem para o elemento gráfico. Para os desenhos complexos, isto ajuda a evitar intermitências. Em contraste, os objectos QImage residem do lado do cliente. A sua ênfase é na fornecer um acesso directo aos pixels da imagem. Isso torna-os úteis para manipular imagens e para coisas como a leitura e gravação em disco (o método load() da QPixmap recebe uma QImage como passo intermédio). Por outro lado, desenhar uma imagem num elemento gráfico é uma operação relativamente dispendiosa, dado que implica uma transferência para o servidor X, o que ainda poderá levar algum tempo, especialmente para imagens grandes e para servidores remotos. Dependendo da profundidade de cor, a conversão de uma QImage para uma QPixmap pode necessitar de ajuste de cores. Desenhar texto O texto poderá ser desenhado com uma das variantes do método QPainter::drawText(). Estas desenham uma QString quer num dado ponto, quer num dado rectângulo, usando o tipo de letra definido pelo QPainter::setFont(). Existe também um parâmetro que recebe uma combinação do tipo OU de algumas opções dos tipos enumerados Qt::AlignmentFlags e Qt::TextFlags A partir da versão 3.0, o Qt toma conta da disposição completa do texto, mesmo para as línguas escritas da direita para a esquerda. Uma forma mais avançada de mostrar o texto marcado é usando a classe QSimpleRichText. Os objectos desta classe podem ser construídos com um pedaço de texto que usa um sub-conjunto das marcas de HTML, o qual é bastante rico e até fornece tabelas. O estilo do texto pode ser personalizado com uma QStyleSheet (a documentação das marcas pode também ser encontrada aqui). Logo que o objecto de texto formatado tenha sido construído, pode ser desenhado num elemento gráfico ou noutro dispositivo de pintura com o método QSimpleRichText::draw(). Imagens estruturadas com o QCanvas A QPainter oferece um modelo de imagens poderosos para desenhar em elementos gráficos e imagens. Contudo, também poderá ser complicadíssimo de usar. De cada vez que o seu elemento gráfico recebe um evento de desenho, ele terá de analisar o QPaintEvent::region() ou o QPaintEvent::rect() que tem de ser desenhado de novo. Aí, ele terá de configurar um QPainter e desenhar todos os objectos que se sobrepõem com essa região. Por exemplo, imagine um programa de gráficos vectoriais que permita arrastar objectos como polígonos, círculos e grupos de outros objectos. De cada vez que esses objectos se movem um pouco, o tratador para os eventos do rato do elemento gráfico irá despoletar um evento para a área toda coberta pelos objectos na sua posição antiga e na sua posição nova. Descobrir quais são as actualizações necessárias e fazê-las de forma eficiente poderá ser difícil e poderá também entrar em conflito com a estrutura orientada por objectos do código-fonte do programa. Como alternativa, o Qt contém a classe QCanvas, na qual você coloca os objectos gráficos, como os polígonos, o texto e as imagens. Você também poderá também fornecer itens adicionais se criar uma subclasse de QCanvasItem ou uma das suas subclasses especializadas. Uma área de desenho ('canvas') poderá ser mostrada no ecrã por um ou mais elementos gráficos da classe QCanvasView, a qual você terá de criar uma subclasse para tratar das interacções com o utilizador. O Qt tratará de todas as actualizações do desenho dos objectos na área visível, quer sejam provocadas pela exposição da janela, quer pela criação ou modificação dos objectos ou ainda por qualquer outra razão. Usando o duplo-'buffering', isto poderá ser feito de uma forma eficiente e livre de intermitências. Os itens da área de desenho podem-se sobrepor uns aos outros. Neste caso, o visível depende da ordem do 'z' que pode ser atribuída pelo QCanvasItem::setZ(). Os itens podem também ser tornados visíveis ou invisíveis. Você pode também indicar um fundo a ser desenhado "por detrás" de todos os itens e uma imagem de primeiro plano. Para associar os eventos do rato com os objectos na área de desenho, existe o método QCanvas::collisions() que devolve uma lista dos itens que se sobrepõem em qualquer ponto. Aqui mostramos uma imagem de uma vista sobre a área de desenho em acção: Aqui, a malha é desenhada no fundo. Para além disso, existe um item QCanvasText item e um QCanvasPolygon violeta. A borboleta é um QCanvasPixmap. Ele tem áreas transparentes, por isso é possível ver os itens de baixo através dele. Um tutorial sobre a utilização do QCanvas para criar jogos baseados em imagens poderá ser encontrado aqui. Gráficos 3D com o OpenGL Interface de baixo nível A norma de facto para desenhar gráficos 3D hoje em dia é o OpenGL. As implementações desta especificação vêm com o Microsoft Windows, o Mac OS X e o XFree86 e muitas vezes suportam as funcionalidades de aceleração por 'hardware' oferecidas pelas placas gráficas modernas. O OpenGL em si só lida com o desenho de uma determinada área do ecrã através de um contexto GL e não tem nenhuma interacção com a plataforma do ambiente O Qt fornece o item QGLWidget que encapsula uma janela com um contexto GL associado. Basicamente, você poderá utilizá-la se criar uma sub-classe dela e implementar de novo alguns métodos. Em vez de reimplementar o paintEvent() e usar o QPainter para desenhar o conteúdo do elemento, você irá sobrepor o paintGL() e usar os comandos do GL para desenhar uma cena. A QLWidget irá tomar conta de tornar o seu contexto GL o actual antes de invocar o paintGL() e irá remeter tudo no fim. O método virtual initializeGL() é invocado logo da primeira vez em que o resizeGL() ou o paintGL() são chamados. Isto pode ser usado para construir listas de visualização para os objectos e para fazer as várias inicializações. Em vez de reimplementar o resizeEvent(), você irá sobrepor o resizeGL(). Este poderá ser usado para definir apropriadamente o porto de visualização. Em vez de invocar o update() sempre que o estado da cena é mudado - por exemplo, quando você o anima com um temporizador -, você deverá invocar o updateGL(). Isto irá despoletar uma actualização. De um modo geral, o QGLWidget comporta-se como outro item gráfico, i.e., por exemplo, você poderá processar os eventos normais do rato como de costume, redimensionar a janela e combiná-la com outras numa dada disposição. O Qt contém alguns exemplos de utilização do QGLWidget no seu exemplo demo. Pode-se encontrar uma colecção de tutoriais aqui, assim como mais informações e uma referência ao OpenGL, na página pessoal do OpenGL. Interfaces de alto-nível O OpenGL é uma interface de relativo baixo nível para gráficos 3D. Da mesma forma que o QCanvas dá ao programador uma interface de maior nível com detalhes para os objectos e as suas propriedades, existem também interfaces de alto nível para os gráficos 3D. Uma das mais conhecidas é o Open Inventor. Sendo originalmente uma tecnologia desenvolvida pela SGI, existe hoje em dia também a implementação 'open-source' que é a Coin, complementada por uma interface para a plataforma e para o Qt chamada SoQt. O conceito básico do Open Inventor é o de uma cena. Uma cena poderá ser carregada a partir do disco e gravada num formato ligeiramente relacionado com o VRML. Uma cena consiste numa colecção de objectos chamados nós. O Inventor já contém uma rica colecção de nós reutilizáveis, como cubos, cilindros e malhas, complementados por fontes de luz, materiais, câmaras, etc. Os nós são representados por classes de C++ e podem ser combinados e herdados. Pode-se encontrar uma introdução ao Inventor aqui (de um modo geral, você poderá substituir todas as menções ao SoXt por SoQt neste artigo). Interface do utilizador O padrão de acções Definir os menus e as barras de ferramentas em XML Introdução Embora o padrão de acções permita encapsular as acções despoletadas pelo utilizador num objecto que possa estar "ligado" noutro sítio qualquer nos menus ou nas barras de ferramentas, ele não resolve por si só o problema de construir os próprios menus. Em particular, você terá de criar todos os menus em código de C++ e inserir explicitamente as acções por uma determinada ordem, segundo as considerações dos guias de estilo para as acções normais. Isto torna bastante difícil para os utilizadores poderem personalizar os menus ou alterar os menus ou os atalhos de teclado de acordo com as suas necessidades, sem ter de alterar o código-fonte. Este problema é resolvido através de um conjunto de classes chamado XMLGUI. Basicamente, isto separa as acções (codificadas em C++) da sua aparência nas barras de menu e de ferramentas (codificadas em XML). Sem modificar nenhum código-fonte, os menus podem simplesmente ser personalizados, ajustando um ficheiro de XML. Para além disso, ajuda a garantir que as acções normais (como o FicheiroAbrir ou o AjudaAcerca) aparecem nas localizações indicadas pelos guias de estilo. O XMLGUI é especialmente importante para os programas modulares, em que os itens que aparecem no menu podem resultar de vários 'plugins' ou componentes diferentes. A classe do KDE para as janelas de topo, a KMainWindow, herda de KXMLGUIClient e, por isso, suporta a XMLGUI logo de raiz. Todas as acções criadas dentro dela terão de ter a actionCollection() do cliente como 'pai'. Uma chamada ao createGUI() irá então criar o conjunto completo de barras de menu e de ferramentas definidas no ficheiro XML da aplicação (que tem, convencionalmente, o sufixo ui.rc). Um exemplo: Menu do KView No seguinte exemplo, iremos ver o visualizador de imagens KView do KDE. Ele tem um ficheiro ui.rc chamado kviewui.rc que é instalado com o código em Makefile.am rcdir = $(kde_datadir)/kview rc_DATA = kviewui.rc Aqui está um excerto do ficheiro kviewui.rc. Por questões de simplicidade, mostramos apenas a definição no menu View (Ver). <!DOCTYPE kpartgui> <kpartgui name="kview"> <MenuBar> <Menu name="view" > <Action name="zoom50" /> <Action name="zoom100" /> <Action name="zoom200" /> <Action name="zoomMaxpect" /> <Separator/> <Action name="fullscreen" /> </Menu> </MenuBar> </kpartgui> A componente correspondente da configuração em C++ é: KStdAction::zoomIn ( this, SLOT(slotZoomIn()), actionCollection() ); KStdAction::zoomOut ( this, SLOT(slotZoomOut()), actionCollection() ); KStdAction::zoom ( this, SLOT(slotZoom()), actionCollection() ); new KAction ( i18n("&Half size"), ALT+Key_0, this, SLOT(slotHalfSize()), actionCollection(), "zoom50" ); new KAction ( i18n("&Normal size"), ALT+Key_1, this, SLOT(slotDoubleSize()), actionCollection(), "zoom100" ); new KAction ( i18n("&Double size"), ALT+Key_2, this, SLOT(slotDoubleSize()), actionCollection(), "zoom200" ); new KAction ( i18n("&Fill Screen"), ALT+Key_3, this, SLOT(slotFillScreen()), actionCollection(), "zoomMaxpect" ); new KAction ( i18n("Fullscreen &Mode"), CTRL+SHIFT+Key_F, this, SLOT(slotFullScreen()), actionCollection(), "fullscreen" ); O menu View (Ver), resultante desta definição da interface, fica então semelhante ao que aparece nesta imagem: O ficheiro XML começa com uma declaração do tipo de documento. O DTD do 'kpartgui' pode ser encontrado no código-fonte do 'tdelibs' em tdeui/kpartgui.dtd. O elemento exterior do ficheiro contém o nome da instância da aplicação como atributo. Poderá também conter um número de versão no formato "version=2". Isto é útil quando você lança novas versões de uma aplicação com uma estrutura de menus alterada, p.ex., com mais funcionalidades. Se você fornecer o número da versão no ficheiro ui.rc, o KDE certifica-se que qualquer versão personalizada do ficheiro é eliminada e passa a ser usado o novo ficheiro em alternativa. A próxima linha, a <MenuBar>, contém a declaração de um menu. Você poderá também introduzir uma quantidade qualquer de declarações de <ToolBar> para criar algumas barras de ferramentas. O menu contém um submenu com o nome "view" (ver). Este nome já está predefinido e, por isso, irá ver a versão traduzida de "View" (no caso do português, "Ver") na imagem. Se você declarar os seus próprios submenus, você terá de adicionar explicitamente o título. Por exemplo, o KView tem um submenu com o título "Image" (Imagem) que é declarado da seguinte forma: <Menu name="image" > <text>&amp;Image</text> ... </Menu> Na plataforma do 'automake' do KDE, esses títulos são automaticamente extraídos e colocados no ficheiro .po da aplicação, para que seja usados pelos tradutores. Lembre-se que você tem de representar o marcador de aceleradores "&" na forma compatível com o XML "&amp;". Voltando ao exemplo do KView, o seu menu View contém um conjunto de acções personalizadas: zoom50, zoom100, zoom200, zoomMaxpect e fullscreen, declaradas com um elemento <Action>. O separador nas imagens corresponde ao elemento <Separator>. Você irá reparar que alguns itens do menu não têm um elemento correspondente no ficheiro XML. Estes são as acções-padrão. As acções-padrão são criadas pela classe KStdAction. Quando você cria essas acções na sua aplicação (como no exemplo em C++ acima), elas serão automaticamente introduzidas numa posição prescrita, e possivelmente com uma tecla de atalho. Você poderá procurar essas localizações no ficheiro tdeui/ui_standards.rc no código-fonte do 'tdelibs'. Um exemplo: Barras de Ferramentas no Konqueror Para a discussão das barras de ferramentas, o foco será agora a definição da GUI do Konqueror. Este excerto define a barra de localização, que contém o campo de introdução de URLs. <ToolBar name="locationToolBar" fullWidth="true" newline="true" > <text>Location Toolbar</text> <Action name="clear_location" /> <Action name="location_label" /> <Action name="toolbar_url_combo" /> <Action name="go_url" /> </ToolBar> A primeira coisa que salta à vista é que existem muitos mais atributos que nos menus. Estes incluem: fullWidth: Diz ao XMLGUI que a barra de ferramentas tem a mesma largura que a janela de topo. Dado que este está igual a "false" (falso), a barra de ferramentas só ocupa o espaço necessário e poderão ser colocadas mais barras de ferramentas na mesma linha. newline: Este está relacionado com a opção acima. Se for igual a "true" (verdadeiro), a barra de ferramentas inicia uma nova linha. Caso contrário, poderá ser colocada na mesma linha que a barra anterior. noEdit: Normalmente as barras de ferramentas podem ser personalizadas pelo utilizador, p.ex. na opção ConfiguraçãoConfigurar as Barras de Ferramentas do Konqueror. Se esta opção for igual a "true" (verdadeiro), a barra de ferramentas em questão não fica editável. Isto é importante para as barras de ferramentas que são carregadas com itens na altura da execução, como por exemplo a barra de Favoritos do Konqueror. iconText: Diz ao XMLGUI para mostrar o texto da acção ao lado do ícone. Normalmente, o texto só é mostrado como uma dica quando o cursor do rato se mantiver em cima do ícone durante algum tempo. Os valores possíveis para este atributo são o "icononly" (mostra só o ícone), "textonly" (mostra só o texto), "icontextright" (mostra o texto do lado direito do ícone) e "icontextbottom" (mostra o texto por baixo do ícone). hidden: Se este valor for "true" (verdadeiro), a barra de ferramentas não fica visível inicialmente e deverá ser activada por um item de menu qualquer. position: O valor por omissão para este atributo é o "top", que significa que a barra de ferramentas fica por baixo do menu. Para os programas com várias ferramentas, como os programas gráficos, poderá ser interessante substituir isto por "left" (esquerda), "right" (direita) ou "bottom" (baixo). Menus dinâmicos Obviamente, um ficheiro XML só poderá conter uma descrição estática da interface do utilizador. Normalmente, existem menus que mudam durante a execução. Por exemplo, o menu do Konqueror Localização contém um conjunto de itens Abrir com XPTO com as aplicações que são capazes de abrir um dado ficheiro com um dado tipo MIME. De cada vez que o documento apresentado muda, a lista de itens do menu muda. O XMLGUI está preparado para lidar com estes casos, usando a noção de listas de acções. Uma lista de acções é declarada como um itm no ficheiro XML, mas de facto consiste em várias acções que são associadas ao menu durante a execução. O exemplo acima está implementado com a seguinte declaração no ficheiro XML do Konqueror: <Menu name="file"> <text>&amp;Location</text> ... <ActionList name="openwith"> ... </Menu> A função KXMLGUIClient::plugActionList() é então usada para adicionar as acções a mostrar, enquanto que a função KXMLGuiClient::unplugActionList() remove todas as acções ligadas. A rotina responsável pela actualização é semelhante à seguinte: void JanelaPrincipal::actualizarAccoesAbrirCom() { unplugActionList("openwith"); // Nome declarado no ficheiro XML accoesAbrirCom.clear(); for ( /* iterar pelos serviços relevantes */ ) { KAction *accao = new KAction( ...); accoesAbrirCom.append(accao); } plugActionList("openwith", accoesAbrirCom); } Lembre-se que, em contraste com as acções estáticas, as que aqui são criadas não têm a colecção de acções como 'mãe', como tal você é responsável por removê-las você mesmo. A forma mais simples de o fazer é usando o método accoesAbrirCom.setAutoDelete(true) no exemplo acima. Menus de contexto Os exemplos acima só continham casos em que as barras de menu e de ferramentas de uma janela principal eram criados. Existem os casos em que os processos de criação destes repositórios estão completamente escondidos do programador por detrás da chamada do createGUI() (excepto se você tiver repositórios personalizados). Contudo, existem casos em que você deseja construir outros repositórios e preenchê-los com definições da GUI a partir do ficheiro XML. Um desses exemplos são os menus de contexto. Para obter uma referência a um menu de contexto, você terá de pedir ao criador ('factory') do cliente essa referência: void JanelaPrincipal::menuPedido() { QWidget *m = factory()->container("menu_contexto", this); QPopupMenu *menu = static_cast<QPopupMenu *>(m); menu->exec(QCursor::pos()); } O método KXMLGUIFactory::container() usado acima procura onde encontrar um repositório no ficheiro XML com o nome indicado. Nesse caso, uma definição possível poderia ser semelhante à seguinte: ... <Menu name="menu_contexto"> <Action name="ficheiro_adicionar"/> <Action name="ficheiro_remover"/> </Menu> ... Fornecer ajuda 'online' Tornar um programa intuitivo e fácil de usar envolve um conjunto de funcionalidades que são chamadas normalmente de ajuda 'online'. A ajuda 'online' tem vários objectivos, alguns deles em conflito: por um lado, deverá dar ao utilizador respostas à pergunta "Como é que faço uma determinada tarefa?"; por outro lado, deverá ajudar o utilizador a explorar a aplicação e a encontrar funcionalidades que ainda não conhece. É importante reconhecer que isto só poderá ser conseguido se oferecer vários níveis de ajuda: As dicas de ferramentas são pequenas legendas que aparecem por cima dos elementos da interface gráfica, sempre que o cursor do rato fica algum tempo sobre eles. Elas são especialmente importantes para as barras de ferramentas, onde os ícones nem sempre são suficientes para explicar o intuito de um botão. A ajuda o "O que é isto?" é normalmente uma explicação mais extensa e mais rica sobre um elemento gráfico ou um item do menu. É também mais complicada de usar: nas janelas, pode ser invocadas de duas formas: quer carregando em ShiftF1 ou carregando no ponto de interrogação na barra de título (onde o suporte para a última depende do gestor de janelas). O cursor do rato irá então mudar para uma seta com um ponto de interrogação, onde a janela de ajuda aparece sempre que um elemento da interface for pressionado. A ajuda "o que é isto?" para os itens do menu é normalmente activada por um botão na barra de ferramentas que contém uma seta e um ponto de interrogação. O problema com esta abordagem é que o utilizador não consegue ver se um elemento contém ajudas ou não. Quando o utilizador activa o botão do ponto de interrogação e não obtém nenhuma janela de ajuda quando carregar num elemento da interface do utilizador, ele irá ficar frustrado muito depressa. A vantagem das janelas de ajuda "O Que É Isto?" oferecidas pelo Qt e pelo KDE é que elas poderão conter texto formatado, i.e. poderão conter vários tipos de letra, texto em negrito e itálico, ou mesmo imagens e tabelas. Um exemplo de uma ajuda "O Que É Isto?": Finalmente, todos os programas deverão ter um manual. Um manual é normalmente visto no KHelpCenter, activando o menu Ajuda. Isto significa, que uma aplicação adicional completa aparece e distrai o utilizador do seu trabalho. Por consequência, a consulta do manual só deverá ser necessária se as outras funcionalidades como as dicas e a ajuda "o que é isto?" não forem suficientes. Obviamente, um manual tem a vantagem que não explica apenas aspectos únicos e isolados da interface do utilizador. Em vez disso, ele poderá explicar aspectos da aplicação num contexto mais amplo. Os manuais para o KDE são escritos usando a linguagem de formatação DocBook. Do ponto de vista do programador, o Qt oferece uma API fácil de usar para a ajuda 'online'. Para atribuir uma dica a um dado elemento gráfico, use a classe QToolTip. QToolTip::add(e, i18n("Este elemento faz uma dada tarefa.")) Se as barras de menu e as barras de ferramentas forem criadas com o padrão de acções, o texto usado como dica deriva do primeiro argumento do construtor da KAction: accao = new KAction(i18n("&Remover"), "editdelete", SHIFT+Key_Delete, actionCollection(), "del") Aqui também é possível atribuir um dado texto que é apresentado na barra de estado quando o item do menu respectivo é seleccionado: action->setStatusText(i18n("Apaga o texto marcado")) A API da ajuda "O que é isto?' é bastante parecido. Nas janelas, use o seguinte código: QWhatsThis::add(e, i18n("<qt>Isto demonstra o motor" " do <b>Qt</b> para" " o texto formatado.<ul>" "<li>Um</li>" "<li>Dois</li>" "</ul></qt>")) Para os itens do menu, use accao->setWhatsThis(i18n("Apaga o ficheiro marcado")) A invocação do KHelpCenter está encapsulada na classe TDEApplication. Para mostrar o manual da sua aplicação, basta usar kapp->invokeHelp() Isto mostra a primeira página com o índice analítico. Quando você quer mostrar apenas uma dada secção do manual, você poderá dar um argumento adicional ao invokeHelp() que indica a 'âncora' para a qual o navegador irá saltar. Componentes e serviços Serviços do KDE O que são os serviços do KDE? A noção de um serviço é um conceito central da arquitectura modular do KDE. Não existe nenhuma implementação técnica restrita associada a este termo - os serviço podem ser 'plugins' sob a forma de bibliotecas dinâmicas ou podem ser programas controlados através de DCOP. Quando alegar ter um determinado tipo de serviço, um serviço promete implementar certas APIs ou funcionalidades. Em termos de C++, pode-se pensar num tipo de serviço como uma classe abstracta e num serviço como uma implementação dessa interface. A vantagem desta separação é clara: Uma aplicação que use um tipo de serviço não tem de conhecer as possíveis implementações od mesmo. Só usa as APIs associadas ao tipo de serviço. Desta forma, o serviço usado pode ser alterado sem afectar a aplicação. Da mesma forma, o utilizador pode configurar os serviços que ele prefere para certas funcionalidades. Alguns exemplos: O motor de desenho de HTML no Konqueror é uma componente embebida que implementa os tipos de serviços KParts/ReadOnlyPart e Browser/View. No HEAD do KDevelop, a maioria da funcionalidade está incorporada em 'plugins' com o tipo de serviço KDevelop/Part. No arranque, todos os serviços deste tipo são carregados, de modo a que você possa extender o IDE de uma forma muito flexível. Na vista em ícones, o Konqueror mostra - se estiver activo - as miniaturas das imagens, páginas HTML, PDF e ficheiros de texto. Esta capacidade pode ser extendida. Se você quiser que ela mostre imagens de antevisão dos seus próprios ficheiros de dados com algum tipo MIME, você poderá implementar um serviço do tipo ThumbCreator. Obviamente, um serviço não é só caracterizado pelos tipos de serviços que implementa, mas também em algumas propriedades. Por exemplo, um ThumbCreator não só alega que implementa a classe de C++ com o tipo ThumbCreator, mas também tem uma lista de tipos MIME pelos quais é responsável. De forma semelhante, as componentes do KDevelop têm a a linguagem de programação que suportam como uma propriedades. Quando uma aplicação pede um tipo de serviço, pode também listar as restrições nas propriedades do serviço. No exemplo acima, quando o KDevelop carrega os 'plugins' de um projecto Java, ele só pede os 'plugins' que tenham o Java como propriedade da linguagem de programação. Para esse fim, o KDE contém um trader (mediador) semelhante ao do CORBA com uma linguagem de pesquisa complexa. Definir tipos de serviço Os novos tipos de serviços são adicionados ao instalar uma descrição dos mesmos em TDEDIR/share/servicetypes. Na plataforma do 'automake', isto pode ser feito com este excerto do Makefile.am: kde_servicetypesdir_DATA = tdeveloppart.desktop EXTRA_DIST = $(kde_servicetypesdir_DATA) A definição tdeveloppart.desktop de uma componente do KDevelop assemelha-se ao seguinte: [Desktop Entry] Type=ServiceType X-TDE-ServiceType=KDevelop/Part Name=KDevelop Part [PropertyDef::X-KDevelop-Scope] Type=QString [PropertyDef::X-KDevelop-ProgrammingLanguages] Type=QStringList [PropertyDef::X-KDevelop-Args] Type=QString Para além dos itens normais, este exemplo demonstra como é que você declara que um serviço tem determinadas propriedades. Cada definição de propriedades corresponde a um grupo [PropertyDef::name] no ficheiro de configuração. Neste grupo, o item Type define o tipo da propriedades. Os tipos possíveis são todos os que conseguem ser registados num QVariant. Definir os serviços das bibliotecas dinâmicas As definições dos serviços são gravadas na directoria TDEDIR/share/services: kde_servicesdir_DATA = kdevdoxygen.desktop EXTRA_DIST = $(kde_servicesdir_DATA) O conteúdo do seguinte ficheiro de exemplo kdevdoxygen.desktop define o 'plugin' do KDevDoxygen com o tipo de serviço KDevelop/Part: [Desktop Entry] Type=Service Comment=Doxygen Name=KDevDoxygen ServiceTypes=KDevelop/Part X-TDE-Library=libkdevdoxygen X-KDevelop-ProgrammingLanguages=C,C++,Java X-KDevelop-Scope=Project Para além das declarações normais, um item importante é o X-TDE-Library. Este contém o nome da biblioteca do 'libtool' (sem a extensão .la). Também corrige (com o prefixo init_ anexado) o nome do símbolo exportado na biblioteca que devolve uma 'factory' (fábrica) de objectos. Para o exemplo acima, a biblioteca deve conter a seguinte função: extern "C" { void *init_libkdevdoxygen() { return new DoxygenFactory; } }; O tipo da classe da 'factory' DoxygenFactory depende do tipo de serviço específico que este serviço implementa. No nosso exemplo de um 'plugin' do KDevelop, a 'factory' deverá ser uma KDevFactory (que herda de KLibFactory). Os exemplos mais comuns são a KParts::Factory que é suposto produzir objectos KParts::ReadOnlyPart ou, na maioria dos casos, a KLibFactory genérica. Usar os serviços das bibliotecas dinâmicas Para usar um serviço de uma biblioteca dinâmica numa aplicação, você precisa de obter um objecto KService que a represente. Isto é discutido na secção sobre os tipos MIME (e numa secção sobre o mediador, a ser escrita :-) Com o objecto KService acessível, você poderá simplesmente carregar a biblioteca e obter uma referência para o objecto da sua 'factory': KService *servico = ... QString nomeBiblioteca = QFile::encodeName(servico->library()); KLibFactory *fabrica = KLibLoader::self()->factory(nomeBiblioteca); if (!fabrica) { QString nome = servico->name(); QString mensagemErro = KLibLoader::self()->lastErrorMessage(); KMessageBox::error(0, i18n("Ocorreu um erro ao carregar o serviço %1.\n" "O diagnóstico da libtool é:\n%2") .arg(nome).arg(mensagemErro); } A partir deste ponto, o procedimento posterior depende de novo do tipo de serviço. Para os 'plugins' genéricos, você cria objectos com o método KLibFactory::create(). Para as KParts, você precisa de converter o ponteiro da 'factory' para a classe mais específica KParts::Factory e usar o seu método create(): if (fabrica->inherits("KParts::Factory")) { KParts::Factory *fabricaComponentes = static_cast<KParts::Factory*>(fabrica); QObject *objecto = fabricaComponentes->createPart(janelaMae, nomeJanela, mae, nome, "KParts::ReadOnlyPart"); ... } else { cout << "O serviço na implementa a fábrica correcta" << endl; } Definir serviços de DCOP Um serviço de DCOP é normalmente implementado como um programa que é iniciado sempre que é necessário. Ele entra então em ciclo e fica à espera de ligações do DCOP. O programa poderá ser interactivo, mas também poderá correr completamente, ou durante parte do seu tempo de vida, como um servidor em segundo plano, sem que o utilizador repare nele. Um exemplo destes servidores é o kio_uiserver, que implementa a interacção com o utilizador, como a janela de progresso para a biblioteca KIO. A vantagem de um servidor centralizado deste no contexto em questão é que p.ex., pode-se mostrar o progresso da transferência para vários ficheiros diferentes numa só uma janela, mesmo que estas transferências tenham sido iniciadas a partir de aplicações diferentes. Um serviço de DCOP é definido de forma diferente de um serviço de uma biblioteca dinâmica. Obviamente, não diz respeito a uma biblioteca, mas sim a um executável. Para além disso, os serviços de DCOP não indicam uma linha ServiceType, porque normalmente eles são iniciados pelo nomes deles. Como propriedades adicionais, contêm duas linhas: O X-DCOP-ServiceType define a forma como o serviço é iniciado. O valor Unique diz que o serviço não poderá ser iniciado mais do que uma vez. Isto significa que, se você tentar iniciar este serviço (p.ex., através do TDEApplication::startServiceByName(), o KDE irá descobrir se já está registado no DCOP e usa o serviço em execução. Se ainda não estiver registado, o KDE irá iniciá-lo e esperar até que esteja registado. Desta forma, você poderá enviar imediatamente chamadas de DCOP para o serviço. Nesse caso, o serviço deverá ser implementado como uma KUniqueApplication. O valor Multi para o X-DCOP-ServiceType diz que poderão coexistir várias instâncias do serviço, por isso todas as tentativas de iniciar o serviço irão criar outro processo. Como última possibilidade, o valor None poderá ser usado. Neste caso, o arranque do serviço não irá esperar até ter sido registado com o DCOP. O X-TDE-StartupNotify deverá normalmente ser igual a 'false' (falso). Caso contrário, quando o programa for iniciado, a barra de tarefas irá mostrar uma notificação de arranque ou, dependo da configuração do utilizador, o cursor irá mudar. Aqui está a definição do kio_uiserver: [Desktop Entry] Type=Service Name=kio_uiserver Exec=kio_uiserver X-DCOP-ServiceType=Unique X-TDE-StartupNotify=false Usar os serviços de DCOP Um serviço de DCOP é iniciado com um de vários métodos na classe TDEApplication: DCOPClient *cliente = kapp->dcopClient(); cliente->attach(); if (!cliente->isApplicationRegistered("kio_uiserver")) { QString erro; if (TDEApplication::startServiceByName("kio_uiserver", QStringList(), &erro)) cout << "O início do kioserver falhou com a mensagem " << erro << endl; } ... QByteArray dados, dadosResposta; QCString tipoResposta; QDataStream arg(dados, IO_WriteOnly); arg << true; if (!client->call("kio_uiserver", "UIServer", "setListMode(bool)", dados, tipoResposta, dadosResposta)) cout << "A chamada do kio_uiserver falhou" << endl; ... Repare que o exemplo de uma chamada de DCOP indicado aqui utiliza a codificação explícita dos argumentos. Nornalmente, você iria usar um 'stub' (uma classe de adaptação) gerado pelo 'dcopidl2cpp', por ser muito mais simples e menos sujeito a erros. No exemplo aqui dado, o serviço foi iniciado "por nome", i.e., o primeiro argumento do TDEApplication::startServiceByName() é o nome que aparece na linha Name do ficheiro 'desktop'. Uma alternativa é usar o TDEApplication::startServiceByDesktopName(), que recebe o nome do ficheiro 'desktop' como argumento, i.e., neste caso, seria igual a "kio_uiserver.desktop". Todas estas chamadas recebem uma lista de URLs como segundo argumento, o qual é dado ao serviço na linha de comandos. O terceiro argumento é um ponteiro para uma QString. Se o início do serviço falhar, este argumento fica igual à mensagem de erro traduzida. Tipos MIME O que são tipos MIME? Os tipos MIME são usados para descrever o tipo de conteúdo dos ficheiros ou dos blocos de dados. Originalmente, foram introduzidos para permitir o envio de imagens, ficheiros de som, etc., por e-mail (o MIME significa "Multipurpose Internet Mail Extensions" - Extensões Multi-Uso de Correio pela Internet). Depois, este sistema foi também usado pelos navegadores Web para saber como apresentar os dados enviados por um servidor Web para o utilizador. Por exemplo, uma página de HTML tem um tipo MIME "text/html", um ficheiro PostScript tem o tipo "application/postscript". No KDE, este conceito é usado em vários sítios: Na vista em ícones do Konqueror, os ficheiros são representados por ícones. Cada tipo MIME tem um dado ícone associado, que é aqui apresentado. Quando você carrega no ícone de um ficheiro ou no seu nome no Konqueror, o ficheiro tanto pode ser aberto num visualizador incorporado como numa aplicação associada ao tipo de ficheiro. Quando você arrasta e larga alguns dados de uma aplicação noutra (ou dentro da mesma aplicação), o destino dos dados pode optar por aceitar apenas alguns tipos de dados. Para além disso, ele irá lidar com os dados de imagens de forma diferente dos dados textuais. Os dados na área de transferência têm um tipo MIME. Tradicionalmente, os programas do X só lidam com imagens ou texto, mas com o Qt, não existem restrições ao tipo de dados. Nos exemplos acima, é óbvio que o tratamento do MIME é uma questão complexa. Primeiro, é necessário estabelecer um mapeamento dos nomes dos ficheiros para os tipos MIME. O KDE vai mais além, permitindo até que o conteúdo dos ficheiros seja mapeado em tipos MIME, para os casos em que o nome do ficheiro não está disponível. Segundo, é necessário mapear os tipos MIME nas aplicações ou nas bibliotecas que podem ver ou editar os ficheiros de um determinado tipo ou ainda criar uma imagem em miniatura deles. Existe uma variedade de APIs para descobrir o tipo MIME dos dados ou ficheiros. De um modo geral, existe um certo compromisso de velocidade/fiabilidade que terá de fazer. Você poderá descobrir o tipo de um ficheiro, examinando apenas o nome do seu ficheiro (i.e., na maioria dos casos, pela sua extensão). Por exemplo, um ficheiro xpto.jpg é normalmente um "image/jpeg". Nos casos em que a extensão não existe, isto não é seguro, e você terá de ver o conteúdo do ficheiro. Isto é obviamente mais lento, em particular para os ficheiros que terão de ser obtidos via HTTP em primeiro lugar. O método baseado no conteúdo baseia-se no ficheiro TDEDIR/share/mimelnk/magic e, como tal, poderá ser difícil de extender. Mas, de um modo geral, a informação do tipo MIME poderá ser disponibilizada de forma simples ao sistema se instalar um ficheiro .desktop, e está disponível de forma eficiente e conveniente através das bibliotecas do KDE. Definir os tipos MIME Vamos então definir um tipo "application/x-xpto" para o nosso programa programaXpto. Para tal, você terá de criar um ficheiro xpto.desktop e instalá-lo em TDEDIR/share/mimelnk/application. (Esta é a localização normal, mas poderá variar entre distribuições). Isto pode ser feito se adicionar o seguinte ao Makefile.am: mimedir = $(kde_mimedir)/application mime_DATA = xpto.desktop EXTRA_DIST = $(mime_DATA) O ficheiro xpto.desktop deverá ser parecido com o seguinte: [Desktop Entry] Type=MimeType MimeType=application/x-xpto Icon=icone_xpto Patterns=*.xpto; DefaultApp=programaXpto Comment=XPTO Data File Comment[pt]=Dados em XPTO O item "Comment" é suposto ser traduzido. Dado que o ficheiro .desktop refere um ícone, você deverá também instalar um ícone icone_xpto.png, que representa o ficheiro p.ex. no Konqueror. Nas bibliotecas do KDE, a definição de um tipo destes é mapeada numa instância da classe KMimeType. Use esta como é mostrado no exemplo a seguir: KMimeType::Ptr tipo = KMimeType::mimeType("application/x-xpto"); cout << "Tipo: " << tipo->name() < endl; cout << "Ícone: " << tipo->icon() < endl; cout << "Comentário: " << tipo->icon() < endl; QStringList padroes = tipo->patterns(); QStringList::ConstIterator it; for (it = padroes.begin(); it != padroes.end(); ++it) cout << "Padrão: " << (*it) << endl; Determinar o tipo MIME dos dados O método mais rápido para determinar o tipo de um ficheiro é o KMimeType::findByURL(). Isto procura pelo texto no URL e, na maioria dos casos, determina o tipo a partir da extensão. Para certos protocolos (p.ex., 'http', 'man', 'info'), este mecanismo não é usado. Por exemplos, os 'scripts' CGI nos servidores Web escritos em Perl normalmente têm a extensão .pl, o que iria corresponder a um tipo "text/x-perl". Todavia, o ficheiro devolvido pelo servidor é o resultado deste 'script', que é normalmente HTML. Para esse caso, o KMimeType::findByURL() devolve o tipo MIME "application/octet-stream" (disponível através do KMimeType::defaultMimeType()), que indica uma falha da descoberta do tipo. KMimeType::Ptr tipo = KMimeType::findByURL("/home/ze/xpto.jpg"); if (tipo->name() == KMimeType::defaultMimeType()) cout << "Não foi possível descobrir o tipo" << endl; else cout << "Tipo: " << tipo->name() << endl; (este método tem mais alguns argumentos, mas estes não estão documentados, por isso esqueça-os). Você poderá querer determinar o tipo MIME a partir do conteúdo do ficheiro em vez de ser pelo seu nome. Isto é mais fiável, mas também é mais lento, dado que implica ler uma parte do ficheiro. Isto é feito com a classe KMimeMagic, que tem tratamentos de erros diferentes: KMimeMagicResult *resultado = KMimeMagic::self()->findFileType("/home/ze/xpto.jpg"); if (!resultado || !resultado->isValid()) cout << "Não foi possível descobrir o tipo" << endl; else cout << "Tipo: " << resultado->mimeType() << endl; Como variante desta função, você também poderá determinar o tipo de um dado bloco de memória. Isto é p.ex. usado no Kate para descobrir o modo de realce: QByteArray dados; ... KMimeMagicResult *resultado = KMimeMagic::self()->findBufferType(dados); if (!resultado || !resultado->isValid()) cout << "Não foi possível descobrir o tipo" << endl; else cout << "Tipo: " << resultado->mimeType() << endl; Obviamente, até mesmo o KMimeMagic só é capaz de determinar o tipo do ficheiro para o conteúdo de um ficheiro local. Para os ficheiros remotos, existe uma outra possibilidade: KURL url("http://developer.kde.org/favicon.ico"); QString tipo = TDEIO::NetAccess::mimetype(url); if (tipo == KMimeType::defaultMimeType()) cout << "Não foi possível descobrir o tipo" << endl; else cout << "Tipo: " << tipo << endl; Isto inicia uma tarefa do KIO para obter uma parte do ficheiro e analisá-la. Lembre-se que esta função é talvez bastante lenta e bloqueia o programa. Normalmente, você só irá querer usar isto se o KMimeType::findByURL() devolveu "application/octet-stream". Por outro lado, se você não quiser bloquear a sua aplicação, você poderá iniciar explicitamente a tarefa do KIO e ligar-se a um dos seus 'signals': void ClasseXpto::descobrirTipo() { KURL url("http://developer.kde.org/favicon.ico"); TDEIO::MimetypeJob *tarefa = TDEIO::mimetype(url); connect( tarefa, SIGNAL(result(TDEIO::Job*)), this, SLOT(mimeResult(TDEIO::Job*)) ); } void ClasseXpto::resultadoMime(TDEIO::Job *tarefa) { if (tarefa->error()) tarefa->showErrorDialog(); else cout << "Tipo MIME: " << ((TDEIO::MimetypeJob *)tarefa)->mimetype() << endl; } Mapear um tipo MIME a uma aplicação ou serviço Quando uma aplicação é instalada, ela coloca um ficheiro .desktop que contém uma lista dos tipos MIME que esta aplicação pode carregar. De forma semelhante, os componentes como as KParts disponibilizam esta informação através dos ficheiros .desktop dos serviços. Por isso, de um modo geral, existem vários programas e componentes que podem processar um dado tipo MIME. Você poderá obter uma dessas listas a partir da classe KServiceTypeProfile: KService::OfferList ofertas = KServiceTypeProfile::offers("text/html", "Application"); KService::OfferList::ConstIterator it; for (it = ofertas.begin(); it != ofertas.end(); ++it) { KService::Ptr servico = (*it); cout << "Nome: " << servico->name() << endl; } O valor devolvido por esta função é uma lista de ofertas de serviços. Um objecto KServiceOffer contém um KService::Ptr em conjunto com um número de preferência. A lista devolvida pelo KServiceTypeProfile::offers() vem ordenada de acordo com a preferência do utilizador. O utilizador poderá alterar isto com o comando "keditfiletype text/html" ou escolhendo Editar o Tipo de Ficheiro no menu de contexto do Konqueror num ficheiro HTML. No exemplo acima, foi pedida uma lista de ofertas de aplicações que suportem o text/html. Isto irá - entre outros - conter os editores de HTML como o Quanta Plus. Você poderá também substituir o segundo argumento "Application" por "KParts::ReadOnlyPart". Nesse caso, você irá obter uma lista dos componentes incorporados para apresentar conteúdos em HTML, como por exemplo o KHTML. Na maioria dos casos, você não está interessado na lista de todas as ofertas de serviços para uma dada combinação de tipo MIME e tipo de serviço. Existe uma função de conveniência que lhe dá apenas a oferta de serviço com a maior preferência: KService::Ptr oferta = KServiceTypeProfile::preferredService("text/html", "Application"); if (oferta) cout << "Nome: " << oferta->name() << endl; else cout << "Não foi encontrado nenhum serviço apropriado" << endl; Para as pesquisas ainda mais complexas, existe um mediador completo e semelhante ao do CORBA. Para poder executar um serviço de aplicação com alguns URLs, use o KRun: KURL::List listaUrls; listaUrls << "http://www.ietf.org/rfc/rfc1341.txt?number=1341"; listaUrls << "http://www.ietf.org/rfc/rfc2046.txt?number=2046"; KRun::run(ofertas.service(), listaUrls); Diversos Nesta secção, nós queremos listar algumas APIs que estão de certo modo relacionadas com a discussão anterior. Obter um ícone para um dado URL. Isto procura o tipo do URL e devolve o ícone associado. KURL url("ftp://ftp.kde.org/pub/incoming/wibble.c"); QString icone = KMimeType::iconForURL(url); Executar um URL. Isto procura pelo tipo do URL e inicia o programa preferido do utilizador associado a este tipo. KURL url("http://dot.kde.org"); new KRun(url); Transparência na rede Introdução Na era na World Wide Web, é de uma importância essencial que as aplicações possam aceder aos recursos na Internet: elas deverão ser capazes de obter os ficheiros a partir de um servidor Web, gravar os ficheiros num servidor FTP ou ler as mensagens de e-mail de um servidor Web. Normalmente, a capacidade de aceder aos ficheiros, independentemente da sua localização é chamada de transparência na rede. No passado, foram implementadas aproximações diferentes para estes objectivos. O sistema de ficheiros antigo NFS é uma tentativa de implementar a transparência de rede ao nível da API do POSIX. Embora esta aproximação funcione bastante bem nas redes locais e próximas, não é escalável para os recursos cujo acesso não seja fiável ou seja lento. Aqui, a assincronização é importante. Enquanto você está à espera do seu navegador Web para transferir uma página, a interface do utilizador não deverá bloquear. Da mesma forma, o início do desenho da página não deverá começar somente quando a página estiver disponível por completo mas sim actualizar-se regulamente à medida que os dados vão chegando. Nas bibliotecas do KDE, a transparência da rede está implementada na API do KIO. O conceito central desta arquitectura é a tarefa de E/S. Uma tarefa pode copiar ou remover ficheiros, entre outras coisas. Logo que uma tarefa seja inicia, ela fica em segundo plano e não bloqueia a aplicação. Todas as comunicações da tarefa para a aplicação - como a entrega dos dados ou a informação de progresso - é feita de forma integrada com o ciclo de eventos do Qt. A operação em segundo-plano é conseguida com o arranque de ioslaves para efectuar certas tarefas. Os 'ioslaves' são iniciados como processos separados e comunicam através de 'sockets' do domínio UNIX. Desta forma, não é necessário nenhum suporte de multi-tarefa e os 'slaves' instáveis não poderão estoirar as aplicações que os usam. As localizações dos ficheiros são indicadas pelos URLs que são usados em grande escala. Mas, no KDE, os URLs não só se expandem à gama de ficheiros endereçáveis para além do sistema de ficheiros local. Também funciona na direcção oposta - p.ex. você poderá navegar nos pacotes TAR. Isto é conseguido com os URLs aninhados. Por exemplo, um ficheiro num pacote TAR num servidor HTTP poderá ter o URL http://www.xpto.pt/~ze/artigo.tgz#tar:/documento.tex Usar o KIO Na maioria dos casos, as tarefas são criadas ao invocar as funções no espaço de nomes do KIO. Estas funções recebem um ou dois URLs como argumentos e possivelmente outros parâmetros necessários. Quando a tarefa terminar, ela emite o 'signal' result(TDEIO::Job*). Depois de este 'signal' ter sido emitido, a tarefa elimina-se a si própria. Deste modo, um caso de uso típico poderia ser semelhante ao seguinte: void ClasseXpto::criarDirectoria() { SimpleJob *tarefa = TDEIO::mkdir(KURL("file:/home/ze/dir_kio")); connect( tarefa, SIGNAL(result(TDEIO::Job*)), this, SLOT(resultadoMkdir(TDEIO::Job*)) ); } void ClasseXpto::resultadoMkdir(TDEIO::Job *tarefa) { if (tarefa->error()) tarefa->showErrorDialog(); else cout << "o 'mkdir' correu bem" << endl; } Dependendo do tipo de tarefa, você poder-se-á também ligar a outros 'signals'. Aqui está uma ideia geral das funções possíveis: TDEIO::mkdir(const KURL &url, int permissao) Criar uma directoria, com algumas permissões opcionais. TDEIO::rmdir(const KURL &url) Remove uma directoria. TDEIO::chmod(const KURL &url, int permissoes) Muda as permissões de um ficheiro. TDEIO::rename(const KURL &origem, const KURL &destino, bool sobrepor) Muda o nome de um ficheiro. TDEIO::symlink(const QString &alvo, const KURL &destino, bool sobrepor, bool mostrarProgresso) Cria uma ligação simbólica. TDEIO::stat(const KURL &url, bool mostrarProgresso) Descobre certas informações sobre o ficheiro, como o tamanho, a data de modificação e as permissões. A informação pode ser obtida a partir do TDEIO::StatJob::statResult() depois de a tarefa terminar. TDEIO::get(const KURL &url, bool reler, bool mostrarProgresso) Transfere os dados de um dado URL. TDEIO::put(const KURL &url, int permissoes, bool sobrepor, bool continuar, bool mostrarProgresso) Transfere os dados para um dado URL. TDEIO::http_post(const KURL &url, const QByteArray &dados, bool mostrarProgresso) Envia os dados. É específico do HTTP. TDEIO::mimetype(const KURL &url, bool mostrarProgresso) Tenta descobrir o tipo MIME do URL. O tipo poderá ser obtido a partir do TDEIO::MimetypeJob::mimetype() depois de a tarefa terminar. TDEIO::file_copy(const KURL &origem, const KURL &destino, int permissoes, bool sobrepor, bool continuar, bool mostrarProgresso) Copia um único ficheiro. TDEIO::file_move(const KURL &origem, const KURL &destino, int permissoes, bool sobrepor, bool continuar, bool mostrarProgresso) Muda o nome ou move um único ficheiro. TDEIO::file_delete(const KURL &url, bool mostrarProgresso) Apaga um único ficheiro. TDEIO::listDir(const KURL &url, bool mostrarProgresso) Lista o conteúdo de uma dada directoria. De cada vez que alguns itens passam a ser conhecidos, o 'signal' TDEIO::ListJob::entries() é emitido. TDEIO::listRecursive(const KURL &url, bool mostrarProgresso) Semelhante à função listDir(), só que esta função é recursiva. TDEIO::copy(const KURL &origem, const KURL &destino, bool mostrarProgresso) Copia um ficheiro ou directoria. As directorias são copiadas de forma recursiva. TDEIO::move(const KURL &origem, const KURL &destino, bool mostrarProgresso) Move ou muda o nome de um ficheiro ou directoria. TDEIO::del(const KURL &src, bool shred, bool showProgressInfo) Apaga um ficheiro ou directoria. Itens da directoria Tanto as tarefas TDEIO::stat() como a TDEIO::listDir() devolvem os seus resultados como um tipo UDSEntry e UDSEntryList respectivamente. A última está definida como sendo um QValueList<UDSEntry>. O acrónimo UDS significa "Universal directory service" (serviço de directório universal). O princípio está em que o item da directoria só contém a informação que um 'ioslave' poderá fornecer, nada mais. Por exemplo, o 'slave' de HTTP não fornece nenhuma informação sobre as permissões de acesso ou os donos dos ficheiros. Em vez disso, uma UDSEntry é uma lista de UDSAtoms. Cada átomo contém um item informativo específico. Ele consiste num tipo armazenado no 'm_uds' e num valor inteiro no 'm_long' ou um valor de texto no 'm_str', dependendo do tipo. Os seguintes tipos estão actualmente definidos: UDS_SIZE (inteiro) - O tamanho do ficheiro. UDS_USER (texto) - O utilizador que possui o ficheiro. UDS_GROUP (texto) - O grupo que possui o ficheiro. UDS_NAME (texto) - O nome do ficheiro. UDS_ACCESS (inteiro) - As permissões sobre o ficheiro, como é guardado p.ex. pela função da 'libc' stat() no campo 'st_mode'. UDS_FILE_TYPE (inteiro) - O tipo do ficheiro, tal como é, p.ex., registado pelo stat() no campo 'st_mode'. Como tal, você poderá usar as macros normais da 'libc' como a S_ISDIR para testar este valor .Repare que os dados fornecidos pelos 'ioslaves' correspondem ao stat(), não ao lstat(), i.e., no caso das ligações simbólicas, o tipo aqui mencionado é o tipo do ficheiro apontado pela ligação, não a ligação em si. UDS_LINK_DEST (texto) - No caso de uma ligação simbólica, o nome do ficheiro para onde esta ligação aponta. UDS_MODIFICATION_TIME (inteiro) - A hora (como no tipo 'time_t') em que o ficheiro foi modificado da última vez, como é p.ex. registado pelo stat() no campo 'st_mtime'. UDS_ACCESS_TIME (inteiro) - A hora (como no tipo 'time_t') em que o ficheiro foi acedido da última vez, como é p.ex. registado pelo stat() no campo 'st_atime'. UDS_CREATION_TIME (inteiro) - A hora (como no tipo 'time_t') em que o ficheiro foi criado, como é p.ex. registado pelo stat() no campo 'st_ctime'. UDS_URL (texto) - Fornece um URL do ficheiro, se não for simplesmente a concatenação do URL da directoria e o nome do ficheiro. UDS_MIME_TYPE (texto) - tipo MIME do ficheiro UDS_GUESSED_MIME_TYPE (texto) - o tipo MIME do ficheiro, tal como é determinado pelo 'slave'. A diferença em relação ao tipo anterior é que o que é fornecido aqui não deverá ser considerado fiável (porque determiná-lo de forma fiável será demasiado dispendioso). Por exemplo, a classe KRun verifica explicitamente o tipo MIME se não tiver nenhuma informação fiável. Ainda que a forma de armazenar as informações sobre os ficheiros num UDSEntry seja flexível e prático do ponto de vista dos 'ioslaves', é uma confusão para ser usado pelo programador da aplicação. Por exemplo, para saber qual é o tipo MIME do ficheiro, você teria de iterar por todos os átomos e testar se o m_uds era o UDS_MIME_TYPE. Felizmente, existe uma API que é bastante mais simples de usar: a classe KFileItem. Utilização síncrona Normalmente, a API síncrona do KIO é demasiado complexa de usar e, como tal, a implementação de uma assincronização completa não é uma prioridade. Por exemplo, num programa que só consiga lidar com um ficheiro de cada vez, não há muito a fazer enquanto o programa está a transferir um ficheiro, de qualquer forma. Para esses casos simples, existe uma API muito mais simples sob a forma de funções estáticas no TDEIO::NetAccess. Por exemplo, para poder copiar um ficheiro, use KURL origem, destino; source = ...; target = ... TDEIO::NetAccess::copy(origem, destino); A função irá regressar depois de o processo de cópia ter terminado por completo. De qualquer forma, este método fornece uma janela de progresso e certifica-se que os processos da aplicação actualizam os eventos. Uma combinação particularmente interessante de funções é a download() em conjunto com a removeTempFile(). A primeira obtém um ficheiro a partir de um dado URL e guarda-o num ficheiro temporário com um nome único. O nome é guardado no segundo argumento. Se o URL for local, o ficheiro não é transferido e, em vez disso, o segundo argumento passa a ser o nome do ficheiro local. A função removeTempFile() apaga o ficheiro indicado pelo seu argumento, se o ficheiro for o resultado de uma transferência anterior. Se não for o caso, não faz nada. Desta forma, uma forma muito simples de carregar os ficheiros, independentemente da sua localização é o pedaço de código a seguir: KURL url; url = ...; QString ficheiroTemporario; if (TDEIO::NetAccess::download(url, ficheiroTemporario) { // carregar o ficheiro com o nome 'ficheiroTemporario' TDEIO::NetAccess::removeTempFile(ficheiroTemporario); } Meta-dados Como pode ser visto em cima, a interface para as tarefas de E/S é bastante abstracta e não considera nenhuma troca de informação entre a aplicação e o 'IO slave', o qual é específico do protocolo. Isto nem sempre é apropriado. Por exemplo, você poderá querer dar certos parâmetros ao 'slave' de HTTP para controlar o comportamento da sua 'cache' ou para enviar um conjunto de 'cookies' com o pedido. Para esse fim, o conceito de meta-dados foi introduzido. Quando for criada uma tarefa, você poderá configurá-la se lhe adicionar meta-dados. Cada item de meta-dados consiste num par chave/valor. Por exemplo, para evitar que o 'slave' de HTTP carregue a página Web da sua 'cache', você pode usar: void ClasseXpto::relerPagina() { KURL url("http://www.tdevelop.org/index.html"); TDEIO::TransferJob *tarefa = TDEIO::get(url, true, false); tarefa->addMetaData("cache", "reload"); ... } A mesma técnica é usada na outra direcção, i.e., para a comunicação do 'slave' para a aplicação. O método Job::queryMetaData() pede o valor de uma certa chave indicada pelo 'slave'. Para o 'slave' de HTTP, um desses exemplos é a chave "modified" (modificado), que contém uma (representação em texto da) data em que a página Web foi modificada da última vez. Um exemplo de como você pode usar isto é o seguinte: void ClasseXpto::mostrarDataModificacao() { KURL url("http://developer.kde.org/documentation/kde2arch/index.html"); TDEIO::TransferJob *tarefa = TDEIO::get(url, true, false); connect( tarefa, SIGNAL(result(TDEIO::Job*)), this, SLOT(transferirResultado(TDEIO::Job*)) ); } void ClasseXpto::transferirResultado(TDEIO::Job *tarefa) { QString tipoMime; if (tarefa->error()) tarefa->showErrorDialog(); else { TDEIO::TransferJob *tarefaTransferencia = (TDEIO::TransferJob*) tarefa; QString modificado = tarefaTransferencia->queryMetaData("modified"); cout << "Última modificação: " << modificado << endl; } Escalonamento Ao usar a API do KIO, você normalmente não tem de lidar com os detalhes de arranque dos 'IO slaves' e da comunicação com eles. O caso de uso normal é iniciar uma tarefa e, com alguns parâmetros, tratar os 'signals' que esta emite. Nos bastidores, o cenário é bastante mais complicado. Quando você cria uma tarefa, esta é posta numa fila. Quando a aplicação regressa ao ciclo de eventos, o KIO reserva processos de 'slaves' para as tarefas na fila. Para as primeiras tarefas que sejam iniciadas, isto é trivial: um 'IO slave' para o protocolo corresponde é iniciado. Contudo, depois de a tarefa (como a transferência de um servidor de HTTP) ter terminado, não é imediatamente morta. Em vez disso, é colocada num grupo de 'slaves' inactivos e é morta ao fim de um dado período de inactividade (normalmente 3 minutos). Se surgir um novo pedido para o mesmo protocolo e máquina, o 'slave' é reutilizado. A vantagem óbvia é que, para uma série de tarefas para a mesma máquina, o custo da criação de novos processos e da passagem por um processo de autenticação é poupado. Claro que a reutilização só é possível quando o 'slave' existente já tiver terminado a sua tarefa anterior. Sempre que chega um novo pedido enquanto um 'slave' existente está ainda a correr, é iniciado um novo processo para ser utilizado. Na utilização da API nos exemplos acima, não existe nenhuma limitação para criar novos processos de 'slaves': se você iniciar uma série consecutiva de transferências para 20 ficheiros, então o KIO irá iniciar 20 processos de 'slaves'. Este esquema de atribuição de 'slaves' a tarefas é chamado de directo. Não é sempre o esquema mais apropriado, dado que poderá necessitar de bastante memória e poderá colocar uma carga elevada tanto no cliente como no servidor. Por isso, existe uma forma diferente. Você poderá escalonar as tarefas. Se o fizer, somente um número limitado (de momento 3) de processos de 'slaves' para um dado protocolo serão criados. Se você criar mais tarefas que isso, elas serão colocadas numa fila e são processadas quando um processo de um 'slave' ficar inactivo. Isto é feito da seguinte forma: KURL url("http://developer.kde.org/documentation/kde2arch/index.html"); TDEIO::TransferJob *tarefa = TDEIO::get(url, true, false); TDEIO::Scheduler::scheduleJob(tarefa); Uma terceira possibilidade é orientar à ligação. Por exemplo, no caso do 'slave' de IMAP, não faz nenhum sentido lançar vários processos para o mesmo servidor. Só uma ligação de IMAP de cada vez é que deverá ser permitida. Neste caso, a aplicação deverá lidar explicitamente com a noção de um 'slave'. Terá de libertar um 'slave' para uma determinada ligação e então atribuir todas as tarefas que deverão ir pela mesma ligação ao mesmo 'slave'. Isto poderá ser conseguido facilmente com a utilização do TDEIO::Scheduler: KURL urlBase("imap://bernd@albert.physik.hu-berlin.de"); TDEIO::Slave *slave = TDEIO::Scheduler::getConnectedSlave(urlBase); TDEIO::TransferJob *tarefa1 = TDEIO::get(KURL(urlBase, "/INBOX;UID=79374")); TDEIO::Scheduler::assignJobToSlave(slave, tarefa1); TDEIO::TransferJob *tarefa2 = TDEIO::get(KURL(urlBase, "/INBOX;UID=86793")); TDEIO::Scheduler::assignJobToSlave(slave, tarefa2); ... TDEIO::Scheduler::disconnectSlave(slave); Você só poderá desligar o 'slave' depois de todas as tarefas atribuídas a ela terem terminado de forma garantida. Definir um 'ioslave' Na parte seguinte iremos discutir como é que você poderá adicionar um novo 'ioslave' ao sistema. Em analogia aos serviços, os 'ioslaves' novos são publicados no sistema, instalando para tal um pequeno ficheiro de configuração. O seguinte excerto do Makefile.am instala o protocolo FTP: protocoldir = $(kde_servicesdir) protocol_DATA = ftp.protocol EXTRA_DIST = $(mime_DATA) O conteúdo do ficheiro 'ftp.protocol' é o seguinte: [Protocol] exec=kio_ftp protocol=ftp input=none output=filesystem listing=Name,Type,Size,Date,Access,Owner,Group,Link, reading=true writing=true makedir=true deleting=true Icon=ftp O item "protocol" define qual o protocolo pelo qual este 'slave' é responsável. O "exec" é (ao contrário do que estaria à espera) o nome da biblioteca que implementa o 'slave'. Quando o 'slave' for suposto arrancar, o executável "tdeinit" é iniciado, o qual por sua vez arranca esta biblioteca no seu espaço de endereçamento. Por isso, na prática, você poderá pensar no 'slave' em execução como um processo separado, ainda que seja implementado como uma biblioteca. A vantagem deste mecanismo é que poupa bastante memória e reduz o tempo necessário pelo editor de ligações durante a execução. As linhas "input" e "output" não são usadas de momento. As linhas restantes do ficheiro .protocol definem quais as capacidades que o 'slave' tem. De um modo geral, as funcionalidades que um 'slave' tem de implementar são muito mais simples do que as funcionalidades que a API do KIO oferece à aplicação. A razão para tal é que as tarefas complexas são escalonadas para um conjunto de sub-tarefas. Por exemplo, para poder listar uma directoria recursivamente, terá de ser iniciada uma tarefa para a directoria de topo. Depois, para cada subdirectoria indicada, são criadas novas sub-tarefas. Um escalonador no KIO certifica-se que não estão demasiadas tarefas activas ao mesmo tempo. De forma semelhante, para poder copiar um ficheiro num protocolo que não suporte a cópia directa (como o protocolo ftp:), o KIO poderá ler o ficheiro de origem e então escrever os dados para o ficheiro de destino. Para isto funcionar, o .protocol precisa de publicar as acções que o seu 'slave' suporta. Dado que os 'slaves' são carregados como bibliotecas dinâmicas, mas constituem em si programas autónomos, a sua plataforma de código parece ligeiramente diferente dos 'plugins' normais das bibliotecas dinâmicas. A função que é invocada para iniciar o 'slave' chama-se kdemain(). Esta função faz algumas inicializações e vai então para o ciclo de eventos, onde fica à espera de pedidos da aplicação que a usa. Isto parece-se com o seguinte: extern "C" { int kdemain(int argc, char **argv); } int kdemain(int argc, char **argv) { KLocale::setMainCatalogue("tdelibs"); TDEInstance instance("kio_ftp"); (void) TDEGlobal::locale(); if (argc != 4) { fprintf(stderr, "Utilização: kio_ftp protocolo " "socket1-dominio socket2-dominio\n"); exit(-1); } FtpSlave slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } Implementar um 'ioslave' Os 'slaves' são implementados como sub-classes da TDEIO::SlaveBase (a FtpSlave no exemplo acima). Como tal, as acções listadas no ficheiro .protocol correspondem a certas funções virtuais no TDEIO::SlaveBase que a implementação do 'slave' precisa de reimplementar. Aqui está uma lista das acções possíveis e das funções virtuais correspondentes: leitura - Lê os dados de um URL void get(const KURL &url) escrita - Escreve os dados num URL e cria o ficheiro se ainda não existir nenhum. void put(const KURL &url, int permissoes, bool sobrepor, bool continuar) mudança de nome - Muda o nome de um ficheiro. void rename(const KURL &origem, const KURL &destino, bool sobrepor) remoção - Remove um ficheiro ou directoria. void del(const KURL &url, bool eUmFicheiro) listagem - Mostra o conteúdo de uma directoria. void listDir(const KURL &url) makedir - Cria uma directoria. void mkdir(const KURL &url, int permissoes) Adicionalmente, existem funções que podem ser implementadas de novo e que não estão listadas no ficheiro .protocol. Para essas operações, o KIO determina automaticamente se elas são suportadas ou não (i.e., se a implementação por omissão devolve um erro). Fornece dados sobre um ficheiro, de forma semelhante à função do C stat(). void stat(const KURL &url) Muda as permissões de acesso de um ficheiro. void chmod(const KURL &url, int permissoes) Determina o tipo MIME de um ficheiro. void mimetype(const KURL &url) Copia um ficheiro. copy(const KURL &url, const KURL &destino, int permissoes, bool sobrepor) Cria uma ligação simbólica. void symlink(const QString &alvo, const KURL &destino, bool sobrepor) Todas estas implementações deverão terminar com uma de duas chamadas: Se a operação foi bem sucedida, deverão invocar o finished(). Se ocorreu um erro, o error() deverá ser invocado com um código de erro como primeiro argumento e um texto no segundo. Os códigos de erro possíveis estão listados no tipo enumerado TDEIO::Error. O segundo argumento é normalmente o URL em questão. É usado, p.ex., no TDEIO::Job::showErrorDialog() para parametrizar a mensagem de erro para o utilizador. Para os 'slaves' que correspondem aos protocolos de rede, poderá ser interessante reimplementar o método SlaveBase::setHost(). Isto é chamado para indicar ao processo do 'slave' qual a máquina e o porto, assim como o utilizador e a senha a usar. De um modo geral, os meta-dados definidos pela aplicação poderão ser questionados pelo SlaveBase::metaData(). Você poderá verificar a existência de meta-dados de uma determinada chave com o SlaveBase::hasMetaData(). Comunicar de volta à aplicação As várias acções implementadas num 'slave' precisam de uma forma qualquer para comunicar de volta para a aplicação, usando o processo do 'slave': O get() envia blocos de dados. Isto é feito com o data(), que recebe um QByteArray como argumento. Claro que não precisa de enviar todos os dados de uma vez. Se enviar um ficheiro grande, invoque o data() com blocos de dados mais pequenos, de modo a que a aplicação os processe. Invoque o finished() quando a transferência terminar. O listDir() devolve informações sobre os itens de uma directoria. Para esse fim, chame o listEntries() com um TDEIO::UDSEntryList como argumento. De forma análoga à do data(), você poderá invocá-lo várias vezes. Quando terminar, invoque o listEntry() com o segundo parâmetro igual a 'true' (verdadeiro). Você poderá também chamar o totalSize() para indicar o número total de itens da directoria, se forem conhecidos. O stat() devolve informações acerca de um ficheiro como o seu tamanho, o tipo MIME, etc. Essa informação está contida num TDEIO::UDSEntry, o qual será discutido em baixo. Use o statEntry() para enviar um desses itens para a aplicação. O mimetype() chama o mimeType() com um argumento de texto. O get() e o copy() podem querer fornecer informações de progresso. Isto é feito com os métodos totalSize(), processedSize(), speed(). O tamanho total e o tamanho processado são indicados em 'bytes', enquanto que a velocidade é dada em 'bytes' por segundo. Você poderá enviar pares arbitrários de chaves/valores de meta-dados com o setMetaData(). Interagir com o utilizador Algumas das vezes, um 'slave' precisa de interagir com o utilizador. Os exemplos incluem as mensagens de informações, as janela de autenticação e as janelas de confirmação, sempre que um ficheiro está prestes a ser sobreposto. infoMessage() - Isto é para fins de informação, como a mensagem "A obter dados de <máquina>" do 'slave' de HTTP, o qual é mostrado normalmente na barra de estado do programa. Do lado da aplicação, este método corresponde ao 'signal' TDEIO::Job::infoMessage(). warning() - Mostra um aviso numa janela com o KMessageBox::information(). Se ainda estiver aberta uma mensagem de uma invocação anterior do warning() do mesmo processo-filho, nada acontece. messageBox() - Este é mais rico que o método anterior. Permite-lhe abrir uma mensagem com um texto e um título, assim como alguns botões. Veja o tipo enumerado SlaveBase::MessageBoxType para mais referências. openPassDlg() - Abre uma janela onde se poderá indicar o nome do utilizador e a sua senha. Licença &underFDL; &underGPL;