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

The book of qt 4 the art of building qt applications - phần 5 pptx

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 (662.19 KB, 45 trang )

6.5 Ready-made DialogsinQt
6.5.4Input Dialogs
Forsimplequeries,Qtprovides various template inputdialogs,consistingofasuit-
able inputwidgetand twobuttons,OKand Cancel.The relevantQInputDialog class
hasonlyready-made static methods that wewillnowlook at in more detail.
Frequently,you areput in thepositionofhavingtoask theusertoenter avalue.Qt
distinguishesherebetween whole number valuesand floating-point values, which
it provides in double precision.
AcceptingIntegerInput Values
Thefollowingexample(Figure 6.13) showshowthegetInteger() method of QIn-
putDialog is used:
bool ok;
intalter=QInputDialog::getInteger(this,tr("EnterAge"),
tr("Pleaseenteryearof birth"),1982,
1850,QDate::currentDate().year(),1,&ok);
if (ok)
{

}
Figure 6.13:
QInputDialog::getIn-
teger() with preset
defaultvalue
Thefirsttwoargumentsofthisare—asinother dialogs—a pointer to th eparent
widgetand theheading of thedialog. Then getInteger() expectsanexplanatory
text, which it displaysabovethe inputwidget. This is followed byadefault value
andthenthe limitsofthe allowed inputrange.Thisexamplerestricts th eupper
limit to thecurrent yeartoavoidinput that makesnosense (i.e., specifyingayear
of birth in thefuture).Todothisweuse QDate, aclass for processing datedetails.
ThecurrentDate()staticmethod provides thesystem time according to thecurrent
date, andinturn, year()extractsthe yearfromthisand returnsitasaninteger


value.Also, insteadofinserting astaticlower limit (1850), as is donehere, this can
be formeddynamically(e.g., with an expressi on such as QDate::currentDate().year()
-200).
179
6 Dialogs
In thenext-to-l astparameter,getInteger() asks forthe amount bywhich thein-
teger value should be increasedordecreased if theuserclicks on oneofthe two
buttons to theright of theinput field to incrementordecrement thevalue (a so-
called spin box).
Since thereturn value provides no information on whether theuserhas canceled
thedialogorhas made aproperdataentry,the method expectsapointer to a
Boolean variable as thefinalparameter.Ifthe user cancels thedialog, getInteger()
stores thevalue false in thevariable;otherwise, it is settotrue.
AcceptingFloating-pointNumbersasInput Values
In thesam ewayas withgetInteger(), you can also prompt theuserfor real numbers
withgetDouble(). Thedialoginthe nextexampleexpectsthe pricefor agiven
product.getDouble() also expectsthe pointer to theparentwidget, th edialog
heading,and thedescription of theexpected inputasits first threeparameters.
This is againfollowed bythedefault,minimum, andmaximum values.
However,for thenext-to-lastparameter,there is adifferencebetween getInteger()
andgetDouble(): Thefloating-point variant here expectsthe number of places after
thedecimal point(seeFigure6.14).Weuse twooftheminthisexample, in order
to specifyaprice.
Figure 6.14:
QInputDialog::getDouble()
worksherewithonly
twoplacesafter the
decimalpoint.
Once again, you can findout whether thedialogcompleted normallyor was in-
terrupted byusinganauxilliaryvariable,the address of which is passedasthe last

parameter:
double getPrice(const QString&product,bool
*
ok)
{
returnQInputDialog::getDouble(this,tr("Price"),
tr("Pleaseenteraprice forproduct’%1.’").arg(product),
0,0,2147483647,2,&ok);
}
Thevalue 2147483647 is themaximum number here that an integer can display.
180
6.5 Ready-made DialogsinQt
ReadinginStrings
Themostfrequentuse of QInputDialog is to allowtheusertoselectastring from
several predefinedstrings.For this purposethe static method getItem()isused
(Figure6.15):ThisexpectsaQStringListand displaysits contents in adrop-down
widget.
Figure 6.15:
QInputDialog::getItem()
returnsthe selected
string.
Again, thefirstthree parametersspecifythepointer to theparentwidget, thehead-
ing, andthe user query.Thisisfollowed bythelistofstrings to be displayed.Then
comesthe indexof thelistele ment that thedrop-downwidgetdisplaysatthe be-
ginning.The next-to-lastparameter specifies whether theusercan add hisown
entriestothe list. If this is this case, thereturn value doesnot havetomatch one
of theprede fined entries. Thefinalparameter,asbefore, is theaddressofavariable
that indicateswhether theuserhas terminated thedialogwithOKorCancel:
QStringList languages;
bool ok;

languages<< "English"<< "German"<< "French"<< "Spanish";
QString language =QInputDialog::getItem(this,tr("SelectLanguage"),
tr("Pleaseselectyour language"),languages,
0,false, &ok);
if (ok) {

}
ReadinginFreeText
Freelywritten texts arereadinwiththe QInputDialog method getText(). Thefol-
lowingexampleintroduce sprobablythemostfrequentusage of this type of user
input: en teringapassword.
Thefirstthree parametersspecifythedetails of theparentwidget, dialog head-
ing, anddialogtext, andare followed bythedisplayform, which is specified by
181
6 Dialogs
avalue from theEchoMode enumeratorofthe QLineEditinput wid get: QLine-
Edit::NormalMode displaysthe textasitisentered;QLineEdit::NoEcho prints noth-
ingatall, so that anybodywatchingcannotsee howmanycharacters getText()
accepts;and theQInputDialog::Passwordvalue used here causesaplaceholderto
be printed for each characterentered,usuallystarsorcircularicons (Figure6.16).
Figure 6.16:
QInputDialog::getText()
in passwordmode
Since adefault value is normallynotspecifiedfor inputofapassword, wepassan
emptyQStringobject as thenext-to-lastparameter.
QString getPassword(const QString&resource)
{
QString passwd=QInputDialog::getText(this,tr("PleaseEnterPassword"),
tr("Pleaseenterapasswordfor’%1’").arg(resource),
QLineEdit::Password, QString(),0);

}
Ourfinalparameter in this exampleisa0(i.e., anullpointer)insteadofapointer
to abool variable,because in thecaseofthe passworditissufficient to check
thereturn value withQString::isEmpty() in ordertosee whether anythinghas been
entered.Since theselasttwovaluesmatch thedefault valuesfor thefifthand sixth
argumentsofQInputDialog::getText(), you can shorten themethod callinthiscase
as follows:
QString getPassword(const QString&resource)
{
QString passwd=QInputDialog::getText(this,tr("PleaseEnterPassword"),
tr("Pleaseenterapasswordfor’%1’").arg(resource),
QLineEdit::Password);
}
6.5.5FontSelection Dialog
TheQFont classisresponsible for thedescription of afonttype in Qt. Each widget
hasafont()method that returnsthe currentfontasaQFontobject an dasetFont()
method that sets anewfonttype.QApplicationknowsthese methods as well. It
changesorreveals thestandardfonttype for newwidgets.
182
6.5 Ready-made DialogsinQt
If you need to havethe user select fonttypes, you can make useofQFontDialog
(Figure6.17).
Figure 6.17:
QFontDialog::getFont()
displays thedefault
font.
This classoffers agetFont()staticmethod, which apartfromapointer to theparent
widgetrequiresapointer to aBooleanvalue as itsfirstargument:true.
bool ok;
QFontfont=QFontDialog::getFont(&ok, this);

If theuserhas selected afont, this value is se ttotrue. If you wanttodefine
afonttype that deviates from thedefault font, you can defineanappropriate
QFontobject andhanditover to getFont(). Note that theQFont object needstobe
number twointhe getFont()argument list:
bool ok;
QFontinitial("TimesNewRoman",48);
QFontfont=QFontDialog::getFont(&ok, initial, this);
Here weselectTimes NewRoman. If this fontdoesnot existonthe system,Qttries
to make an approximationbyfinding asimilarfontthrough theuse of heuristics.
Thesecondparameter of theQFont constructorshownabovegives thefontsize, in
this example48points.
6.5.6Color Selectionand Printing Dialog
As wellasthe dialogs mentioneduntil now,Qtalsoprovides acolor selectionand
aprintingdialog. It makesmoresense to explainthe useofthese after wehave
183
6 Dialogs
introducedthe colorand paintingsystem of Qt in more detail, so thesedialogs will
be introducedinChapter 10on pages275 and302.
184
7
Chapter
Events, Drag and Drop,and
the Clipboard
From Section1.1,weknowthat allinteractiveQtprogramshave eventloops,be-
cause theyworkinanevent-driven manner: GUI-basedprogramsare influenced
byapplicationevents such as mousemovements.
7.1Event Loop andEvent Handler
Theeventloop performs twokinds of tasks. First, it managesevents that it obtains
from thewindowsystem used,suchasqueries to redrawawindowarea.Todothis
it transforms them into Qt-specificevents.Events areencapsulated in classesthat

arederived from theQEventbaseclass.
At the same time,Qtalsogen erates it sownevents.Anexampleofthisisthe
QTimerEvent, which is triggeredafter aspecific time hasexpired, setbythepro-
185
7 Events, Drag and Drop,and theClipboard
grammer. Such events arealsobased on QEventand areprocessedbytheeven t
loop.
Each QEventhas atype.SubclassesofQEventcan contain arbitraryamountsof
information;for example, QMouseEventhandles messagesabout buttons clicked
andthe positionofthe mousecursor.
Qt passesevents via QCoreApplication::postEvents() sp ecificallyto certainobjects.
TheseobjectsmustinheritfromQObject.The method expectsthe receivingobject
as thefirstparameter,followed byaQEvent.
To deliver it,postEvents() passesthe eventtothe event()method of thetarget
object.The task of theevent()method is to either processorignorethe incoming
events,depending on therequirementsofthe classofthe receivingobject.This
method is thereforealsoreferredtoasan eventhandler.Ifaneventcan notbe
processedimmediatelybythereceiver,the eventisput into aqueue andscheduled
for delivery.Ifanother part of theapplicationblocks theapplicationbyexecutinga
syncronous long-windedope ration,
1
thequeue cannotbeprocessedbytheevent
loop during that time.Usercan easilyconfusethisbehaviorwithanapplication
“crashing.”
Thestandardimplementationofevent()calls aseparatevirtual method for the
most important eventhandlers, which alreadyused thematchingQEventsubclass
as aparameter.Thisallowsustosavecode .Wewillnowtake acloserlook at how
this works.
7.2HandlingEvents
We willimplement awidgetthatdisplaysthe clocktimeinthe local displayformat

andthe currentdate, also in theappropriate format, alternating everyten seconds
(Figure7.1 on page 189).The displayitself should updateeverysecond.
7.2.1Using Specialized EventHandlers
We implementthe clockinaclasscalledClockWidget, which wederivefromQ-
LCDNumber, aQtclass that providesanimitation of an LCDdisplay:
// clockwidget/clockwidget.h
#ifndef CLOCKWIDGET_H
#define CLOCKWIDGET_H
#include <QLCDNumber>
1
Youcan use threads to avoidblocking.Wewill discussthisinChapter 12.
186
7.2 Handling Events
class QTimerEvent;
class ClockWidget:public QLCDNumber
{
Q_OBJECT
public:
ClockWidget(QWidget
*
parent=0);
protected:
void timerEvent(QTimerEvent
*
e);
private:
intupdateTimer,switchTimer;
bool showClock;
} ;
#endif // CLOCKWIDGET_H

Here weare particularlyinterested in,besides theconstruct or,the specialized event
handlertimerEvent(), which willupdatethe clocktime. In theupdateTimer and
switchTimermembervariableswesavenumbersthatserveasidentifiersfor the
timers. TheshowClockstatusflag determineswhether theclock time (showClock=
true)orthe date(showClock=false)appearsonthe wid get.
Theimplementationinclockwidget.cppbeginsbyspecifyingthe formofthe dis-
play.UsuallyQLCDNumber showsaframearound thedigital display.Thisbehavior,
inherited from QFrame,isdisabledbytheQFrame::NoFrame framestyle.Inaddi-
tion wedissuade thewidgetfromdrawingthe LCDelementswithshadowsand a
border,bypassing on QLCDNumber::Flattothe widget’ssetSegmentStyle() method.
// clockwidget/clockwidget.cpp
#include <QtGui>
#include "clockwidget.h"
ClockWidget::ClockWidget(QWidget
*
parent)
:QLCDNumber(parent),showClock(true)
{
setFrameShape(QFrame::NoFrame);
setSegmentStyle(QLCDNumber::Flat);
updateTimer=startTimer(1000);
switchTimer=startTimer(10000);
QTimerEvent
*
e=newQTimerEvent(updateTimer);
QCoreApplication::postEvent(this,e);
}
Nowweneed twotimers. Each QObject can startatimerusing thestartTimer()
method. As an argument startTimer() expectsthe number of secondsthatmust
187

7 Events, Drag and Drop,and theClipboard
passbeforeittriggers aQTimerEvent, which is addressedtothe currentwidget.
Each QTimerEventinturncontainsanidentification number,which is returned by
theinvocationofstartTimer()thatoriginates it .Wecan usethistodistinguish
between thetwotimersintimerEvent()later on.
So that wedonot havetowaitfor asecondtoelapsebeforethe time appears on
thewidget’sdisplay,wemanuallysend atimer eventwiththe ID of theupdate-
Timer, usingthe postEvent()method of QCoreApplication. As thetargetwespecify
thecurrent widget(in this case, this)aswedolater on for theevents generated by
thetimersthemselves.
In thetimerEvent()method wefirstcheck whether thepointer to theeventreally
is valid—just to be on thesafe side.Next, if theeventcontainsthe switch Timer
ID,thisonlytoggles theshowClockvariable.The act ualworkawaits in thelast
conditionalstatement, which is triggeredbyan eventcontainingthe updateTimer
ID.
// clockwidget/clockwidget.cpp (continued)
void ClockWidget::timerEvent(QTimerEvent
*
e)
{
if (!e)return;
if (e->timerId() == switchTimer)
showClock =!showClock;
if (e->timerId() == updateTimer) {
if (showClock) {
QTime time =QTime::currentTime();
QString str =time.toString(Qt::LocalDate);
setNumDigits(str.length());
display(str);
} else {

QDatedate=QDate::currentDate();
QString str =date.toString(Qt::LocalDate);
setNumDigits(str.length());
display(str);
}
}
}
If thewidgetissupposed to displaythetime, then wefirstdetermine thecurrent
time.InQt, theQTime classisresponsible for handlingtime. ThecurrentTime()
static method of this provides thecurrent system time in aQTime object.Thistime
is converted bytoString() into aQString.Qt::LocalDate instructsthe method to
take into account thecountrysettings(locales)ofthe user.Finallywemustinform
thedisplayhowmanyLCDdigit positions arerequired. We dedu ce this from th e
string length anddisplaythestringwithdisplay().
188
7.2 Handling Events
Figure 7.1:
Our ClockWidget
alternatelydisplays
thetime(above) and
thedate(below).
AlthoughQLCDNumbercannotdisplayallalphanumerical characters, it doescope
withall th echaractersrequiredfor thedateand clocktime(0–9, slash, colon, and
dot). setNumDigits(), bytheway,doesnot change thesizeofthe widget, thetext
just gets smaller, themorenumbersthere are.
On theother hand,ifshowClockisset to false,which meansthatthe widgetshould
displayjust thedate, weproceed in thesamewaywiththe QDate class, which in Qt
is responsible for managing datespecifications,and whose APIcorresponds almost
exactlyto that of QTime.
Nowwecan tryoutour widgets withthe followingtestprogram (Figure7.1 shows

theresultinthe formoftwoscreenshotsrecordedataninterval of ten seconds):
// clockwidget/main.cpp
#include <QtGui>
#include "clockwidget.h"
intmain(intargc, char
*
argv[])
{
QApplication app(argc, argv);
ClockWidgetw;
w.show();
returnapp.exec();
}
7.2.2Using theGen eral EventHandler
Insteadoftreatingthe timereventspecifically,wecould also usethe general
event()eventhandler.Since this receives alltypesofevents andweare onlyinter-
ested in timerevents,wemustfirstcheck theeventtype.Furthermore,inorder to
accessthe timerId()method of atimer event, acasttoQTimerEventisnecessary:
189
7 Events, Drag and Drop,and theClipboard
bool ClockWidget::event(QEvent
*
e)
{
if (!e)return;
if (e->type() == QEvent::Timer) {
QTimerEvent
*
te=static_cast<QTimerEvent
*

>(e);
if (te->timerId() == switchTimer) {
showClock =!showClock;
returntrue;
}
if (te->timerId() == updateTimer) {
// handle eventtimerasbefore

returntrue;
}
}
returnQObject::event(e);
}
Otherwise, weworkwiththe te variable in thesameman nerasinthe timerEvent()
method (see page 188).One peculiarityis that even t( ), in contrast to thespecialized
eventhandlers, returnsaBoolean value.Thisreveals whether an eventhas been
processedornot.
If weoverride thedefault event(), wemustnot forgettoforwardall events th at
wedo not handle to theevent()method of theparentclass.Oth erwise, th eevent()
method of theparentclass would never be calledand theeventhandlingofour
classwould be lastinglydisrupted.BycallingQObject::event()unconditionallyin
theend,weavoidabroken eventhan dling.
Thus,whenever thereisanappropriate specialized eventhandler,you should over-
ride it,ratherthanimplement ageneral eventhandler.There is no need for acast
because theinput parameter is alreadyof thecorrect even ttype,and no need to
forwardunhandledevents.Inthiswayit can also be seen from just aglanceatthe
classdeclaration which even thandlersare implemented bytheclass.
7.3Using Ev ent Filters
QObject-based classeshave, in additiontothe eventhandlerswithwhich theyreact
to theirownevents, eventfilters that allowan object Atoreceivethe events of

anotherobject B. Foreach BeventthatAreceives,Acan then either forwarditto
BorremoveitfromB’s eventstream.
Beforeyou can filter events,the eventfilter mustbeinstalled. To do this wecall
installEventFilter() in theconstructor of theobject Athatistomonitor theevents
of object B:
190
7.3 UsingEvent Filters
b->installEventFilter(this);
Here bisapointer to B. NowBgives up allits events to Aand leaves Awith
thedecisionwhether it should filter outthe eventorlet it throughtoB.For this
purposeaneventFilter() method is used,which hasthe followingsig nature:
bool QObject::eventFilter(QObject
*
watched, QEvent
*
e);
This mustbereimplemented byA. Thewatched parameter allowsevents from sev-
eral monitoredobjectstobedis tinguished from oneanother,and eisthe eventto
be processed.
Thereturn value tells theeventsystem of Qt howit should proceed withthe event.
If falseisreturned,itisforwardedtothe monitoredobject,whereas true causesit
to be filtered out. This meansthatthe eventdoesnot arriveatthe object for which
it was originallyintended.
Classeswitheventfilterscan change thebehaviorofother QObject-based objects
in this way.Thisisofparticularbenefitbecause you do notwanttoreimplement a
widgetjusttomake aminor modification to itseventprocessing.
Aclassicexampleofthe useofeventhandlersisinchatdialogs,inwhich QTextEdit
is used.Incontrasttothe standardimplementationofthe class, here the





Return
and




Enter keysshouldnot startanewline, butsendoff whathas been written.
2
Thedeclaration in chatwindow.h appears as follows:
// chatwindow/chatwindow.h
#ifndef CHATWINDOW_H
#define CHATWINDOW_H
#include <QWidget>
class QTextBrowser;
class QTextEdit;
class QEvent;
class ChatWindow:public QWidget
{
Q_OBJECT
public:
ChatWindow(QWidget
*
parent=0);
bool eventFilter(QObject
*
watched, QEvent
*
e);

void submitChatText();
private:
2
Although




Return and




Enterare generallyused synonymously,strictlyspeakingtheyaretwo
different keys, which is reflectedinthe code.
191
7 Events, Drag and Drop,and theClipboard
QTextBrowser
*
conversationView;
QTextEdit
*
chatEdit;
} ;
#endif // CHATWINDOW_H
ThesubmitChatText()method is responsible for sendingthe text. In this exampleits
onlytask consists of includingthe written textfromthe QTextEditinstancechatEdit
into theconversationView.Pointerstoeach of thesewidgets aresaved in member
variables.
In thechatwindow.cpp implementation,wefirstdefine theconstructor:Weplace a

vertical splitter into th ewidgetwithaQVBoxLayout. Theconversation viewcomes
into thesplitter at thetop, followed bytheactualinput widget, chatEdit:
// chatwindow/chatwindow.cpp
#include <QtGui>
#include "chatwindow.h"
ChatWindow::ChatWindow(QWidget
*
parent)
:QWidget(parent)
{
QVBoxLayout
*
lay=newQVBoxLayout(this);
QSplitter
*
splitter=newQSplitter(Qt::Vertical, this);
lay->addWidget(splitter);
conversationView=newQTextBrowser;
chatEdit=newQTextEdit;
splitter->addWidget(conversationView);
splitter->addWidget(chatEdit);
chatEdit->installEventFilter(this);
setWindowTitle(tr("ChatWindow"));
setTabOrder(chatEdit,conversationView);
} ;
Then weinstall th eeventfilter in theinput widgetusing installEven tFilter(), as just
described. Thetargetisthe ChatWindowobject itself (this).
TheChatWindowwill filter thekeypressevents of thechatEditobject andrespond
to them, so that wedonot need to implementaspecialized subclass of QTextEdit
for this application.

Finally,weset thewindowtitleand usesetTabOrder()tospecifytheorder in which
thewidgets willbegiven focusinsidethe ChatWindowif th euserpressesthe




Tab key.The callinthiscasehas theeffect that chatEditobtains thefocus before
conversationView,sothatthe user can begintyping immediatelyafter th eprogram
starts.Atthe same time chatEdit obtainsthe focusassoon as theshow() method
of aChatWindowinstance is called.
Untilnowwehaveonlylearnedhowto specifythetab orderwiththe help of the
Qt Designer, in Chapter 3.1.5onpage 89.Ifyou read theC++ code generated by
192
7.3 UsingEvent Filters
uic, you willrealizethatthe Designer also converts th etab orderspecifiedintoa
series of setTabOrder()calls.
We shallnowturn to thecoreitem of theexample, theeventFilter() method:
// chatwindow/chatwindow.cpp (continued)
bool ChatWindow::eventFilter(QObject
*
watched, QEvent
*
e)
{
if (watched == chatEdit&& e->type() == QEvent::KeyPress) {
QKeyEvent
*
ke =static_cast<QKeyEvent
*
>(e);

if (ke->key() == Qt::Key_Enter||
ke->key() == Qt::Key_Return) {
submitChatText();
returntrue;
}
}
returnQWidget::eventFilter(watched, e);
}
We first useapointer comparison to checkwhether thefilter is currentlyhandling
chatEdit at alland whether thepressing of akey(QEvent::KeyPress) is involved.
Once weare sure of this,wecastthe genericeven teintoits actualeventtype,
QKeyEvent, withastatic_cast.
This is necessaryto accessthe keypressevent’skey() method, which wenowuse
to checkwhether thekeypressediseitherthe




Enter or




Return key.Ifthisisthe
case, wecallsubmitChatText()and requestQttofilter theeventwithreturn true,
that is,not to forwardittothe chatWindowobject.Ifthe eventisnot akeypress
event, weforwardittothe parentclass’seventfilter.Wetake this precaution since
several Qt classesrelyon eventfilters.
ThesubmitChatText()method, which would also be responsible for forwarding text
in arealchatclient,inour exampleonlyattaches thetypedtexttothe conversation

viewandempties thetextwindow:
// chatwindow/chatwindow.cpp (continued)
void ChatWindow::submitChatText()
{
// append text asnewparagraph
conversationView->append(chatEdit->toPlainText());
// clearchatwindow
chatEdit->setPlainText("");
}
We also checkthisclass againfor itsfunctionalitywithashorttestprogram,by
starting an eventloop via QApplication::exec(), after wehaveinstantiated anddis-
played ChatWindow:
193
7 Events, Drag and Drop,and theClipboard
// chatwindow/main.cpp
#include <QtGui>
#include "chatwindow.h"
intmain(intargc, char
*
argv[])
{
QApplication app(argc, argv);
ChatWindowwin;
win.show();
returnapp.exec();
}
7.4Dragand Drop
The drag anddrop functionality,thatis, thecapabilityto transfer information
withthe mousebetween twowidgets withinthe same program,orbetween two
applications,isalsoregulated in Qt via events (Figure7.2 on page 196).Each event

hasits owneventhandler in QWidget-basedclasses.
7.4.1MIMETypes
Thefirstquestiontoarise here is howtheinformation should be encodedsothat
it can be transferredatall between twowidgets via drag anddrop. This is solved
bytheQMimeData class: It serves as acontainer for data, whose type is specified
as aMIMEtype.
3
APNG image,for example, hasthe MIME type image/png, anda
normal ASCII textfile hasthe type text/plain.
It is also possible to useyourownMIMEtypesthatare understood onlybyyourown
application. Thenames of theseare defined acc ording to thepatternapplication/x-
vendor. contentdesignator (page 242showsanexample).
In thefollowingexamplewepack an image so that it can be “sent away”witha
drag. To do this wewrite aQLabel-basedwidgetthatexpectsthe pathtoaPNG
image,displaysit, andallowsittobeincludedinoth er applications via drag and
drop.
Thefollowinghelpfunction,calledprepareImageDrag(), packs theimage into a
QMimeDataobject:
QMimeData
*
prepareImageDrag(const QString&path)
{
QFile file(path);
3
MIME standsfor MultipurposeInternet Mail Extensions andisdescribedinRFCs2045, 2046,
and2047.
194
7.4 Drag and Drop
if (!file.open()) return;
QByteArrayimage =file.readAll();

QMimeData
*
mimeData=newQMimeData;
mimeData->setData("image/png",image);
returnmimeData;
}
FortunatelyQMimeDataalreadyincludes itsownencoding methods for themost
important datatypes, such as for colors, HTML, reformatted text, andURLs. In
practice,the followingcode is thereforesufficient to encode an image:
QMimeData
*
prepareImageDrag(const QString&path)
{
QImage image(path);
QMimeData
*
mimeData=newQMimeData;
mimeData->setImageData(image);
returnmimeData;
}
Qt even makesthe image available in different formats withsetImageData().QMime-
Data can saveseveral MIME typestogether withtheir datainone object.When
dragging,Qtoffers allsupported image formats, butithas apreferencefor PNG
here,since this displaysthe best quality.The program that receives thedropthen
iterates throughthe listofMIMEtypesand selectsthe datafor thefirstMIMEtype
that it can handle.
We make useofthispropertyto includethe pathspecificationfor theimage:We
packitintoaQUrl object,which converts it into an RFC-compliant URL, andwe
also includethe normalized pathspecificationasatext:
// draglabel/draglabel.cpp

#include <QtGui>
QMimeData
*
prepareImageDrag(const QString&path)
{
QImage pic(path);
QMimeData
*
mimeData=newQMimeData;
mimeData->setImageData(pic );
QList<QUrl> urls;
QUrlimageUrl(path);
urls.append(imageUrl);
mimeData->setUrls( urls);
mimeData->setText( imageUrl.path() );
returnmimeData;
}
We intentionallydo notuse thepathvariable here directly:Ifweare passedarel-
ativepath, this couldbecomeaproblemwithdragand drop between applications
withdifferent working directories. QUrl,however,resolves relativepaths.
195
7 Events, Drag and Drop,and theClipboard
An applicationthatobtains adroporiginating from adragwiththese MIME data
first comesacrossthe im age data. If it cannothandleimages, it then checks
whether it can handle URLs,which would be thecasefor afile manager, for ex-
ample. If theseattempts areunsuccessful,the program can still accessthe pathin
textform, so that even an editor mayact as adroptarget. We willuse this flexible
variation in ourexample.
Figure 7.2:
Aspecific event

handlerisresponsible
forevery
drag-and-drop step
in Qt.
Source
Destination
Application A
Application B
Source::mousePressEvent()
Source::mouseMoveEvent()
Destination::dropEvent()
Destination::dragEnterEvent()
Destination::dragLeaveEvent()
Destination::dragMoveEvent()
7.4.2The Drag Side
We haveseen howto encode datainMIMEformat. Buthowdo th eMIMEdata
from awidgetinone part of ourprogram manage to gettoanother part—oreven
into acompletelydifferent application? To illustrate this,Figure7.2 showsthe
sequence of atypical drag-and-drop operation.
Thesourcewidgetdefineswhenadrag begins.Ifthe widgetcan notbeclicked,
which is thecasefor labels,itissufficient to reimplementthe mousePressEvent( )
eventhandler in awaythat adragistriggeredbyclicking:
// draglabel/draglabel.cpp (continued)
void DragLabel::mousePressEvent(QMouseEvent
*
event)
{
if (event->button() == Qt::LeftButton) {
QMimeData
*

data=prepareImageDrag(picPath);
QDrag
*
drag=newQDrag(this);
drag->setMimeData(data);
if (pixmap())
drag->setPixmap(pixmap()->
scaled(100,100,Qt::KeepAspectRatio));
drag->start();
}
}
Firstwecheck whether theuserisholding downthe left mousebutton. Then we
preparethe QMimeDataobject withthe help function prepareImageDrag()(page
195).Weobtainthe pathfromthe member variable picPath.The constructor
196
7.4 Drag and Drop
retrieves th eimage displayed bythelabel from thespecifiedpath, withthe help of
theQLabel::setPixmap()method, as showninthe followingcode:
// draglabel/draglabel.cpp (continued)
#include "draglabel.h"
DragLabel::DragLabel(const QString&path, QWidget
*
parent)
:QLabel(parent),picPath(path)
{
setPixmap(QPixmap(path));
}
In ordertostart theactualdrag, weneed to instantiateanewQDrag object in the
mousePressEvent()and equipitwiththe MIME data usingsetMimeData().
In addition weassign theimage from thelabel to thedrag, for which weobtaina

pointer withpixmap(). Th egraphical interfacelin ks it to themouse cursor so that
thecontentofthe drag object is visualized.Thereforedrags do nothavetohave
an image set, although this is recommended from ausabilitypoint of view,since
theusercan then seewhatheisjugglingwith. We mustensurethatthe image
is presented in thepreviewsize. To do this wespecifyaversion scaled down, with
scaled(). KeepAspectRatioinstructs themethod to retain thepage proportions, but
nottoexceed themaximum sizeof100 pixelsineitherdirection.
drag->start() begins theactualdragaction. In thepat ternfromFigure7.2,the
source corresponds to ourDragLabel.
To testthe widget, wewillwrite asmall program that requires thepathofanimage
filethatcan be read byQt as acommand-lineargument.Ifthisisavailable,wepass
it to DragLabel during theinstantiation:
// draglabel/main.cpp
#include <QtGui>
#include "draglabel.h"
intmain(intargc, char
*
argv[] )
{
QApplication app( argc, argv);
if (argc <2) return1;
DragLabel w(argv[1]);
w.setWindowTitle(QObject::tr("Dragme!"));
w.show();
returnapp.exec();
}
197
7 Events, Drag and Drop,and theClipboard
Theprogram then lookssomethinglikewhatisshowninFigure7.3.Wecan drag
theimage into various programs, such as Gimp or Paint, andsee whathappensto

it.
Figure 7.3:
The DragLabel with
theQt-4logo
7.4.3The Drop Side
So that wecan better understandthe drag-and-drop processillustrated in Figure
7.2onpage 196, wewillnowimplementalabelwidgetcomplementaryto the
DragLabel,which wewillcallDropLabel.
Each wid getthatshouldaccept drops mustfirstactivatethiscapabilityin itscon-
structor,withsetAcceptDrops(true):
// droplabel/droplabel.cpp
#include <QtGui>
#include "droplabel.h"
DropLabel::DropLabel(QWidget
*
parent)
:QLabel(parent)
{
setAcceptDrops(true);
}
Events to be Handled
Thefirstdropeventthatthe wid getneedstoprocess occurs as so on as themouse
cursor moves into thewidget. Accordingly,the widget’sdragEnterEvent()handler
mustcheck to seeifthe MIME typescontained in thedragobject areonesitcan
handle.For this purposeweaccess thethe QMimeDataobject,via themimeData( )
method:
198
7.4 Drag and Drop
// droplabel/droplabel.cpp (continued)
void DropLabel::dragEnterEvent(QDragEnterEvent

*
event)
{
if (event&& event->mimeData()) {
const QMimeData
*
md =event->mimeData();
if (md->hasImage() || md->hasUrls() || md->hasText())
event->acceptProposedAction();
}
}
We checkthe contents of theQMimeData object andaccept thedropaction, via
acceptProposedAction(), as soon as wefind that th ereisanimage,someURLs, or
atext. Otherwisethe mousecursorwilldisplayan X ,signalingtothe user that the
widgetwillnot accept thedrop. If you want, you can carryoutmoreprecise checks
here,but you should be awarethattoo muchchecking at this point mayprevent
thewidgetfromsignalingpromptlythat it can acceptthe drag.
If you wanttoperformadditional checks withinthe widget, such as allowingdrops
onlyin specificareas,you can implementadragMoveEvent()handler.The function
takesapointer to aQDragMoveEvent, withwhich thecurrent positioninthe widget
can be checked, usingpos(). This method mustalsocallacceptProposedAction(),
passing it theevent, if thewidgetshouldaccept adropataparticular point. Most
widgets andapplications usuallydo notneed to handle this event, however.
Forthe sake of completeness, weshouldalsogiveamentiontodragLeaveEvent().
This eventhandler is also normallynotneeded,but can be used in specialcases to
undochanges made to thecurrent widgetbydragEnterEvent()ordragMoveEvent().
The dropEvent() Handler
Thecorepartofadropoperation is thedropEvent()handler;itisusedtodecode
themimeData()object andcomplete th edrag-and-drop process:
// droplabel/droplabel.cpp (continued)

void DropLabel::dropEvent(QDropEvent
*
event)
{
QPixmappix;
if(event&& event->mimeData()) {
const QMimeData
*
data=event->mimeData();
if (data->hasImage())
pix=data->imageData().value<QPixmap>();
elseif(data->hasUrls())
foreach(QUrlurl, data->urls()) {
QFileInfo info(url.toLocalFile());
199
7 Events, Drag and Drop,and theClipboard
if(info.exists() && info.isFile())
pix=QPixmap(url.toLocalFile());
if (pixmap() && !pixmap()->isNull())
break;
}
elseif(data->hasText()) {
QUrlurl(data->text());
QFileInfo info(url.toLocalFile());
if(info.exists() && info.isFile())
pix=QPixmap(url.toLocalFile());
}
}
if (!pix.isNull()) {
setPixmap(pix);

resize(pix.size());
}
}
Because theQMimeData object is const(that is,write protected), weare notre-
sponsible for freeing itsmemory.
If th eimage exists as adatastreaminthe QMimeDatainstance(determinedusing
hasImage()), weconvertthistoapixmap. Since imageData() returnsaQVariant and
QPixmapisacomponentofthe QtGui module,about which theQVariant“living”
in QtCorehas no knowledge,wewillmake useofthe QVariant template method
value< type>(), to which wepassQPixmapasatype parameter.
If theMIMEdatacontain URLs instead, wefirstconverteach of them to thecor-
responding local filepathwithtoLocalFile(). If thepathisnot local,the method
returnsanemptystring.
Using QFileInfo, wethencheck thepathtosee if it exists andalsotosee whether
it reallyreferences afile.Ifthisisthe case, wetryto read thefile as an image file.
If this doesn’t work, pixbecomesanullobject,which willrespondtoisNull()with
true.Assoon as wehavefound avalid URL, weskipthe otherURLs, withbreak.
It maysometimesbethe casethatQMimeData contains several URLs for thesame
object.For example, theKDE desktopenvironment references files on externaldata
mediafirstwithamedia:/ URL, butals oprovides thematchingtraditional Unix
pathfor non-KDE programs.
Finally,ifall else fails,because afile locator can also be represented as unformatted
text, wetryto interpret anyexisting textpartofthe MIME data as an URL, so that
wecan tryto obtain apixmapfromthis.
If oneofour extractionattempts is successful,the pixfilledwithdatabecomes the
newlabel’spixmap, andwecan adjustthe labeltothe pixmapsize.
We willalsoput this exampletothe testwithasmalltestprogram.Insteadof
aDragLabel,weinstantiate aDropLabel here andblowit up to an initialsizeof
100x100 pixels, so that thereisenoughspacefor objectstobedropped:
200

7.5 TheClipboard
// droplabel/main.cpp
#include <QtGui>
#include "droplabel.h"
intmain(intargc, char
*
argv[] )
{
QApplication app( argc, argv);
DropLabel w;
w.setWindowTitle(QObject::tr("Drophere!"));
w.resize(100,100);
w.show();
returnapp.exec();
}
If wepullthe image from theDragLabel exampleonpage 197ontothe DropLabel
window,the widgetacceptsthe drop. If you letgoofthe buttonthe DropWidget
accepts thegraph ics, as can be seen in Figure 7.4. Theprogram can also process
drops made from filemanagers, thanks to itsabilityto interpret URLs.
Figure 7.4:
The DropLabel,after it
hasreceived th edrop
with theQt-4logo
7.5The Clipboard
TheQtQClipboardclass is responsible for handlingthe “clipboard” providedby
manyoperating systems. Interactionwiththe system clipboardrequiresnoevent
handling, butlikethe Qt drag-and-drop functionality,itutilizes MIME data encod-
ing.
Youdon’t even need to defineaQClipboardobject,because everyQApplication
alreadyprovides onethatyou can usetoreadtextfromorwrite textto, as shown

below.
QClipboard
*
clipboard=QApplication::clipboard();
201
7 Events, Drag and Drop,and theClipboard
qDebug() << clipboard->text();
clipboard->setText(newText);
Butthe clipboardcan also storeand retrievemorecomplexdata, anditisable to
do so ba sedonaMIME type.The methods mimeData() an dsetMimeData()transfer
theMIMEdatainexisting QMimeDataobjectstoand from theclipboard.
To demonstratehowcloselytheclipboardand thedrag-and-drop system arere-
lated,weshall write asmall testapplicationcalleddrag2clip.The core of this is
alabel widgetnamed D2cLabel,which copies datareceived from adroptothe
clipboard. Conversely,the clipboarddatacan be retrieved bydragging from the
D2cLabel object.
Apartfromthe constructorand thethree events handlers necessaryfor drag and
drop, mousePressEvent(), dragEnterEvent()and dropEvent(), thereisalsothe clone-
MimeData() method. This creates an identical copyof awrite-protected QMime-
Data object,asisobtainedbyQClipboardorQDropEvent:
// drag2clip/d2clabel.h
#ifndef D2CWIDGET_H
#define D2CWIDGET_H
#include <QLabel>
class QMimeData;
class D2cLabel :public QLabel
{
Q_OBJECT
public:
D2cLabel(QWidget

*
parent=0);
void mousePressEvent(QMouseEvent
*
event);
void dragEnterEvent(QDragEnterEvent
*
event);
void dropEvent(QDropEvent
*
event);
protected:
QMimeData
*
cloneMimeData(const QMimeData
*
data);
} ;
#endif // D2CWIDGET_H
In theconstructor weadd an inscriptiontothe newlabeland enable drops into it.
Thanks to setWordWrap(true), thelabel line-wraps th etextthe moment it is longer
than thewidgetiswide. Byenclosingthe textinside<center>tags,wecause it to
appearcentered:
// drag2clip/d2clabel.cpp
202
7.5 TheClipboard
#include <QtGui>
#include "d2clabel.h"
D2cLabel::D2cLabel(QWidget
*

parent)
:QLabel(parent)
{
setWordWrap(true);
setText(tr("<center>Dragfrom heretoretrievethe text currently"
"located in the clipboardorfill the clipboardby"
"dragging text from abitrary placesand dropping ithere."
"</center"));
setAcceptDrops(true);
}
In themousePressEvent()weretrievethe MIME data from theclipboardand check
thepointer to it for itsvalidity.Ifeverythingisinorder,wegenerateaQDrag object
andtransferthe MIME data there:
// drag2clip/d2clabel.cpp (continued)
void D2cLabel::mousePressEvent(QMouseEvent
*
event)
{
if (event->button() == Qt::LeftButton) {
const QMimeData
*
mimeData=QApplication::clipboard()->mimeData();
if (!mimeData)return;
QDrag
*
drag=newQDrag(this);
drag->setMimeData(cloneMimeData(
QApplication::clipboard()->mimeData()));
drag->start();
}

}
At thispoint werequire cloneMimeData(), as wehavenoinformation on theMIME
typesfromthe clipboardand theirlifespans, which also hasabearingonthe validity
of themimeData()pointer from theQApplication::clipboard(). We can passthe
cloned QMimeDatainstance, on theother hand,withafree conscience to the
QDrag object,and initiate thedragprocess withstart().
Next, weimplement dragEnterEvent(). This accepts everything, andafter all, we
wanttoleaveall thedatainthe clipboard:
// drag2clip/d2clabel.cpp (continued)
void D2cLabel::dragEnterEvent(QDragEnterEvent
*
event)
{
event->acceptProposedAction();
}
203

×