Cap12

286 views
214 views

Published on

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
286
On SlideShare
0
From Embeds
0
Number of Embeds
8
Actions
Shares
0
Downloads
11
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Cap12

  1. 1. 12. Entrada / Saída  Lendo e Escrevendo dados Binários  Lendo e Escrevendo Texto  Diretórios Transversais  Comunicação Inter-Processo A necessidade de leitura e escrita em arquivos ou outros dispositivos é comum em quase toda aplicação. Qt fornece ótimo suporte para E/S através de QIODevice, uma poderosa abstração que encapsula “dispositivos” capazes de ler e escrever blocos de bytes. Qt inclui as seguintes subclasses de QIODevice: QFIle Acessa arquivos no sistema de arquivos local e em dispositivos incorporados QTemporaryFile Cria e acessa arquivos temporários no sistema de arquivos local QBuffer Lê dados ou os escreve em um QByteArray QProcess Roda programas externos e controla comunicações inter-processos QTcpSocket Transfere um stream de dados pela rede usando TCP QUdpSocket Envia ou recebe datagramas UDP pela rede QSslSocket Transfere o stream de dados encriptados pela rede usando SSL/TLS QProcess, QTcpSocket, QUdpSocket e QSslSocket são dispositivos sequenciais, o que significa que os dados podem ser acessados apenas uma vez, iniciando do primeiro byte e progredindo serialmente até o último byte. QFile, QTemporaryFile e QBuffer são dispositivos de acesso randômico, desta forma bytes podem ser lidos quantas vezes quiser, de qualquer posição; eles fornecem a função QIODevice::seek() para reposicionar o ponteiro do arquivo. Em adição às classes dispositivo, Qt também fornece duas classes alto-nível de stream as quais podemos usar para ler de qualquer dispositivo E/S, ou escrever nele: QDataStream para dados binários e QTextStream para texto. Essas classes tomam conta de situações como ordenação de bytes e codificação de textos, garantindo que aplicações Qt rodando em diferentes plataformas ou em países podem ler e escrever arquivos entre si. Isto torna as classes Qt de E/S muito mais convenientes do que as correspondentes classes padrão do C++, que deixam esses casos nas mãos do programador da aplicação. QFile torna fácil o acesso a arquivos individuais, estejam eles no sistema de arquivos ou incorporados no executável da aplicação como recursos. Para aplicações que necessitam identificar conjuntos inteiros de arquivos para trabalhar, Qt fornece as classes QDir e QFileInfo, que controlam diretórios e fornecem informações sobre os arquivos dentro deles.
  2. 2. A classe QProcess nos permite lançar programas externos e comunicar com eles através de sua entrada padrão, saída padrão, e canais de erro padrões(cin, cout e cerr). Podemos setar as variáveis de ambiente e diretório de trabalho que a aplicação externa vai usar. Por padrão, a comunicação com o processo é assíncrona (não- bloqueante), mas é possível também bloquear certas operações. Compartilhamento, leitura e escrita XML são tópicos substanciais que cobrimos separadamente em capítulos dedicados (Capítulos 15 e 16). Lendo e Escrevendo Dados Binários O jeito mais simples de carregar e salvar dados binários com Qt é instanciar um QFile para abrir o arquivo, e acessá-lo através de um objeto QDataStream. QDataStream fornece um formato de armazenamento independente de plataforma que suporta tipos básicos C++ como int e double, e vários tipos de dados Qt, incluindo QByteArray, QFont, QImage, QPixmap, QString e QVariant, assim como classes container do Qt como QList<T> e QMap<K, T>. Aqui vemos como armazenamos um inteiro, um QImage e um QMap<QString, QColor> em um arquivo chamado facts.dat: QImage image (“philip.png”); QMap<QString, QColor> map; map.insert (“red”, Qt::red); map.insert (“green”, Qt::green); map.insert (“blue”, Qt::blue); QFile file (“facts.dat”); if (!file.open(QIODevice::WriteOnly)) { std::cerr << “Cannot open file for writing: ” << qPrintable(file.errorString()) << std::endl; return; } QDataStream out(&file); out.setVersion(QDataStream::Qt_4_3); out << quint32(0x12345678) << image << map; Se não podemos abrir o arquivo, informamos ao usuário e retornamos. O macro qPrintable() retorna um const char * para um QString. (Outra aproximação seria usar QString::toStdString(), que retorna um std::string, para o qual <iostream> tem uma sobrecarga de << ). Se o arquivo for aberto com sucesso, criamos um QDataStream e setamos seu número de versão. O número de versão é um inteiro que influencia o modo como tipos de dados do Qt são interpretados (tipos básicos C++ são sempre representados da mesma forma). No Qt 4.3, o formato mais compreensivo é versão 9. Podemos tratar a constante 9 hard-coded ou usar o nome simbólico QSataStream::Qt_4_3. Para assegurar que o número 0x12345678 é escrito como um inteiro sem sinal de 32 bits em todas as plataformas, convertemos para quint32, um tipo de dados que é de tamanho assegurado 32 bits exatamente. Para assegurar interoperacionalidade, QDataStream mantém o padrão de último bit mais importante; isto pode ser alterado chamando setByteOrder(). Não precisamos explicitamente fechar o arquivo, já que isto é feito automaticamente quando a varável QFile sal do escopo. Se quisermos verificar que os dados foram escritos, podemos chamar flush() e checar seu valor de retorno (true no caso de sucesso). O código para ler dados é semelhante ao usado para ler dados:
  3. 3. quint32 n; QImage image; QMap<QString, QColor> map; QFile file (“facts.dat”); if (!file.open(QIODevice::ReadOnly)) { std::cerr << “Não pôde abrir o arquivo para leitura: ” << qPrintable(file.errorString()) << std::endl; return; } QDataStream in(&file); in.setVersion(QDataStream::Qt_4_3); in >> n >> image >> map; A versão de QDataStream que usamos para leitura é a mesma usada para escrita. Isso deve sempre acontecer. Incluindo o número de versão por hard-code, garantimos que a aplicação pode sempre ler e escrever os dados (assumindo que está sendo compilada com Qt 4.3 ou qualquer versão posterior de Qt). QDataStream armazena dados de uma forma que podemos a ler de volta sem custos. Por exemplo, um QByteArray é representado como um contador de bytes de 32 bits seguido dos bytes. QDataStream também pode ser usado para leitura e escrita de dados brutos, sem nenhum cabeçalho de contador de bytes, usando readRawBytes() e writeRawBytes(). Tratamento de erros ao ler de um QDataStream é fácil. O stream tem um valor status() que pode ser QDataStream::Ok, QDataStream::ReadPastEnd, ou QDataStream::ReadCorruptData. Quando um erro ocorre, o operador >> sempre lê zero ou valores vazios. Isso significa que podemos geralmente ler um arquivo inteiro sem se preocupar sobre erros potenciais e checar o valor de status() no final para ver o que estamos lendo é válido. QDataStream controla uma variedade de tipos de dados C++ e Qt; a lista completa está disponível em http://doc.trolltech.com/4.3/datastreamformat.html. Podemos também adicionar suporte aos nossos tipos customizados sobrecarregando os operadores << e >>. Aqui está uma definição de um tipo customizado de dados que pode ser usado com QDataStream: class Painting { Public: Painting() { myYear = 0; } Painting(const QString &title, const QString &artist, int year){ myTitle = title; myArtist = artist; myYear = year; } void setTitle(const QString &title) { myTitle = title; } QString title() const {return myTitle; } … private: QString myTitle; QString myArtist; Int myYear; }; QDataStream &operator<< (QDataStream &out, const Painting &painting); QDataStream &operator>> (QDataStream &in, Painting &painting);
  4. 4. Aqui mostramos como implementamos o operador << : QDataStream &operator<< (QDataStream &out, const Painting &painting) { out << painting.title() << painting.artist(); >> quint32(painting.year()); return out; } Para a saída de um Painting, simplesmente imprimimos dois QStrings e um quint32. No final da função, retornamos o stream. Isto é um recurso padrão do C++ que nos permite usar uma corrente de operadores << com um stream de saída. Por exemplo: out << painting1 << painting2 << painting3; A implementação de operator>> () é parecida com a de operator<< (): QDataStream &operator>>(QDataStream &in, Painting &painting) { QString title; QString artist; Quint32 year; in >> title >> artist >> year; painting = Painting(title, artist, year); return in; } Há diversos benefícios em fornecer operadores de stream para tipos customizados de dados. Um deles é que nos permite enviar containers de stream que usam o tipo customizado. Por exemplo: QList<Painting> painting = ...; out << paintings; Podemos ler todos os containers facilmente: QList<Painting> paintings; in << paintings; Isso resultaria em um erro de compilação se Painting não suportasse <<ou >>. Outro benefício de fornecer operadores de stream para tipos customizados é que podemos armazenar valores desses tipos como QVariants, o que aumenta seu campo de uso-por exemplo, com QSettings. Isto funciona somente se registrarmos o tipo de antemão usando qRegisterMetaTypeStreamOperators<T>(), como explicado no capítulo 11. Quando usamos QDataStream, Qt toma conta da leitura e escrita de cada tipo, incluindo containers com um número arbitrário de itens. Isto nos liberta da necessidade de estruturar o que escrevemos e nos livra de realizar qualquer tipo de análise no que precisamos. Nossa única obrigação é assegurar que lemos todos os tipos exatamente na mesma ordem em que os escrevemos, deixando o Qt controlar todos os demais detalhes. QDataStream é útil para nossos formatos de arquivos de aplicação customizados e para formatos padrão da biblioteca. Podemos ler e escrever formatos binários padrão usando os operadores de stream em tipos básicos (como quint16 ou float) ou usando readRawBytes() e writeRawBytes(). Se QDataStream está sendo usado puramente para ler e escrever tipos de dados básicos do C++, não precisamos nem chamar setVersion(). Até agora, carregamos e salvamos dados aplicando um hard-coding na versão do stream, com QDataStream::Qt_4_2. Essa técnica é simples e segura, porém possui um pequeno problema: Jamais poderemos tomar vantagem de formatos novos ou atualizados. Por exemplo, se a versão mais recente do Qt adicionou um atributo a QFont (em adição ao seu tamanho de ponto, família, etc ), e nós inserimos a versão
  5. 5. diretamente para Qt_4_3, o atributo não seria salvo ou carregado. Há duas soluções. A primeira é incluir o número de versão de QDataStream no arquivo: QDataStream out(&file); out << quint32 (MagicNumber) << quint16(out.version()); (MagicNumber é uma constante que unicamente identifica o tipo de arquivo). Essa técnica assegura que sempre escrevamos os dados usando a versão mais recente de QDataStream, não importa qual seja. Quando começamos a ler o arquivo, lemos a versão do stream: quint32 magic; quint16 streamVersion; QDataStream in(&file); In >> magic >> streamVersion; if(magic != MagicNumber) { std::cerr << “Arquivo não é reconhecido por essa aplicação” << std::endl; } else if (streamVersion > in.version()) { std::cerr << “Arquivo é de uma versão mais recente da” << “aplicação” << std::endl; Return false; } in.setVersion (streamVersion); Podemos ler os dados contando que a versão da remessa é menor ou igual a versão usada pela aplicação; Se o formato do arquivo contém um próprio número de versão, podemos o usar para deduzir o número de versão ao invés de o armazenar explicitamente. Por exemplo, suponhamos que o formato de arquivo é para versão 1.3 da nossa aplicação. Podemos então escrever os dados dessa maneira: QDataStream out(&file); out.setVersion(QDataStream::Qt 4 3); out << quint32(MagicNumber) << quint16(0x103); Quando lemos de volta, determinamos qual versão de QDataStream será usada no número de versão da aplicação: QDataStream in (&file); In >> magic >> appVersion; if (magic != magicNumber) { std::cerr << “Arquivo não reconhecido por essa aplicação” << std::endl; return false; } if(appVersion < 0x1003) { in.setVersion (QDataStream::Qt_3_0); }else{ in.setVersion (QDataStream::Qt_4_3); } Neste exemplo, especificamos que qualquer arquivo salvo com versões anteriores a 1.3 da aplicação usa stream de dados versão 4 (Qt_3_0), e que arquivos salvos com versão 1.3 da aplicação usam stream de dados versão 9 (Qt_4_3). Em suma, há três políticas para controlar versões de QDataStream: incluir número de versão diretamente, escrever e ler explicitamente o número de versão, e usar números de versões de versão que estão inclusos
  6. 6. diretamente dependendo do tipo de versão. Qualquer uma dessas práticas pode ser usada para assegurar que os dados escritos por uma versão antiga de uma aplicação possam ser lidas por uma nova versão, mesmo se a nova versão estiver ligada a uma versão mais recente do Qt. Assim que escolhemos uma prática para controlar versão do QDataStream, escrita e leitura de dados binários usando Qt se torna simples e confiável. Se quisermos ler ou escrever um arquivo de uma vez, podemos evitar o uso de QDataStream e ao invés disso usar as funções write() e readAll() de QIODevice. Por exemplo: bool copyFile (const QString &source, const QString &dest) { QFile sourceFile (source); if(!sourceFile.open(QIODevice::ReadOnly)) return false; QFile destFile(dest); if (!sourceFile.open(QIODevice::ReadOnly)) return false; QFile destFile(dest); if (!destFile.open(QIODevice::WriteOnly)) return false; destFile.write(sourceFile.readAll()); return sourceFile;error() == QFIle::NoError && destFile.error() == QFile::NoError; } Na linha onde readAll() é chamado, todo o conteúdo do arquivo de entrada são lidos em um QByteArray, que é então passado à função write() para ser escrito no arquivo de saída. Ter todos os dados em um QByteArray requer mais memória do que fazer uma leitura item a item, mas oferece mais vantagens. Por exemplo, podemos então usar qCompress() e qUncompress() para comprimir e descomprimir dados. Uma alternativa menos dependente de memória é QtIOCompressor das soluções Qt. Um QtIOCompressor comprime o stream escrito e descomprime o stream lido, sem armazenar o arquivo inteiro na memória. Há outros cenários nos quais acessar QIODevice diretamente é mais apropriado do que usar QDataStream. QIODevice fornece uma função peak() que retorna os próximos bytes de dados sem mover a posição do dispositivo bem como a função ungetChar() “deslê” um byte.Essa abordagem funciona tanto para dispositivos de acesso randômico (como arquivos) e para dispositivos sequenciais (como sockets de rede). Existe também uma função seek() que seta a posição do dispositivo, para dispositivos que suportam acesso randômico. Formatos de arquivo binário fornecem a mais versátil e mais compacta maneira de armazenar dados, e QDataStream faz o acesso a dados binários muito fácil. Em adição aos exemplos dessa sessão, nós já vimos o uso de QDataStream no Capítulo 4 para ler e escrever planilhas, e nós o veremos novamente no capítulo 21, onde o usaremos para escrever e ler arquivos do mouse do Windows. Lendo e Escrevendo Texto Enquanto que formatos de arquivo binário são tipicamente mais compactos do que formatos baseados em texto, eles não são passíveis de leitura ou edição humana. Em caso onde isso é um problema, podemos usar ao invés formatos de texto. Qt fornece a classe QTextStream para leitura e escrita em arquivos de texto e para arquivos que utilizam outros formatos de texto, como HTML, XML e código fonte. Cobriremos controle a arquivos XML numa sessão separada no Cpítulo 16. QTextStream se responsabiliza pela conversão entre Unicode e a codificação local do sistema ou qualquer outra codificação, e controla transparentemente as diferentes convenções de quebra de linha usadas por diferentes Sistemas Operacionais (“rn” em Windows, “n” em Unix e Mac OS X). QTextStream usa o tipo QChar de 16
  7. 7. bits como sua unidade fundamental de dados. Em adição a caracteres e strings, QTextStream suporta tipos numéricos básicos do C++, os quais este pode converter ou reverter de strings. Por exemplo, o código a seguir escreve “Thomas M. Disch: 334/n” no arquivo sf-book.txt: QFile file(“sf-book.txt”); If (!file.open(QIODevice::WriteOnly)) { std::cerr << “Cannot open file for writing: ” << qPrintable(file.errorString()) << std::endl; return; } QTextStream out(&file); Out << “Thomas M. Disch: “ << 334 << endl; Escrever texto é muito fácil, mas a leitura pode ser um desafio já que dados textuais (diferente de dados binários escritos usando QDataStream) é fundamentalmente ambíguo. Consideremos o seguinte exemplo: Out << “Denmark” << “Norway”; Se out for um QTextStream, os dados que realmente são escritos é a string “DennmarkNorway”. Nós não podemos realmente esperar o código seguinte ler os dados corretamente: in >> str1 >> str2; Na verdade, o que acontece é que str1 pega a palavra inteira “DenmarkNorway”, e str2 recebe nada. Este problema não ocorre com QDataStream porque este armazena o tamanho de cada string na frente dos dados caractere. Para formatos de arquivos complexos, um analisador tipo full-blown pode ser necessário. Este tipo deparser trabalha lendo os dados caractere por caractere usando >> em um QChar, ou linha por linha, usando QTextStream::readLine(). No final desta sessão, apresentamos dois pequenos exemplos, um onde há leitura de um arquivo linha por linha, e outro onde a leitura é feita caractere por caractere. Para analisadores que trabalham em um texto inteiro, poderíamos ler o arquivo completo de uma vez usando QTextStream::readAll() caso não nos importemos com uso de memória, ou caso saibamos que o arquivo em questão é pequeno. Por padrão, QTextStream usa a codificação local do sistema (por exemplo, ISSO 8859-1 ou ISSO 8859-15 na América e em parte da Europa) para leitura e escrita. Isto pode ser alterado usando setCodec() como no exemplo: stream.setCodec(“UTF-8”); A codificação UTF-8 usada no exemplo é uma codificação popular com compatibilidade com ASCII que pode representar o conjunto completo de caracteres Unicode. Para mais informações sobre Unicode e o suporte de QTextStream para codificações, veja o Capítulo 18. QTextStream possui várias opções modeladas após aquelas oferecidas por <iostream>. Estas podem ser ativadas passando objetos especiais, chamados stream manipulators, no stream para alterar seu estado, ou chamando as funções listadas na Figura 12.1. O exemplo a seguir ativa as opções showbase, uppercasedigits, e hex antes de exibir o inteiro 12345678, produzindo o texto “0xBC614E”: out << showbase << uppercasedigits << hex << 12345678;
  8. 8. Figura 12.1. Funções de ativação das opções de QTexStream setIntegerBase(int) 0 Detecção automática baseada no prefixo (na leitura) 2 Binário 8 Octal 10 Decimal 16 Hexadecimal setNumberFlags(NumberFlags) ShowBase Mostrar um prefixo se a base for 2 (“0b”), 8 (“0”) ou 16 (“0x”) ForceSign Mostrar sempre os sinais em números reais ForcePoint Incluir sempre o separador decimal em números UppercaseBase Usar versões maiúsculas dos prefixos da base (“0B”, “0X”) UppercaseDigits Usar letras maiúsculas em números hexadecimais setRealNumberNotation(RealNumberNotation) FixedNotation Notação de ponto-fixo (ex.: “0.000123”) ScientificNotation Notação científica (ex.: “1.234568e-04”) SmartNotation Notações ponto-fixo ou científica, a que for mais compacta setRealNumberPrecision(int) Ativa o numero máximo de dígitos que devem ser gerados (6 por padrão) setFieldWidth(int) Ajusta o tamanho mínimo de um campo (0 por padrão) setFieldAlignment AlignLeft Tabulação no lado direito do campo AlignRight Tabulação no lado esquerdo do campo AlignCenter Tabulação nos dois lados do campo AlignAccountingStyle Tabulação entre o sinal e o número
  9. 9. setPadChar(QChar) Define o caractere usado para campos de tabulação (espaço por padrão) Opções também podem ser ativadas usando funções membro: out.setNumberFlags(QTextStream::ShowBase | QTextStream::UppercaseDigits); out.setIntegerBase(16); out << 12345678; Assim como QDataStream, QTextStream opera em uma subclasse QIODevice, que pode ser um QFile, um QTemporaryFile, um QBuffer, um QProccess, um QTcpSocket, um QUdpSocket, ou um QSslSocket. Além disso, pode ser usado diretamente em um QString. Por exemplo: QString str; QTextStream(&str) << oct << 31 << " " << dec << 25 << endl; Isto faz o conteúdo de str ser “37 25n”, já que o número decimal 31 é expresso como 37 em octal. Neste caso, não precisamos setar uma codificação no stream, já que QString é sempre Unicode. Vamos olhar um exemplo simples de um formato de arquivo de texto básico. Na aplicação Planilha descrita na Parte 1, usamos o formato binário para armazenar dados da Planilha. Os dados consistiam de uma sequência de triplas (linha, coluna, fórmula), uma para cada célula não vazia. Escrever os dados como texto é simples e direto; aqui está um extrato de uma versão revisada de Spreadsheet::writeFile(): QTextStream out(&file); for (int row = 0; row < RowCount; ++row) { for (int column = 0; column < ColumnCount; ++column) { QString str = formula(row, column); if (!str.isEmpty()) out << row << " " << column << " " << str << endl; } } Lemos nos dados da Planilha uma linha de cada vez. A função readLine() remove o código ‘n’. QString::split() retorna uma lista de string, cortando a string caso o separador dado esteja presente. Por exemplo, a linha “5 19 Total value” resulta na lista de quatro itens [“5”, “19”, “Total”, “value”] . Se temos ao menos três campos, estamos prontos para extrair os dados. A função QStringList::takeirst() remove o primeiro item em uma lista e retorna o item removido. Nós a usamos para obter os números de linha e coluna. Nós não fazemos nenhuma verificação de erros; caso leiamos um valor não-inteiro de linha ou coluna, QString::toInt() retornará 0. Ao chamarmos setFormula(), devemos concatenar os campos restantes de volta para uma única string. No segundo exemplo QTextStream, usaremos uma metodologia caractere por caractere para implementar um programa que lê em um arquivo texto e exibe o mesmo texto porém com espaços removidos das linhas e todos os parágrafos substituídos por espaços. A função tidyFile() faz o trabalho do programa: Código: void tidyFile(QIODevice *inDevice, QIODevice *outDevice) { QTextStream in(inDevice); QTextStream out(outDevice); const int TabSize = 8; int endlCount = 0; int spaceCount = 0; int column = 0;
  10. 10. QChar ch; while (!in.atEnd()) { in >> ch; if (ch == 'n') { ++endlCount; spaceCount = 0; column = 0; } else if (ch == 't') { int size = TabSize - (column % TabSize); spaceCount += size; column += size; } else if (ch == ' ') { ++spaceCount; ++column; } else { while (endlCount > 0) { out << endl; --endlCount; column = 0; } while (spaceCount > 0) { out << ' '; --spaceCount; ++column; } out << ch; ++column; } } out << endl; } Nós criamos um QTextStream de entrada e saída baseado nos QIODevices que são passados para a função. Em adição ao caractere atual, mantemos três variáveis para rastrear estado: uma contando novas linhas, uma contando espaços e uma marcando a posição atual da coluna na linha atual (para converter as abas para o número correto de espaços). A análise é feita em um laço while que itera em cada caractere no arquivo de entrada, um de cada vez. O código é um pouco sútil em alguns pontos. Por exemplo, apesar de atribuirmos 8 à variável TabSize, substituímos tabulações por espaços suficientes o suficiente para incluir tabulação até o próximo ponto de tabulação, ao invés de simplesmente substituir cada tabulação com oito espaços. Se chegarmos á nova linha, tabulação ou espaço, nós simplesmente atualizamos as variáveis de estado. Apenas quando obtemos outro tipo de caractere faremos qualquer saída, e antes de escrever o caractere escrevemos qualquer nova linha e espaço pendentes (a fim de respeitar linhas vazias e para preservar indentação) e atualizamos o estado. int main() { QFile inFile; QFile outFile; inFile.open(stdin, QFile::ReadOnly); outFile.open(stdout, QFile::WriteOnly); tidyFile(&inFile, &outFile); return 0; } Para este exemplo, nós não precisamos de um objeto QApplication, pois estamos usando apenas as classes ferramenta do Qt. Veja http://doc.trolltech.com/4.3/tools.html para a lista de todas as classes ferramenta. Nós assumimos que o programa é usado como um filtro, por exemplo: tidy < cool.cpp > cooler.cpp
  11. 11. Seria fácil estende-lo para ser possível controlar nomes de arquivo dados na linha de comando caso estes sejam dados, e para filtrar cin para cout caso contrário. Já que se trata de uma aplicação do console, há uma pequena diferença no arquivo .pro comparado ao dos que vimos para aplicações GUI: TEMPLATE = app QT = core CONFIG += console CONFIG -= app.bundle SOURCES = tidy.cpp Nós apenas fazemos o link para QtCore já qie não usamos nenhuma funcionalidade GUI. Depois especificamos que queremos habilitar saída do console em Windows e que não queremos que a aplicação execute em um pacote no Mac OS X. Para leitura e escrita arquivos planos ASCII ou arquivos ISSO 8859-1 (Latin-1), é possível usar a API do QIODevice diretamente ao invés de usar um QTextStream. É geralmente sábio fazer isto já que a maioria das aplicações precisa de suporte para outras codificações em um ou outro ponto, e apenas QTextStream fornece suporte sem custo para estes. Caso você ainda queira escrever texto diretamente em um QIODevice, você deve especificar explicitamente a flag QIODevice::Text para a função open, por exemplo: file.open(QIODevice::WriteOnly | QIODevice::Text); Na escrita, esta flag avisa QIODevice para converter caracteres ‘n’ para sequencias “rn” no Windows. Na leitura, esta flag avisa o dispositivo para ignorar caracteres ‘r’ em todas as plataformas. Podemos então assumir que o fim de cada linha é sinalizado com um caractere de nova linha ‘n’ independente da conversão de fim de linha usado pelo sistema operacional. Acessando Diretórios A classe QDir fornece maneiras livres de plataforma de acessar diretórios e obter informação sobre arquivos. Para ver como QDir é usada, vamos escrever uma pequena aplicação que calcula o espaço consumido por todas as imagens em um diretório particular e todos seus subdiretórios em qualquer profundidade. O coração da aplicação é a função imageSpace(), que recursivamente computa o tamanho acumulativo das imagens de um dado diretório: qlonglong imageSpace(const QString &path) { QDir dir(path); qlonglong size = 0; QStringList filters; foreach (QByteArray format, QImageReader::supportedImageFormats()) filters += "*." + format; foreach (QString file, dir.entryList(filters, QDir::Files)) size += QFileInfo(dir, file).size(); foreach (QString subDir, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) size += imageSpace(path + QDir::separator() + subDir); return size; } Começamos criando um objeto QDir usando o caminho dado, que pode ser relativo ao diretório atual ou absoluto. Passamos dois argumentos a função entryList(). O primeiro é uma lista de filtros de nome de arquivo. Estes podem conter os caracteres ‘*’ e ‘?’. Neste exemplo, estamos filtrando também para incluir apenas formatos de
  12. 12. arquivo que QImage pode ler. O segundo argumento especifica quais tipos de entradas queremos (arquivos normais, diretórios, drivers, etc). Iteramos sobre a lista de arquivos, acumulando seus tamanhos. A classe QFileInfo nos permite acessar os atributos de um arquivo, como seu tamanho, permissões, dono e timestamp. A segunda chamada a entryList() retorna todos os subdiretórios neste diretório. Iteramos sobre eles (excluindo . e ..) e recursivamente chamamos imageSpace() para verificar o tamanho acumulado de suas imagens. Para criar o caminho de casa subdiretório, combinamos o caminho do diretório atual com o nome do subdiretório, separando-os com uma barra. QDir trata ‘/’ como um separador de diretórios em todas as plataformas, além de reconhecer ‘’ no Windows. Quando apresentamos caminhos ao usuário, podemos chamar a função específica QDir::toNativeSeparators() para converter barras no separador correspondente de acordo com a plataforma. Vamos adicionar uma função main() ao nosso pequeno programa: int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QStringList args = QCoreApplication::arguments(); QString path = QDir::currentPath(); if (args.count() > 1) path = args[1]; std::cout << "Space used by images in " << qPrintable(path) << " and its subdirectories is " << (imageSpace(path) / 1024) << " KB" << std::endl; return 0; } Usamos QDir::currentPath() para inicializar o caminho para o diretório atual. Alternativamente, poderíamos ter usado QDir::homePath() para inicializa-lo para o diretório home do usuário. Caso o usuário tenha especificado o caminho na linha de comando, usamos esta linha. Finalmente, chamamos nossa função imageSpace() para calcular quanto espaço é consumido por imagens. A classe QDir fornece outras funções relacionadas a arquivos e diretórios, incluindo entryInfoList() (a qual retorna uma lista de objetos QFileInfo), rename(), exists(), mkdir() e rmdir(). A classe QFile fornece algumas funções de convenientes, como remove() e exists(). E a classe QFileSystemWatcher pode nos notificar quando uma mudança ocorre em um arquivo ou diretório, emitindo sinais directoryChanged() ou fileChanged(). Incorporando Recursos Até agora neste capítulo, falamos sobre acesso a dados em dispositivos externos, mas em Qt é possível também incorporar dados binários ou texto dentro do executável da aplicação. Isto é alcançado usando o sistema de recursos do Qt. Em outros capítulos, usamos arquivos de recurso para incorporar imagens no executável, mas é possível incorporar qualquer tipo de arquivo. Arquivos incorporados podem ser lidos usando QFile assim como arquivos normais no sistema de arquivos. Recursos são convertidos em código C++ pelo compilador de recursos do Qt, rcc. Podemos dizer a qmake para que inclua regras especiais para rodar rcc adicionando a seguinte linha ao arquivo .pro: RESOURCES = myresourcefile.qrc
  13. 13. O arquivo myresourcesfile.qrc é um arquivo XML que lista os arquivos a serem incorporados no executável. Vamos imaginas que estamos escrevendo uma aplicação que mantém detalhes de contato. Para a conveniência de nossos usuários, queremos incorporar os códigos de chamada internacional no executável. Se o arquivo estiver no diretório datafiles no diretório build da aplicação, o arquivo de recursos deve ser algo parecido com: <RCC> <qresource> <file>datafiles/phone-codes.dat</file> </qresource> </RCC> Da aplicação, recursos são identificados pelo prefixo de caminho :/ . Neste exemplo, o arquivo de códigos de chamada possui o caminho :/datafiles/phone-codes.dat e pode ser lido assim como qualquer outro arquivo usando QFile. Incorporar dados no executável tem a vantagem de que nunca poderá se perder e torna possível criar verdadeiros executáveis stand-alone (caso links estáticos também sejam usados). Duas desvantagens são que caso os dados incorporados precise mudar então o executável inteiro deve ser substituído, e o caminho do executável será maior já que deve acomodar os dados incorporados. O sistema de recursos do Qt fornece mais características do que as que apresentamos neste exemplo, incluindo suporte para pseudônimos de nomes de arquivo e para localização. Essas facilidades estão documentadas em http://doc.trolltech.com/4.3.resources.html. Comunicação Inter Processo A classe QProcess nos permite executar programas externos e interagir com eles. A classe trabalha assincronamente, fazendo seu trabalho no plano de fundo para que a interface do usuário permaneça responsiva. QProcess emite sinais para nos notificar quando um processo externo tem dados ou terminou. Vamos rever o código de uma aplicação pequena que fornece uma interface de usuário para um programa de conversão de imagem externo. Para este exemplo, usaremos o programa convert do ImageMagick, que é livre para a maioria das plataformas. Nossa interface de usuário é mostrada na Figura 12.2. Figura 12.2. A aplicação Conversor de Imagem A interface de usuário foi criada no Qt Designer. O arquivo .ui está entre os exemplos que acompanham o livro. Aqui, focaremos na subclasse que é derivada da classe uic-gerada Ui::ConvertDialog, iniciando com o cabeçalho:
  14. 14. #ifndef CONVERTDIALOG_H #define CONVERTDIALOG_H #include <QDialog> #include <QProcess> #include "ui_convertdialog.h" class ConvertDialog : public QDialog, private Ui::ConvertDialog { Q_OBJECT public: ConvertDialog(QWidget *parent = 0); private slots: void on_browseButton_clicked(); void convertImage(); void updateOutputTextEdit(); void processFinished(int exitCode, QProcess::ExitStatus exitStatus); void processError(QProcess::ProcessError error); private: QProcess process; QString targetFile; }; #endif O cabeçalho segue o padrão similar para subclasses de forms do Qt Designer. Uma pequena diferença dos outros exemplos que temos visto é que aqui usamos herança privada para a classe Ui::ConvertDialog. Isto previne acesso aos widgets do form de fora das funções do form. Graças ao mecanismo de conexão automática do Qt Designer (p. 28), o slot on_browseButton_clicked() é automaticamente conectado ao sinal clicked() do botão Browse. ConvertDialog::ConvertDialog(QWidget *parent) : QDialog(parent) { setupUi(this); QPushButton *convertButton = buttonBox->button(QDialogButtonBox::Ok); convertButton->setText(tr("&Convert")); convertButton->setEnabled(false); connect(convertButton, SIGNAL(clicked()), this, SLOT(convertImage())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(&process, SIGNAL(readyReadStandardError()), this, SLOT(updateOutputTextEdit())); connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(processFinished(int, QProcess::ExitStatus))); connect(&process, SIGNAL(error(QProcess::ProcessError)), this, SLOT(processError(QProcess::ProcessError))); } A chamada setupUi() cria e gera o layout de todos os widgets do form, e estabelece a conexão sinal-slot para o slot on_browserBtton_clicked(). Obtemos um ponteiro para o botão OK da caixa do botão e damos um texto diferente a ele. Nós também o desabilitamos, já que inicialmente não há imagem para ser convertida, e o conectamos ao slot convertImage(). Depois conectamos o sinal rejected() do botão (emitido pelo botão Fechar) ao slot reject() da caixa de diálogo. Depois disso, conectamos três sinais do objeto QProcess
  15. 15. a três slots privados. Sempre que o processo externo tiver dados em seu cerr, controlaremos isto em updateOutputTextEdit(). void ConvertDialog::on_browseButton_clicked() { QString initialName = sourceFileEdit->text(); if (initialName.isEmpty()) initialName = QDir::homePath(); QString fileName = QFileDialog::getOpenFileName(this, tr("Choose File"), initialName); fileName = QDir::toNativeSeparators(fileName); if (!fileName.isEmpty()) { sourceFileEdit->setText(fileName); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } } O sinal clicked() do butão Browse é automaticamente conectado ao slot on_browseButton_clicked() pelo setupUi(). Caso o usuário tenha escolhido previamente um arquivo, iniciamos a caixa de diálogo de arquivo com o nome daquele arquivo; caso contrário, usamos o diretório home do usuário. void ConvertDialog::convertImage() { QString sourceFile = sourceFileEdit->text(); targetFile = QFileInfo(sourceFile).path() + QDir::separator() + QFileInfo(sourceFile).baseName() + "." + targetFormatComboBox->currentText().toLower(); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); outputTextEdit->clear(); QStringList args; if (enhanceCheckBox->isChecked()) args << "-enhance"; if (monochromeCheckBox->isChecked()) args << "-monochrome"; args << sourceFile << targetFile; process.start("convert", args); } Quando o usuário clica no botão Convert, copiamos o nome do arquivo fonte e mudamos a extensão para combinar com o formato alvo do arquivo alvo. Usamos o separador de diretório específico dependendo da plataforma (‘/’ ou ‘’, disponíveis como QDir::separator()) ao invés de incluir barras diretamente pois o nome do arquivo ficará visível para o usuário. Nós então desabilitamos o botão Convert para evitar que o usuário acidentalmente lance conversões múltiplas, e limpamos o editor de texto que usamos para mostrar status de informação. Para iniciar o processo externo, chamamos QProcces::start() com o nome do programa que queremos executar (convert) e quaisquer argumentos que este requeira. Neste caso, passamos as flags –enhance e – monochrome caso o usuário tenha checado as opções apropriadas, seguidas pelos nomes do arquivo fonte e arquivo e arquivo de destino. O programa convert infere a conversão requerida das extensões do arquivo. void ConvertDialog::updateOutputTextEdit() { QByteArray newData = process.readAllStandardError(); QString text = outputTextEdit->toPlainText() + QString::fromLocal8Bit(newData); outputTextEdit->setPlainText(text); }
  16. 16. Sempre que o processo externo escrever para cerr, o slot updateOutputTextEdit() é chamado. Lemos o texto de erro e o adicionamos ao texto existente de QTextEdit. void ConvertDialog::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { if (exitStatus == QProcess::CrashExit) { outputTextEdit->append(tr("Conversion program crashed")); } else if (exitCode != 0) { outputTextEdit->append(tr("Conversion failed")); } else { outputTextEdit->append(tr("File %1 created").arg(targetFile)); } buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } Quando o processo termina, deixamos o usuário saber a saída e habilitamos o botão Convert. void ConvertDialog::processError(QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { outputTextEdit->append(tr("Conversion program not found")); buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } } Se o processo não pôde ser iniciado, QProcess emite error() ao invés de finished(). Reportamos qualquer erro e habilitamos o botão Click. Neste exemplo, fizemos a conversão de arquivos assincronamente-ou seja, dizemos a QProcess para rodar o programa convert e retornar o controle à aplicação imediatamente. Isto mantém a interface responsiva enquanto o processo ocorre no plano de fundo. Mas em algumas situações precisamos que o processo externo seja finalizado antes que possamos ir mais a fundo em nossa aplicação, e em tais casos precisamos que QProcess opere sincronamente. Um exemplo comum onde comportamento assíncrono é indesejável é em aplicações que suportam edição de texto plano usando o editor de texto preferido do usuário. Isto é direto de se implementar usando QProcess. Por exemplo, vamos assumir que temos o texto em um QTextEdit, e que fornecemos um botão Edit no qual o usuário pode clicar, conectado ao slot edit(). void ExternalEditor::edit() { QTemporaryFile outFile; if (!outFile.open()) return; QString fileName = outFile.fileName(); QTextStream out(&outFile); out << textEdit->toPlainText(); outFile.close(); QProcess::execute(editor, QStringList() << options << fileName); QFile inFile(fileName); if (!inFile.open(QIODevice::ReadOnly)) return; QTextStream in(&inFile); textEdit->setPlainText(in.readAll()); } Usamos QTemporaryFile para criar um arquivo vazio com um nome único. Não especificamos nenhum argumento para QTemporaryFile::open() já que este, por conveniência, mantém o padrão para abertura
  17. 17. no modo escrita-leitura. Escrevemos o conteúdo do editor de texto no arquivo temporário, e depois fechamos o arquivo porque alguns editores de texto não conseguem trabalhar em arquivos já abertos. A função estática QProcess::execute() executa um processo externo e bloqueia até que o processo esteja finalizado. O argumento editor é uma QString armazenando o nome do executável de um editor (“gvim” por exemplo). O argumento options é uma QStringList(contendo um item, “-f”, se estivermos usando gvim). Após o usuário tiver fechado o editor de texto, O processo termina e a chamada execute() é retornada. Nós então abrimos o arquivo temporário e lemos seu conteúdo no QTextEdit. QTemporaryFile automaticamente deleta o arquivo temporário quando o objeto sai de escopo. Conexões sinal-slot não são necessárias quando QProcess é usado sincronamente. Caso um controle mais detalhado do que o fornecido pela função estática execute() seja necessário, podemos usar uma outra metodologia. Isto envolve criar um objeto QProcess e chamar start() neste, e então força-lo a bloquear através da chamada QProcess::waitForFinished(). Veja a referência a QProcess para um exemplo que usa esta estratégia. Nesta seção, usamos QProcess para nos dar acesso a funcionalidades pré-existentes. Usando aplicações que já existem pode salvar tempo de desenvolvimento e pode nos insular de detalhes que não são tão importantes para o propósito da aplicação principal. Outro jeito de acessar funcionalidade pré-existente é usando um link para uma biblioteca que o fornece. Mas caso não haja biblioteca que sirva adequadamente, envolver uma aplicação console usando QProcess pode funcionar bem. Outro uso de QProcess é lançar outras aplicações GUI. Entretanto, se nosso alvo é comunicação entre aplicações mais do que simplesmente executar uma sobre a outra, é uma saída melhor tê-los comunicando diretamente, usando as classes de networking do Qt ou a extensão ActiveQt no Windows. E se quisermos lançar o navegador de internet favorito do usuário ou seu cliente de email favorito, podemos simplesmente chamar a função QDesktopServices::openUrl().

×