Tải bản đầy đủ (.pdf) (45 trang)

The book of qt 4 the art of building qt applications - phần 8 doc

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (415.28 KB, 45 trang )

10 TheGraphics Library“Arthur”
In XOR mode (QPainter::CompositionMode_Xor),the Painter links thealpha value
of thesource(subtracted from theinverse of thedestination)tothe destination,
thealpha value of which it subtractsfromthe inverse of thesource. Theresultof
this operation is shownonthe left side of Figure 10.17.
The clear mode displayed on theright (QPainter::CompositionMode_Clear) is used,
for example, to stencilout masksfromfigures. Foreach pixel in thesourcein
which thealpha channelhas avalue otherthan0,the Painter in clearmode sets
thealpha value of thedestination pixel to 0,thus making thecorresponding pixel
transparent.
Figure 10.17:
TheXOR operator
links source and
destinationwithan
exclusiveOR; Clear
enables complete
figures to be
stencilledout of
images.
Usingthe DestinationOutOperatoronaPainterPath
We willadjustthe examplefromSection 10.10.2 (page 309) so that acompositing
operatorcoversthe area in adarkcolor.The result should matchthatshownin
Figure 10.18.
ThePainter pathinpaintEvent()remains thesame; weagaininstantiate thePainter
andactivateanti-aliasing.Thenwedrawthebackground.For thepaintbrush,
weselectblack withasemitransparentalpha channel. With theDestinationOut
operator, thePainter paththus acquires itsblack andsemitransparentcoloring:
// composition/paintwidget.cpp
#include <QtGui>
#include "paintwidget.h"
PaintWidget::PaintWidget(QWidget


*
parent)
:QWidget(parent)
{
}
314
10.10 ComplexGraphics
voidPaintWidget::paintEvent(QPaintEvent
*
/
*
ev
*
/)
{
QPainterPathpath;
path.cubicTo(rect().topLeft(),rect().bottomLeft(),
rect().bottomRight());
path.cubicTo(rect().topRight(),rect().bottomRight(),
rect().bottomLeft());
QPainterp(this);
p.setRenderHint(QPainter::Antialiasing, true);
p.drawTiledPixmap(rect(),QPixmap("qt.png"));
p.setBrush(QColor::fromRgba(qRgba(0,0,0,128)));
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.drawPath(path);
}
Thefact that thewidgetitselfisopaquemeans that most of thecompositingop-
eratorsinthisexam pleare notveryexciting,astheyonlyrevealtheir full effectsif
asourceand destinationhaveanon-opaquealpha channel.

Figure 10.18:
DestinationOut
darkens thePainter
path.
Nevertheless, compositingisaninteresting alternativetoclipping,which cannot
offercapabilitiessuchasalpha transparencyor anti-aliasing.Thisadvantage is
offset at times, however,particularlyunder X11, byprogramsthatrun considerably
more slowly.
315

11
Chapter
Input/OutputInterfaces
Youcan hardlyimagine anyapplicat iontodaythat does notaccess files,networks,
or externalprocesses. Consequently,Qt4provides interfaces for communicating
withthe environment that areindependent of theoperating system.
Althougheach of theoperating systemssupported byQt provides interfaces to deal
withthe various kindsofI/O,there is sadlyno uniformstandardfor managing this
functionality.These circumstances often force programmers to completelyredesign
thecode if theywantto, for example, send adatastreamacrossanetworkinstead
of savingittoafile. Qt 4gets around this problembyprovidingabase classcalled
QIODevice, which provides aplatformfor allI/O interfaces.
11.1The QIODevice ClassHierarchy
QIODeviceimplementsoperationssuchasreading andwriting on onedevice. Qt
also considersanetworkconnectiontobeadevice. Of course,there aresome
317
11 Input/OutputInterfaces
restrictions,because stream-oriented connections (alsocalled sequentialconnec-
tions ), such as thoseimplemented via TCP, arenot available for limitless access.
Figure 11.1:

Thebaseclass
QIODevice andits
specializations
QUdpSocket QTcpSocket
QAbstractSocket QBuffer
QTemporaryFile
QFile QProcess
QIODevice
QIODeviceisanabstract class, so thedeveloperonlyever instantiates itssubclasses.
It represents thelowestcommonden ominator of alltypesofinput andoutput
operations. Fi gure 11.1provides an overviewof theinput/output classesthatare
basedonQIODevice.
11.1.1 DerivedClasses
QAbstract Socketcannotbeuseddirectlyas thebaseclass for socket-basedcom-
munication,incontrasttoits subclassesQUdpSocketand QTcpSocket. QUdpSocket
enablescommunicationsvia the UserDatagram Protocol (UDP). This works with-
outaconnectionand provides no guarantee that thedatasentwillarriveintact or
in thecorrect order. Due to the lack of correctivemeasures, however,itisconsid-
erablyfaster than the Transmission Control Protocol (TCP),via which theQTcp-
Socketclass se ndsdata.
In contrasttoUDP,TCP connections areconnectionoriented andensureareliable
transfer of data. Manyof theprotocols populartoday,suchasHTTP,which is used
commonlyin theWorld Wide Webfor transmitting web pagesand downloads,are
basedonTCP.
TheQBuffe rclass allowsyou to write to QByteArrayinstancesasiftheywereQIO-
Device-baseddevices.Wewerealreadyintroducedtothisprocedure in Section8.8
on page 243, andwewilltake afurther look at it on page 322 in connectionwith
QDataStream.
Probablythemostfrequentlyused subclass of QIODeviceisthe QFile subclass.We
learnedabout it sabilityto read andwrite files on thelocal filesystem for thefirst

time on page 113.
In casethere is notenoughmemoryto storetemporarydatavia QBufferina
QByteArray,QTemporaryFileisavailable.IncontrasttoQFile,thisclass generates
afilename independentlyandensures that this name is unique, so that it will not
318
11.1 TheQIODevice ClassHierarchy
overwrite ot herfilesbymistake.The temporaryfileisstoredbyQTemporaryFile
beneaththe temporarydirectoryused byQt. This directory’s location is revealed
bythestaticmethod QDir::tempPath(). As soon as theQTemporaryFileobject is
deleted,the temporaryfileassociatedwithitisalsoautomaticallydeleted.
QProcess is also ba sedonQIODevice. This classenablesprocessestobestarted
withadditional argumentsand permitscommunication withthemvia theQIODe-
vicemethods.Inaddition theclass can select ivelymanipulate theenvironment
variablesofthe process.
11.1.2Opening I/ODevices
EveryQIODevicemustfirstbeopenedbeforeitcan be used.The open() method
is available for this purpose, an dits argumentsdescribe in detail howthedevice
in question is to be accessed, for example, whether theprogram (and thus the
enduseraswell) should haveonlywrite or onlyread permissions. This method is
thereforesimilartothe POSIXfunction open().
Table11.1:
Parameters of the
QIODevices::open()
method
Flag Value Description
QIODevice::NotOpen 0x0000 Deviceisnot open(notauseful detail
for open())
QIODevice::ReadOnly0x0001Deviceisopenedfor reading
QIODevice::WriteOnly0x0002Deviceisopenedfor writing
QIODevice::ReadWrite ReadOnly|

WriteOnly
Deviceisopenedfor reading and
writing
QIODevice::Append 0x0004Deviceisopenedinappend mode and
alldataaddedtothe end
QIODevice::Truncate0x0008Ifpossible,all previous contents are
deleted whendeviceisopened
QIODevice::Text0x0010When reading text, linebreaks are
converted to thesystem-specific
end-of-lineindicators(Windows: \r\f,
Unix/OSX:\r) andvice-versa when
writing
QIODevice::Unbuffered0x0020Direct access, allbuffers under device
areignored
Table 11.1showsthe possible accessflags represented as powersofbase2,sothat
theycanbecombined in anywayyou like(at leasttheoretically)byusingalogical
OR operator(|).WithReadWrite, Qt doesthisitself: This flagcombines ReadOnly
319
11 Input/OutputInterfaces
andWriteOnly.Since each devicemayignore individualflags that do notapplyto
it,there is littleriskthatthe devicedoesnot behaveexactlyto theprogrammer’s
expectations. In this case, you should checkthe APIdocsfor exceptions.
This meansthatthere is no reason not to make useofthe veryfinelystructured
accessmethods.Inplain language,ifyou onlywan ttoreadfromafile, then
you should openthe filewithReadOnly.The operating system can under certain
circumstances manage without resource-intensivelocking,and theprogram will
getthe files it wants muchmorequickly.Inaddition theapplicationdoesnot run
anydangerofoverwriting files byaccidentwhenreading.
11.2Access to LocalFiles
TheQFile classwas used to openfilesanumber of timesinthe precedingchapters.

When doing this wepassedthe filetobeopenedasanargument in theconstruc-
torand then openedthe file. Belowwehaveanewsituation, in which weopen
traditional FILE pointerswithQFile.
To demonstratethiswewillwrite asmall program that removes allthe emptylines
andcomment lines from afile.The hash symbol (#)atthe beginning of alineis
assumedtobethe comment sign.
Ourprogram is invoked from thecommand lineand expectsthe name of thefile
to be analyzed as thefirstargument.Ifthere is asecondargument,itwrites the
output to thefile namedthere.Otherwise, themodifiedfile appears on theconsole
via thestandardoutput, stdout.
It is remarkable that wedonot even requireaQCoreApplicationobject for this
example, sinceQFile is notdependent on an eventloop.
In themain() function wefirstcheck whether thereisatleast oneargument apart
from thenameofthe executable.Thenwetryto openthe filefor reading.Ifitdoes
notexist, open() announces an errordue to theReadOnlyaccess, which wecatch
withanerror message.Thankstothe TextFlag, QFile converts thelineendings
whenreading to thecorresponding Unixconventionsifnecessary,for example,
under Windows(seeTable 11.1):
// extractessentials/main.cpp
#include <QtCore>
#include <iostream>
#include <stdio.h>
using namespace std;
intmain(intargc, char
*
argv[])
{
if (argc <2) {
320
11.2 Access to LocalFiles

cout << "Usage: "<< argv[0]<< "infile [outfile]"<< endl;
return1;
}
QFile in(argv[1]);
if(!in.open(QIODevice::ReadOnly|QIODevice::Text)) {
cerr << "File "<< argv[1]<< "doesnotexist" << endl;
}
QFile out;
if (argc >= 3) {
out.setFileName(argv[2]);
if (out.exists()) {
cerr << "File"<< argv[2]<< "alreadyexists" << endl;
return1;
}
if(!out.open(QIODevice::WriteOnly|QIODevice::Text)) {
cerr << "Failed toopen file "<< argv[2]<<
"forwriting"<< endl;
return1;
}
}
else
if(!out.open(stdout,QIODevice::WriteOnly|QIODevice::Text)) {
cerr << "Failed toopen standardoutput forwriting"<< endl;
return1;
}
while (!in.atEnd()) {
QByteArrayline =in.readLine();
if (!line.trimmed().isEmpty() &&
!line.trimmed().startsWith(’#’))
out.write(line);

}
in.close();
out.close();
return0;
}
Then wecheck whether thereisatleast onemoreparameter suppliedonthe com-
mand line. Whether or notthere is one, werequire asecondQFile instance,which
weallocate on thestack without passing an argument to theconstructor.Ifthe
second parameter exists,thenwepassittothe QFile object ,via setFileName(), as a
filename. Beforeweoverwrite thefile,using theQIODevice::WriteOnlyparameter,
weuse theexists() method to warnthe user andexit theprogram.Onlynowdo we
openthe file.
If theuserhas notpassedasecond parameter to theprogram,wedirectthe output
to thestandardoutput. To do this weuse an overloadedvariation of open(), which
apartfromthe accesspermissions, expectsaFILE pointer as thefirstargument.The
321
11 Input/OutputInterfaces
CInclude stdio.hdefinesaseries of FI LE pointers, includingstdout, which pointsto
thestandardoutput.
In thefollowingloop wereadout thecontents of thefile namedbythefirst
command-lineargument,linebyline. Foreach line, wecheck whether theline
is emptyor if it begins withacomment sign.trimmed()additionallyremoves all
whitespaces at thebeginning andend of alinesothatthe program willalsorec-
ognizelines consisting of forgotten emptyspacesasemptylines andindented
commentsascomment lines.
Alllines that do notmat ch thecriteria for exclusionlandinout,which is either the
standardoutputoranewfile, dependingonthe parameters.
Finallyweclose both files,tobeonthe safe side.However,aslongasweplace the
QFile object on thestack or ensure that objectslocat ed on theheapare deleted
beforethe program terminates,anexplicit close()isnot necessary,because the

QFile destructor doe sthisfor us.
11.3SerializingObjects
In C++, dataisusuallyrepresented as an object .Whendataisinthisform, pro-
gramscannotsaveitinfilesorsenditacrossthe networkdirectly.Instead, the
developermustfirstspecifywhich propertiesofanobject he wants to saveand in
whatsequencehewants to send them.
What is involved is taking theobjectsapart andplacing theirbasic components
“onaconveyor belt.” To restorethem, dataistakenfromthe conveyor belt and
packedback into an object.These procedures arereferredtoas serializing and
deserializing.(In interprocesscommunication,where this procedureisalsoapplied,
theterms marshalling and demarshalling arealsoused.)
TheQDataStream classisresponsible in Qt for theserializationofall datatypes. It
thereforeworks on allQIODeviceclasses. On page 243 weusedthe classtopack a
listofstringlists into aQByteArrayused for adrag-and-drop operation:
QByteArrayencodedData;
QDataStreamstream(&encodedData,QIODevice::WriteOnly);
ThealternativeQDataStream constructor used here simplifies handlingthe QByteAr-
ray,whereas themainconstructor demands, as an argument,apointer to the
QIODevicewithwhich it is to operate. Theabovecode thereforecorresponds to
thefollowingcode that uses thestandardconstruct or:
QByteArrayencodedData;
QBufferbuffer(&encodedData);
buffer.open(QBuffer::WriteOnly);
QDataStreamstream(&buffer);
322
11.3 SerializingObjects
To serializethe dataofaQByteArray,therefore, you essentiallyuseaQBuffer. We
alreadykn owoneapplicationofthisfromSection 8.8: sendingdatabetween pro-
gramsvia drag anddrop.
So that this can also function across networkconnections,for example, theformat

of QDataStream is platformindependent.Thus,astream serialized on aPowerPC
can thereforebetransferred back to an object on an Intel computer wit hout any
problem.
TheQDataStream format haschanged several timesthroughoutthe development
of Qt, however,and willcontinue to do so in thefuture. This is whytheclass has
different version types: If you tryto bindaQDataStream to aspe cific version using
setVersion(), then it will be sent correctlyin this format, even in later Qt versions,
andwillbereadable on theoth er side.
In ordertoreaddataintoadatastreamyou useits << operator:
QByteArrayencodedData;
QDataStreamstream(&encodedData,QIODevice::WriteOnly);
QString text ="Nowcomesatimestamp";
QTime currentTime =QTime::currentTime();
stream<<text << currentTime;
We can observehowQDataStream is used in practice to savedatatoafile in the
followingexample, in which datasets arerepresented byaDataset classdefinedas
follows:
// record/record.h
#ifndef RECORD_H
#define RECORD_H
#include <QString>
#include <QDataStream>
#include <QDebug>
class Dataset
{
private:
QString m_surname;
QString m_name;
QString m_street;
intm_streetnumber;

intm_zip;
QString m_locality;
public:
Dataset(QString name, QString surname, QString street,
intstreetnumber,intzip,QString locality)
323
11 Input/OutputInterfaces
{
m_name= name;
m_surname =surname;
m_street=street;
m_streetnumber=streetnumber;
m_zip=zip;
m_locality =locality;
}
Dataset() {}
QString name() const { returnm_name; }
QString surname() const { returnm_surname; }
QString street() const { returnm_street; }
intstreetnumber() const { returnm_streetnumber; }
intzip() const { returnm_zip; }
QString locality() const { returnm_locality; }
void setName(const QString&name) { m_name =name; }
void setSurname(const QString&surname) { m_surname =surname; }
void setStreet(const QString&street) { m_street=street; }
void setStreetnumber(intstreetnumber) { m_streetnumber=
streetnumber; }
void setZip(intzip) { m_zip=zip; }
void setLocality(const QString&locality) { m_locality =locality; }
Record(const Record&r) {

m_surname =r.m_surname;
m_name =r.m_name;
m_street=r.m_street;
m_streetnumber=r.m_streetnumber;
m_zip=r.m_zip;
m_locality =r.m_locality;
}
Record&operator=(const Record&that) {
m_name =that.m_name;
m_surname =that.m_surname;
m_street=that.m_street;
m_streetnumber=that.m_streetnumber;
m_zip=that.m_zip;
m_locality =that.m_locality;
return
*
this;
}
} ;
Each field in thedataset hasagetmethod andacorresponding setmethod. It
is important that theget methods arealwaysdeclaredasconst.Thisisnot only
better for thecompiler, butitalsohelps us whenserializingdata. In additionwe
requirethe copyoperatordataset(constdataset& ds)and theassignment operator
operator=,since wemustcopytheclass in avalue-based manner.
324
11.3 SerializingObjects
11.3.1DefiningSerializationOperators
Finally,wedefine operator<<()for serializing, which transfersadataset into a
QDataStream.The code showsthe reason theget methods name(), surname(), and
so on mustbedeclaredasconst:The dataset instance is declared to be const:

// record/record.h(continued)
QDataStream&operator<<(QDataStream&s,const Record&r)
{
s<< r.name() << r.surname() << r.street()
<< (qint32)r.streetnumber() << (qint32)r.zip() << r.streetnumber();
returns;
}
In theoperatordefinition wenowonlyneed to specifytheorder of thedataele-
mentsinthe stream.Inaddition,wemustcastprimitivedatatypes(PODs), such as
integers, to aplatform-independenttype definitionhere, at thelatest. An overview
of allthese type definitions can be found in SectionB.6 in AppendixBonpage 422.
We nowdefinethe oppositeoperator>>(), which converts datafromaQDataS-
treamintoadataset object.Todothisweinstantiate aQString andaqint32 and
usethese to read datainthe orderinwhich theywerereadinbyoperator<<().
Then wefill therespectivepropertyof thepasseddataset instance usingthe corre-
sponding setmethod. Finallywepassonthe datastreamusing return,even though
wehavenot changeditinthismethod:
// record/record.h(continued)
QDataStream&operator>>(QDataStream&s,Record(&r))
{
QString data;
qint32 number;
s>> data;
r.setName(data);
s>> data;
r.setSurname(data);
s>> data;
r.setStreet(data);
s>> number;
r.setStreetnumber(number);

s>> number;
r.setZip(number);
s>> data;
r.setLocality(data);
returns;
}
#endif // RECORD_H
325
11 Input/OutputInterfaces
11.3.2SavingSerialized Data to aFile andReading from It
Nowthat wehavedefinedthe dataset datastructure andits serializationopera-
tors, wecan write asmall exampleprogram to workwiththem: It provides the
saveData()and readData()functionssothatsuitable datacan be stored to afile or
read outfromit.
saveData()opens theoutputfile initiallyin write mode,installs theQDataStream
instance ds on topofthis, andsets theversion to themostcurrent version (at
presstime, QDataStreamversion 4.0). To ensure that thefile hasbeen written by
ourprogram,wereservethe first 32 bitsfor aso-called MagicNumber.Weals o
includeinformation on theversion in asecondfield(here,1). Then weserialize
each dataset in thelist, write it to thefile,and then closeit:
// record/main.cpp
#include <QtCore>
#include <iostream>
#include "record.h"
using namespace std;
void saveData(const QList<Record> &data,const QString &filename) {
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) return;
QDataStreamds(&file);
ds.setVersion(QDataStream::Qt_4_0);

// Magic number
ds<< (quint32)0xDEADBEEF;
// Version
ds<< (qint32)1;
foreach(Recordr,data)
ds<< r;
file.close();
}
ThereadData()function hasthe task of opening afile (the name of which it is given
as astring),analyzingthe contents,and reading them out. In this caseweagain
openthe file, butthistimeinreadmode,and weagaininstall theQDataStream
andset thedesired datastreamversion.Nowwecheck, usingthe MagicNumber,
whether thefile reallydoesoriginate from us,and wealsocheck theself-defined
version.Ifeverythingiscorrect ,wecan nowread outthe information in thefile
dataset bydataset, to itsend,close it,and providethe datastructure weobtained
to therequester:
// record/main.cpp (continued)
QList<Record> readData(const QString &filename) {
326
11.3 SerializingObjects
QFile file(filename);
file.open(QIODevice::ReadOnly);
QDataStreamds(&file);
ds.setVersion(QDataStream::Qt_4_0);
// Magic number
quint32 magic;
ds>> magic;
if (magic != 0xDEADBEEF) {
qWarning("Wrong magic!\ n");
returnQList<Record>();

}
// Version
qint32 version;
ds>> version;
if (version != 1) {
qWarning("Wrong version!\ n");
returnQList<Record>();
}
QList<Record> recordList;
Recordrecord;
while (!ds.atEnd()) {
ds>> record;
recordList.append(record);
}
file.close();
returnrecordList;
}
Nowwehaveeverythingweneed for amainprogram that wecan usetotryout
thefunctionalityof ourmethods.Wefirstcreatetwodatasets,which wewillinsert
into atypedQList.Wepassthis, withafilename, to saveDat a(). Then wedelete
allthe entriesinthe list, so that afterwarditcan be refilledwiththe resultsfrom
readData():
// record/main.cpp (continued)
intmain()
{
QList<Record> data;
data.append(Datensatz("Tilda","Tilli","Rosenweg",4,20095,
"Hamburg"));
data.append(Datensatz("Lara","Lila","Lilienweg",14, 80799,
"Munich"));

saveData(data,"file.db");
data.clear();
data=readData("file.db");
foreach(Recordrecord, data)
cout << qPrintable(record.surname()) << endl;
return0;
}
327
11 Input/OutputInterfaces
If wenowoutput thelastnam es from each dataset read, thestandardoutputwill
displaythestrings Tilli andLila on thescreen.
Thehelperfunction qPrintable() provides supportinoutputting QStringobjects,
making useofthe toLocal8Bit()method internally.
11.4Startingand ControllingProcesses
Nowandagainyou maywanttomake useofthe services of command-linebased
programs, particularlyon Unix-based operating systems. QProcess is responsible
for executingand controllingsuchexternalprocesses. Because it inherits from
QIODevice, this classisinapositiontoreadthe output of processesand to create
inputs.Inaddition it hasmethods for manipulating theenvironment variablesofa
process.
QProcess belongstothe groupofasynchronousdevices:Assoon as dataiswaiting
or otherevents occur, theclass sets off asignal. Thecorresponding callreturns
immediately.You can useQProcessfor operationsthatare shortenoughnot to
block theGUI,oreven usethemsynchronouslyin threads.Thisbehaviorappliesin
thesamewayfor allasynchronous, QIODevice-basedclasses.
11.4.1Synchronous Useof QProcess
In thefollowingexamplewecan look at thecontents of archivefiles, created us-
ingthe system tool tar, in aQListWidget(Figure 11.2).Each fileinthe tarfile
should formaseparateentryin thelist. We passthe archivetothe program as
acommand-lineargument;ifthisargument is missing,weterminate theprogram

immediately:
// showtar/main.cpp
#include <QtGui>
intmain(intargc, char
*
argv[])
{
if (argc <2)
return1;
QApplication app(argc, argv);
QProcess tar;
QStringList env=QProcess::systemEnvironment();
env.replaceInStrings(QRegExp("ˆLANG=(.
*
)"),"LANG=C");
tar.setEnvironment(env);
QStringList args;
args<< "tf"<< argv[1];
328
11.4 Starting and Controlling Processes
Otherwise, weinstantiate QApplicationand QProcess. Then weensurethattar
displaysits output in English—localized output would irritate ourparser. Forthis
purposewelook for theLANGvariable in theenvironment variablesofthe process,
which wecan obtain from thesystemEnvironment() method as astringlist, and
replaceitwithLANG=C, thestandardlocale.
Figure 11.2:
showtar displays the
contents of a .tar
archive.
Once wehavepassedthe newenvironment to theprocess withsetEnvironment(),

wecollect theargumentswithwhich wewanttoinvoketar into astringlist: Given
theflags tf,the tarprogram lists thecontents of an archivefile,which wealso
includehereasthe second argument to tar.
We passonthe finishedargument list, together withthe program name tar, to the
start()method, which nowruns thecommand tartf filename :
// showtar/main.cpp (continued)
tar.start("tar",args);
QByteArrayoutput;
while (tar.waitForReadyRead() )
output +=tar.readAll();
Since this method returnsimmediately,yet wewanttoworksynchronously,we
usewaitForReadyRead()towaituntil thefirstdataarrives.Wecollect this in a
QByteArrayandcontinue waiting until theprocess is finisheddeliveringdata.
We nowbegintoparse theoutputbychopping up thelineendsand putting th em
first into astringlist:
// showtar/main.cpp (continued)
QStringList entries=QString::fromLocal8Bit(output).split(’\ n’);
entries.removeLast();
QListWidgetw;
QIcon fileIcon =app.style()->standardIcon(QStyle::SP_FileIcon);
QIcon dirIcon =app.style()->standardIcon(QStyle::SP_DirClosedIcon);
329
11 Input/OutputInterfaces
foreach(QString entry,entries) {
if (entry.endsWith(’/’))
newQListWidgetItem(dirIcon, entry,&w);
else
newQListWidgetItem(fileIcon, entry,&w);
}
w.show();

returnapp.exec();
}
The8-bit encodeddataisconverted bythefromLocal8Bit()method to Unicode,as
it can be understood byQString. We removethe final entry,asitisempty,becau se
thefinallineofthe taroutputalsoendswitha\n.
We nowinstantiatethe QListWidgetinwhich wewanttodisplaythecontents of
thetar archive. We packeach entryinto aQListWidgetItem in such awaythat
wecan distinguishbetween directoriesand files:Weembellishthemwithdifferent
icons, which wecan take from theStyle used.Thishas aseriesofstandardicons,
especiallyfor input/output operations. We can accessthe currentQStyle object via
theQApplicationmeth od style(). Allwehavetodonowis displaythelistwidget
andpassoncontrol to theeventloop.
Theresultshouldlook similartothatshowninFigure11.2. If you tryoutthisexam-
ple, you willrealizethatyou hardlynotice thedelayat thebeginning,particularly
withsmallerarchives.Thingsare different withprocessesthatperformmorecom-
plexoperations, such as searchingdirectories,which can take awhile even on very
modern hard drives.
11.4.2Asynchronous Useof QProcess
Thefollowingexampledemonstrates theasynchronoususe of QProcess: TheLine-
ParserProcess classreads outthe output of aprocess asynchronouslyandstores
it,justasinthe previous example, as itemsinaQListWidget. We implementitas
asubclassofQProcess. Theonlyslot werequire here is calledreadData(). In this
wemustaccess theinstanceofQListWidget, which is whywemake provision for a
corr esponding member variable:
// lineparserprocess/lineparserprocess.h
#ifndef LINEPARSERPROCESS_H
#define LINEPARSERPROCESS_H
#include <QProcess>
class QListWidget;
330

11.4 Starting and Controlling Processes
class LineParserProcess :public QProcess
{
Q_OBJECT
public:
LineParserProcess(QListWidget
*
w,QObject
*
parent=0);
protected slots:
void readData();
protected:
QListWidget
*
listWidget;
} ;
#endif // LINEPARSERPROCESS_H
In theconstructor wefirstconnect thereadyRead()signaltoour newslot.Thenwe
againensurethatthe output of theprocess is notinlocalized form:
// lineparserprocess/lineparserprocess.cpp
#include <QtGui>
#include <QDebug>
#include "lineparserprocess.h"
LineParserProcess::LineParserProcess(QListWidget
*
w,QObject
*
parent)
:QProcess(parent),listWidget(w)

{
connect(this,SIGNAL(readyRead()),SLOT(readData()));
QStringList env=systemEnvironment();
env.replaceInStrings(QRegExp("ˆLANG=(.
*
)"),"LANG=C");
setEnvironment(env);
}
void LineParserProcess::readData()
{
QByteArrayline;
while (!(line =readLine()).isEmpty())
newQListWidgetItem(QString::fromLocal8Bit(line),listWidget);
}
In readD at a()wereadinall datatothe final linebreak in thedatastream, with
readLine(), linebyline. This procedureissafe,because weknowthat \n is the
last charactertobereadinthe ou tput,and sooner or later wehavereadall the
characters anyway.Whenthere is no more newdata, lineremains emptyandthe
program returnsfor thetimebeing to theeventloop, until readyRead()signals that
newdataisarriving.
Nowweconvertthe 8-bit encodedlines,asinthe previous example, withfrom-
Local8Bit()toaQStringand insert thecontents into aQListWidgetItem. Since we
331
11 Input/OutputInterfaces
specifytheparentwidgetasthe second argument,thisisimmediatelyincluded in
thelistwidget.
Nowwecan usethe class, for example, to listadirectorytree recursivelywithls. As
before, weexpect to receivethe starting directoryas acommand-lineargument.
Afterwehaveensured that an argument hasbeen passed, weinstantiate aQList-
Widget(apartfromthe obligatoryQApplicationobject)and aLineParserProcess

(which contains apointer to theinstanceofthe QListWidget):
// lineparserprocess/main.cpp
#include <QtGui>
#include "lineparserprocess.h"
intmain(intargc, char
*
argv[])
{
if (argc <2) return1;
QApplication app(argc, argv);
QListWidgetw;
LineParserProcess process(&w);
process.setWorkingDirectory(QString::fromLocal8Bit(argv[1]));
process.start("ls",QStringList() << "-Rl");
w.show();
returnapp.exec();
}
Insteadofpassing theoriginaldirectoryas an argument to ls,wechangethe pro-
cess’scurrent working directoryto thecorresponding directorywithsetWorkingDi-
rectory(). Then westart theprocess; theargument -R lensures arecursiveand
detailedlis ting of allfilenamesbeneath thecurrent path. Finallywedisplaythe
widgetand enter theeventloop.
If you tryoutthisexam ple, you willnoticethatthe GUI doesnot lock while it is re-
ceivingnewdatafromthe processstarted.Thistype of asynchronousprogramming
is also referred to as event loop programming.
11.5Communicationinthe Network
Networkfunctionalityin Qt is also basedtoalarge extentonQIODevice. This is not
acomponentofthe QtCorepackage butisstoredinalibrarycalledQtNetwork. To
make this accessible to aQtapplication, the.profile mustcontain thefollowing
line:

QT +=network
Thereisalsoaseparatemeta-include filefor theQtNetworklibrarythat contains
allthe otherheaders. Thefollowinglineissufficient to integrate this into the
applicationsourcecode:
332
11.5 Communication in theNetwork
#include <QtNetwork>
Themodule consists of theQIODevicesubclassesQAbstractSocket, QTcpSocket, and
QUdpSocket, andalsocontainsaclassQTcpServer th at enablesthe implementation
of TCP-basedservices.Inaddition theQtNetworkalsocontainsfullimplementa-
tions, in theclassesQHttp (see page 361) andQFtp, twoofthe most common
Internetprotocols.Inadditon, theQHostAddressclass,which encapsulates host
namesand IP addresses, is alreadyIPv6-capable.
Thanks to theQNetworkProxy class, themodule hashad aSocks-5 proxy imple-
mentationfor UDPand TCPaswellasproxy supportonthe user layer for HTTPand
FTP since Qt 4.1. TheclassesQHttp andQTcpthus containmethods for specifying
an applicat ionproxy,without theneed to instantiateQNetworkProxy manually.
11.5.1NameResolutionwith QHostInfo
TheQHostInfo classisresponsible for simple name resolution.The static method
QHostInfo::fromName() provides information on thespecifiedaddressasanin-
stance of QHostAddress, butindoing so blocks theeventloop. If you wantto
avoidthis, you should make useofthe static method lo okupHost(), which expects
aslotasanargument that operates furtheronthe QHostAddressobject passed.
Thefollowingcode
QHostInfo::lookupHost("www.example.com",
this,SLOT(doSomething(const QHostInfo&)));
thereforelook supthe host www.example.cominthe DNSand deliversthe result
to aslotcalleddoSomething().
11.5.2Using QTcpServer and QTcpSocket
To become familiarwiththe waythenetworkclasseswork, weshall implementa

smallservicethatbinds itself to aportand returnsthe currenttimeinISO format
to everyinquirer.The client should acknowledge that it hasprocessedthe string,
withACK (nottobeconfu sedwiththe ACKpacket of TCP).
Theserver uses theeventloop of thesystem to do this:Each call, therefore, returns
immediately,and resultsare delivered via sign als, which wehavetomatch up to
slotsaccordingly.
As can be seen in thedeclaration,weonlyrequireone additionalslotfor this ex-
ample, sincethe remainingfunctionalitycan be inherited from QTcpServer:
333
11 Input/OutputInterfaces
// timeserver/timeserver.h
#ifndef TIMESERVER_H
#define TIMESERVER_H
#include <QTcpServer>
class TimeServer:public QTcpServer
{
Q_OBJECT
public:
TimeServer(QObject
*
parent=0);
protected slots:
void serveConnection();
} ;
#endif // TIMESERVER_H
In theconstructor weconnect thenewConnection() signal to this slot,calledserve-
Connection(). With nextPendingConnection() theslotretrieves theclient connec-
tion closesttothe socket. Each activeconnectionisrepresented byaQTcpSocket
object.
As asubclassofQIODevice, QTcpSocket()isable to send datatothe client or to

receivedatafromit. We delegatethe sockettoahelper classcalledConnection-
Handler, which looksafter everythingelse:
// timeserver/timeserver.cpp
#include <QtCore>
#include <QtNetwork>
#include "timeserver.h"
TimeServer::TimeServer(QObject
*
parent)
:QTcpServer(parent)
{
connect(this,SIGNAL(newConnection()),
SLOT(serveConnection()));
}
void TimeServer::serveConnection()
{
QTcpSocket
*
socket=nextPendingConnection();
if (!socket)
return;
newConnectionHandler(socket);
}
TheConnectionHandler first sendsoff thedateand then waits for newdata, which
it checks in theconfirm() slot.Weuse aQTimer, in casethe client doesn’t respond.
334
11.5 Communication in theNetwork
This classprovides atimekeeper which—in contrast to thetimerEvent()procedure
from Chapter 7—calls calls asignalwhenits timeoutexpires.
If it is intendedtoset thetimeout off once,asinthiscase, thestaticmethod

singleShot() is sufficient; it expectsthe time to thetimeout in milliseconds,aswell
as theobject andthe slotscallingit, as arguments. Finally,wesavethe slot in the
privatemembervariable socket. After atimoutweinformthe client that weare no
longer waiting andterminate theconnection:
// timeserver/connectionhandler.h
#ifndef CONNECTIONHANDLER_H
#define CONNECTIONHANDLER_H
#include <QtCore>
#include <QtNetwork>
#include <QDebug>
class ConnectionHandler:public QObject
{
Q_OBJECT
private:
QTcpSocket
*
socket;
public:
ConnectionHandler(QTcpSocket
*
socket,QObject
*
parent=0)
:QObject(parent)
{
QString dt=QDateTime::currentDateTime().toString(Qt::ISODate);
socket->write(dt.toUtf8());
connect(socket,SIGNAL(readyRead()),SLOT(confirm()));
QTimer::singleShot(10000,this,SLOT(timeout()));
this->socket=socket;

}
protected:
void closeConnection() {
socket->close();
deletesocket;
deleteLater();
}
protected slots:
void timeout() {
socket->write("ERROR:Timeout while waiting for
acknowledgement \ n");
closeConnection();
}
void confirm()
{
QByteArrayreply=socket->readAll();
if(reply== "ACK\ n")
closeConnection();
335
11 Input/OutputInterfaces
else
socket->write("ERROR:Unknowncommand\ n");
}
} ;
#endif // CONNECTIONHANDLER_H
In theconfirm() slot wecheck whether theclient hassentanACK followed byaline
break. If this is notthe case, wesendthe client an errormessage,and otherwise
weclose theconnectionwithout comment.Closing in bo th cases is takenover by
thecloseConnection() method. It simplycloses thesocket.
So that theConnectionHandler is also deleted,wecalldeleteLater(). QObject-based

objectsmustnever be deleted directlywithdelete. deleteLater() ensuresthatthe
application, as it entersthe eventloop again,deletes theobject.
We couldhavepassedthe QTcpServer object here as theparentobject andthen
waited for theclass to be deleted whenthe program ends.For an applicationthat
oversees several thousand connections,and thereforejustasmanyConnection-
Handlerobjects, memoryusage would be enormous.For this reason,wefirstdelete
theQTcpSocket, which is no longer needed.
To tryoutour newprogram,weinstantiate aTimeServer object,set it to listen to
port4711, andbindittoall networkinterfaces.Ifyou just wanttobindyourservice
to theloopbackinterface, you would just usethe address QHostAddress::LocalHost
insteadofQHostAddress::Any.Finallyweenter theeventloop, andour server is
ready:
// timeserver/main.cpp
#include <QtCore>
#include "timeserver.h"
intmain(intargc, char
*
argv[])
{
QCoreApplication app(argc, argv);
TimeServerts;
ts.listen(QHostAddress::Any,4711);
returnapp.exec();
}
336
12
Chapter
ThreadingwithQThread
If aprogram needstouse parallelprocessesorperformresource-intensivejobs
without blocking theGUI,there areonlytwoalternatives:forking or threading.

With forking,the operating system creates an exact copyof thecurrent process.
Thenewprocesscan executealong aseparatecode path, for example, to make
calculationswhile itsparentperforms some othertask. Some formofinterprocess
communication is also required between theoriginalprocess andits fork. Although
this procedureisquite normal on Unix-based operating systems, supportfor fork-
ingunder Windowsisnot so good, anditisverytime consuming to carryout
there.
Since Qt places an emphasis on platform-independentprogramming,threading is
seen here as themeans of choice.Hereseveral threads,alsocalled lightweight
processes,run simultaneouslywithinaprocess. It is arelativelysimple matter to
create newthreads andtoswitch between them ,since athreadmerelyhasastack
andacopyof theprocessorregisters. As can be seen in Figure 12.1, thethreads in
aprocess mustshare allother resources, such as theheap.
337
12 ThreadingwithQThread
12.1Using Threads
TheQThread classrepresentsthreads in Qt. Even if you do notexplicitlycreate any
lightweightprocesses, QCoreApplicationalwayscreat es amainthreadinternally.In
connectionwithQApplication, wealsotalkabout the GUI thread.Thisisentrusted
withaveryspecialtask: OnlytheGUI thread can create widgets or drawwith
QPainter.
Figure 12.1:
Threads in aprocess
have theirown
register setand a
separatestack,but
they shareeverything
else.
In thefirstexamplewewillmodifythetimeserver from Section11.5.2(page 333)
from asynchronouslyhandlingrequestsbyusingthe eventloop to usingQThread.

To do this wereimplement QTcpServer,but insteadofconnectingsignals to slots
in theconstructor,which this time remainsempty,weoverride theincomingCon-
nection() method. This accepts an argument,socketDescriptor, which is anumber
identifyingaTCP socket. QTcpServer makesacalltothe incomingConnection()
method for each incoming connection, in which socketDescriptorreferencesthe
socketfor theconnection:
// threadedtimeserver/timeserver.h
#ifndef TIMESERVER_H
#define TIMESERVER_H
#include <QTcpServer>
class TimeServer:public QTcpServer
{
Q_OBJECT
public:
TimeServer(QObject
*
parent=0)
:QTcpServer(parent) {}
protected:
void incomingConnection(intsocketDescriptor);
} ;
#endif // TIMESERVER_H
338

×