17

Click here to load reader

Cap12

Embed Size (px)

Citation preview

Page 1: Cap12

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.

Page 2: Cap12

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:

Page 3: Cap12

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);

Page 4: Cap12

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

Page 5: Cap12

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

Page 6: Cap12

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 (“\r\n” em Windows, “\n” em Unix e Mac OS X). QTextStream usa o tipo QChar de 16

Page 7: Cap12

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;

Page 8: Cap12

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

Page 9: Cap12

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 25\n”, 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;

Page 10: Cap12

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

Page 11: Cap12

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 “\r\n” 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

Page 12: Cap12

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

Page 13: Cap12

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:

Page 14: Cap12

#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

Page 15: Cap12

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);

}

Page 16: Cap12

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

Page 17: Cap12

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().