<<

The Book of 4

Daniel Molkentin

TheBook of Qt 4

TheArt of BuildingQtApplications

®

Munich SanFrancisco The Book of Qt 4: The ArtofBuildingQtApplications. Copyright " 2007OpenSource PressGmbH

Allrightsreserved.Nopartofthisworkmaybe reproducedortransmittedinanyform or byanymeans, electronic or mechanical, includingphotocopying, recording, or byanyinformationstorageorretrieval system,without theprior writtenpermission of thecopyright ownerand thepublisher.

Printedonrecycled paperinthe United States of America.

12345678910—10090807

No Starch Pressand theNoStarch Presslogo areregisteredtrademarksofNoStarch Press, Inc. Otherproduct and companynamesmentioned herein maybe thetrademarksoftheir respectiveowners.Ratherthanuse atrademark symbol witheveryoccurrence of atrademarked name,weare usingthe namesonlyin an editorialfashionand to the benefit of thetrademark owner, withnointention of infringement of thetrademark.

Publisher: William Pollock Cover Design:Octopod Studios U.S. editionpublishedbyNo Starch Press, Inc. 555 De HaroStreet,Suite 250,San Francisco, CA 94107 phone: 415.863.9900;fax:415.863.9950;[email protected];http://www.nostarch.com

Original edition " c 2006OpenSource PressGmbH PublishedbyOpenSource PressGmbH, Munich, Germany Publisher: Dr.Markus Wirtz Original ISBN 978-3-937514-12-3 For informationontranslations, pleasecontact OpenSource PressGmbH, Amalienstr.45Rg, 80799 M unche¨ n, Germany phone+49.89.28755562; fax+49.89.28755563; [email protected];http://www.opensourcepress.de

Theinformationinthisbook is distributed on an “AsIs” basis, without warranty.While everyprecaution hasbeen takeninthe preparationofthiswork, neitherthe author norOpenSource PressGmbHnor No Starch Press, Inc. shall haveanyliabilityto anyperson or entitywithrespect to anyloss or damage caused or allegedtobecaused directly or indirectlybytheinformationcontained in it. LibraryofCongressCataloging-in-Publication Data

Molkentin, Daniel [Qt4, Einf¨uhrung in die Applikationsentwicklung.English] The book of Qt4: the art of building Qtapplications/byDaniel Molkentin.-- 1st ed. p. cm. Includesindex. ISBN-13978-1-59327-147-3 ISBN-101-59327-147-6 1.Qt(Electronic resource)2.Graphicaluserinterfaces(Computersystems) 3. Application software--Development. I.Title.QA76.9.U83M62132007 005.4’37--dc22 2007013181 Contents

Introduction 19

Preparations23

1Basics, Tools, andFirst Code 25 1.1Our FirstQtProgram ...... 25 1.1.1CompilingaQtProgram ...... 27 1.2Layouts,Object Hierarchy,and MemoryManagement ...... 29 1.2.1Howto ArrangeWidgets Automatically...... 29 1.2.2MemoryManagement in Object Hierarchies...... 31 1.2.3Other LayoutTypes...... 33 1.3Signals andSlots ...... 35 1.3.1The SimplestCase: ASlotResponds to aSignal...... 35 1.3.2Signals CarryingAdditional Information andHowThey AreProcessed...... 36 1.4BaseClassesinQt...... 39 1.4.1ClassesDerived from QObject ...... 39 1.4.2QString andOther Classesnot Derived from QObject ... 40 1.4.3The Qt InheritanceHierarchy...... 41 1.5QtataGlance ...... 42 1.5.1The Qt Libraries...... 42 1.5.2Toolsand Utilities...... 47 1.5.3Examples andDemos ...... 58 1.6Howto Use theDocumentation...... 59

5 Contents

2The ToolsNeeded to Create Dialogs61 2.1What’sthe DifferenceBetween Dialogsand Widgets?...... 62 2.1.1Inheriting from QObject ...... 64 2.1.2MoreComplexLayouts ...... 65 2.1.3IncreasingUsability...... 68 2.1.4ImplementingSlots ...... 70 2.2SeparationofGUI andProcessing Logic...... 74 2.2.1AlternativeDesign...... 74 2.2.2Declaring andSending OutSignals ...... 76 2.2.3Using Your OwnSignals ...... 79

3GUI Design Using theQtDesigner81 3.1Dialogs “ByMouseClick” ...... 81 3.1.1Making Layouts With theDesigner...... 84 3.1.2The PropertyEditor...... 85 3.1.3The Preview...... 88 3.1.4Signal/SlotConnections ...... 88 3.1.5The TabSequence...... 89 3.1.6Shortcuts andBuddies ...... 90 3.2Integrating Designer-generated FilesintoYourQtProject ..... 91 3.2.1Using Designer-generated ClassesasHelperClasses.... 92 3.2.2AlwaysHavingDesigner-generated Widgets Available ... 94 3.2.3MultipleInheritance...... 95 3.3Automatic Signal/SlotConnections ...... 97 3.4Including Derived Classesinthe Designer ...... 99 3.5The Resource Editor ...... 99

4Developing aGUI Application BasedonaMain Window 101 4.1The Anatomyof theMainWindow...... 101 4.2DerivingfromQMainWindow...... 103 4.3CreatingaMain Windowwiththe Qt Designer...... 106 4.3.1Adding Menu Bars ...... 107 4.3.2RecyclingActions in theToolbar...... 108

6 Contents

4.3.3Integrating theMainWindowwithYourSourceCode ... 110 4.4Making theMostofthe Status Bar...... 118 4.4.1TemporaryMessages...... 120 4.4.2NormalMessages...... 120 4.4.3Permanent Messages...... 121 4.5Toolbars ...... 125 4.6HowDo ActionsWork? ...... 126 4.6.1Howto InstantiateQAction Manually...... 127 4.6.2Selectable Actions...... 128 4.6.3Grouped Actions...... 128 4.7DockWindows...... 130 4.7.1Positioning Dock Windows...... 131 4.7.2ADock Windowfor OurEditor...... 133 4.8SavingPreferences ...... 136 4.8.1Extending CuteEdit...... 139

5Laying OutWidgets 141 5.1ManualLayout...... 141 5.2Automatic Layout...... 143 5.2.1Horizontaland Vertical Layout...... 144 5.2.2GridLayout...... 148 5.2.3Nested Layouts ...... 149 5.3Splitter ...... 150 5.3.1BehaviorDuringSizeChanges ...... 150 5.3.2SavingSplitter Positionsand Determining theWidgetSize151 5.3.3Defining RelativeSizes ...... 152 5.3.4CustomizingHandles ...... 153 5.3.5Layoutfor LanguagesWritten from RighttoLeft..... 156 5.4StackedLayouts ...... 157 5.4.1The Alternative: Stacked Widgets ...... 157 5.4.2WhentoUse Stacked Layouts andWidgets ...... 157

7 Contents

6Dialogs161 6.1Modal Dialogs...... 161 6.2Non-modal Dialogs...... 163 6.2.1UsabilityProblems ...... 163 6.3Semi-modal Dialogs...... 164 6.4Avoiding Bloated Dialogs...... 164 6.5Ready-made DialogsinQt...... 166 6.5.1Message Dialogs...... 166 6.5.2Error MessagesThatAre OnlyVisible Once ...... 174 6.5.3File SelectionDialogs ...... 175 6.5.4Input Dialogs...... 179 6.5.5FontSelection Dialog ...... 182 6.5.6Color Selectionand Printing Dialog ...... 183

7Events, Drag andDrop, andthe Clipboard185 7.1EventLoop andEventHandler ...... 185 7.2HandlingEvents ...... 186 7.2.1Using Specialized EventHandlers...... 186 7.2.2Using theGeneral EventHandler ...... 189 7.3Using EventFilters...... 190 7.4Dragand Drop ...... 194 7.4.1MIMETypes...... 194 7.4.2The Drag Side...... 196 7.4.3The Drop Side...... 198 7.5The Clipboard...... 201

8Displaying Data Using “Interview”207 8.1UnderlyingConcepts...... 208 8.1.1The ViewClasses...... 210 8.1.2The Model Classes...... 211 8.2DisplayingDirectoryHierarchies...... 212 8.2.1Using ViewClassesinthe Designer ...... 214 8.2.2Implementingthe Functionalityof theFile SelectionDialog216

8 Contents

8.3The String ListsModel...... 221 8.4ImplementingYourOwnModels ...... 222 8.4.1AnAddressBook Model...... 222 8.4.2Making Your OwnModels Writable ...... 227 8.5Sorting andFilteringDatawithProxy Models...... 231 8.5.1Adjustments to theUserInterface...... 232 8.6Making EntriesSelectable withCheckboxes ...... 234 8.7Designing Your OwnProxy Models...... 237 8.8ImplementingDragand Drop in Models...... 241 8.9YourOwnDelegates ...... 245 8.10WithoutYourOwnDataSource: TheStandardModel...... 249 8.11 Element-basedViewsWithout Model Access ...... 251 8.11.1Items ...... 251 8.11.2The List View...... 251 8.11.3The Tree View...... 252 8.11.4The Table View...... 253 8.11.5Cloning Items...... 254

9The QtSqlModule 257 9.1Structure of theQtSql Module ...... 257 9.2Selecting theAppropriate Driver ...... 258 9.3Making aConnection...... 260 9.4Making Queries...... 261 9.5Transactions ...... 264 9.6EmbeddedDatabases ...... 264 9.7Using SQLModelClasseswithInterview...... 265 9.7.1DisplayingSQL TablesWithout ForeignKeysinTable and Tree Views...... 265 9.7.2ResolvingForeign KeyRelations...... 266 9.7.3DisplayingQueryResults...... 267 9.7.4Editing Strategies...... 268 9.7.5Errorsinthe Table Model ...... 270

9 Contents

10 TheGraphicsLibrary “Arthur”271 10.1 Colors...... 271 10.1.1 TheRGB Color Space ...... 272 10.1.2 Other Color Spaces...... 273 10.1.3 Color SelectionDialog...... 275 10.2 Painting withQt...... 276 10.3 Geometrical Helper Classes...... 278 10.4 Howto PaintonWidgets ...... 280 10.4.1 Howto PreventMonitor Flicker...... 282 10.5 Using QPainter in Practice ...... 283 10.5.1 DrawingaPieChart ...... 284 10.5.2 Definingthe WidgetSize...... 289 10.5.3 TheDiagram Application...... 290 10.6 Transformationsofthe CoordinateSystem ...... 290 10.6.1 TransformationsinPractice...... 293 10.7 QImage ...... 297 10.7.1 Storage Formats, Transparency,and Color Palettes ..... 297 10.7.2 Reading outPixelsLinebyLine ...... 298 10.8 SVGSupport...... 300 10.9 Printing withQPrinter ...... 302 10.9.1 Digression:Making Screenshots...... 304 10.9.2 Printing an Image File ...... 305 10.9.3 Generating PDFs ...... 306 10.9.4 TheTestApplication...... 306 10.10ComplexGraphics...... 307 10.10.1 Clipping ...... 307 10.10.2 Painter Paths...... 309 10.10.3 Composition Modes...... 310

11 Input/OutputInterfaces 317 11.1The QIODeviceClass Hierarchy...... 317 11.1.1Derived Classes...... 318 11.1.2Opening I/O Devices ...... 319

10 Contents

11.2Access to Local Files...... 320 11.3SerializingObjects...... 322 11.3.1Defining SerializationOperators ...... 325 11.3.2SavingSerialized Data to aFile andReading from It .... 326 11.4Startingand ControllingProcesses...... 328 11.4.1SynchronousUse of QProcess ...... 328 11.4.2AsynchronousUse of QProcess ...... 330 11.5Communication in theNetwork...... 332 11.5.1NameResolutionwithQHostInfo ...... 333 11.5.2Using QTcpServer andQTcpSocket...... 333

12 Threading with QThread337 12.1Using Threads ...... 338 12.2SynchronizingThreads ...... 341 12.2.1The Consumer/ProducerPattern...... 342 12.3Thread-dependentDataStructures...... 345 12.4Using Signals andSlots Between Threads ...... 347 12.5YourOwnEventLoops for Threads ...... 350 12.5.1Communication via Events WithoutaThread-basedEvent Loop ...... 352

13 Handling XMLwithQtXml 353 13.1The SAX2 API...... 354 13.1.1HowIt Works...... 354 13.1.2ReimplementingaDefault HandlertoReadRSS Feeds..355 13.1.3Digression:Equipping theRSS ReaderwithaGUI and NetworkCapability...... 361 13.2The DOMAPI ...... 366 13.2.1Reading in andProcessing XMLFiles...... 367 13.2.2Searching for SpecificElements...... 370 13.2.3Manipulatingthe DOMTree...... 371 13.2.4The DOMTreeasXML Output ...... 372

11 Contents

14 Internationalization 375 14.1Translating Applications into Other Languages...... 375 14.1.1Preparing theApplication...... 376 14.1.2Processing TranslationSources withLinguist...... 377 14.1.3Using Translations in theProgram ...... 378 14.1.4Adding Notesfor theTranslation ...... 380 14.1.5Specifyingthe TranslationContext...... 380 14.1.6InternationalizingStrings OutsideQtClasses...... 381

Appendixes 383

ADebugging Help 385 A.1Debugging Functions...... 385 A.1.1SimpleDebug Output ...... 386 A.1.2Errorsand Warnings ...... 387 A.1.3Customizingthe Output of Debugging Functions..... 388 A.2WaystoEliminateErrors...... 390 A.2.1Checking Assertions ...... 390 A.2.2Checking Pointers...... 391 A.2.3CommonLinkerErrors...... 392

BTulip: Containers andAlgorithms393 B.1Iterators...... 394 B.1.1STL-Style Iterators...... 395 B.1.2Java-Style Iterators...... 396 B.2Lists ...... 398 B.2.1SimpleList(QList) ...... 400 B.2.2LinkedList(QLinkedList) ...... 401 B.2.3Vectors (QVector)...... 401 B.3Stacks andQueues...... 403 B.3.1Stacks (QStack)...... 403 B.3.2Queues(QQueue) ...... 404 B.4AssociativeArrays...... 404 B.4.1Dictionaries (QMap) ...... 404

12 Contents

B.4.2AllowingSeveral Identical Keys(QMultiMap) ...... 407 B.4.3HashTableswithQHash ...... 409 B.4.4Hash-basedAmountswithQSet...... 411 B.5Algorithms ...... 412 B.5.1The foreach Keyword...... 412 B.5.2Sorting ...... 413 B.5.3Sorting in Unsorted Containers...... 414 B.5.4CopyingContainer Areas...... 415 B.5.5BinarySearch in Sorted Containers...... 416 B.5.6Countingthe Number of Occurences of Equal Elements .. 418 B.5.7Deleting PointersinLists ...... 418 B.5.8Checking that Data Structures HaveIdentical Elements .. 419 B.5.9FillingDataStructures...... 420 B.5.10Swapping Values ...... 420 B.5.11 Minimum, Maximum, andThreshold Values ...... 421 B.5.12 DeterminingAbsoluteValue ...... 422 B.6Qt-specificType Definitions ...... 422 B.6.1Integer types...... 422 B.6.2Floating-point Values ...... 423 B.6.3Shortcuts for CommonTypes...... 423

Index425

13

Foreword to the EnglishEdition

“Weneed an object-oriented displaysystem,” HaavardNordsaid, as thetwoofus satonapark benchinthe summersun outsidethe regional hospital in Trondheim, Norway,inthe summerof1991. “Huh, what’sthat?”was myresponse. Haavardwentontosketchwhatseemed likeanobvious idea at thetime. AC++ libraryof user interfacecomponents,orwidgets,thatwould havethe same APIon allplatforms. We hadbothjustgonethrough thetransitionfromproceduralprogramming to event-driven programming andwereappalledatthe toolsavailable to do thejob. Almost threeyears, acoupleofdesigniterations,and some serioushackinglater,we incorporated whatwas to become Trolltech. At last wecould work full time on our greatpassion:tomake thelives of more pleasant.Weweresickand tiredofusing toolsthatdetracted from thejoyof creating software. We wanted to create toolsthatmade you think, “Ofcourse, this is thewayit was alwaysmeant to be.” And werefused to compromise.Wedesigned, redesigned,and threwawaylots of code until wefeltwegot it just right. If ause casecould be solved byoneline less of code andstill be easyto read ayearafter you hadwritten thecode (both areimportant), then wewould ditchthe currentdesignand redo whathad to be redone. TodayTrolltechhas almost 250employees andisapublic companylisted on the Oslo stockexchange.But still, that passion for making thebestpossible developer toolsare evidentinthe hallwaysofour offices. Haavardand Ihaveleftthe controlofQt’sdestinyto muchmorecapable program- mers than ourselves.And Ihavetosaytheyaredoing an extraordinaryjobatit. Qt hasdevelopedintoanexceptionallybeautifulpiece of software. TodayMatthiasEttrich(KDE founder)and Lars Knoll (ofKHTML fame) lead the teamofdevelopers responsible for keepingQtthe kick-assproduct you expect from Trolltech.

15 Foreword to theEnglish Edition

Qt 4ismoreorlessatotalrewrite of Qt. AndIknowoneofthe partsthe Trolltech developers areespeciallyproudofisthe newpaintingengineinQt4.Itiscalled Arthur the paint engine,after “Thomasthe Tank Engine,” 1 anditreallyis “the paint engine that could!” Arthur hasdevelopedintoastate-of-the-artpaintingengine that makesitpossible to easilycreate allthose eye-catchingvisual effectsend usershavecometoexpect.And once you havecreated yourbreathtaking stuff, recompilingwillmake it runonall platforms supported byQt. From theveryfirst version of Qt released back in May1995, Trolltechhas been usingadual-licensing business modelwithafree version of Qt available for devel- opers of free andopensourcesoftware. Thefirsttoe in thewater back then was abinary-onlyversion for developmentoffreesoftwareunder Linuxonly.Thiswas quicklyfollowed byafreeversion containing thecomplete .Today,Qt is available both under astandardcommerciallicense andunder theGPL (General Public License) on allplatforms. What this meansinpracticeisthatifyou wanttodonateyourworkbased on Qt to thecommunityat largebylicensing yourworkunder theGPL,thengoahead anduse ourproduct for free. Youcan even modifyandredistribute Qt under the terms of theGPL,nostrings attached.If, on theother hand,you wanttokeep yoursoftwarebased on Qt proprietaryanddonot wanttolicense yourcode under theGPL,thenyou also haveanoption. Youcan purchase Qt under astandard commerciallicense from Trolltechand you can license yourcode anywayyou want, no stringsattached. Qt is an open source product withathrivingcommunityandatthe same time a commercialproduct withthe backing of apublic companywithastrong developer, support, anddocumentationmuscle. It reallyis thebestofbothworlds. And it is because of thefeedback from thethousands of usersout there, both commercialand opensourcedevelopers, that Qt is such ahigh-qualitytoolkit. We still relyon andlisten closelyto allthe feedback wereceiveonQt. So,ifyou do find somethingyou thinkcan be improved in Qt or simplyhaveabugtoreport, do not hesitate to letusknow,e.g., bysendinganemail to [email protected]. We haveateamofeagerengineerswanting to hear from you. Well, Iguess Ihavebraggedenoughabout Qt now.The proof reallyis in thepud- ding.The onlywayto trulyappreciate Qt is to startusing it.So, startdivinginto this excellent coverage of Qt byDaniel Molkentin,and as you go along, playaround withQtasmuchasyou can.Ido hope andbelievethatyou willbesurprised and pleasedbytheprogramming universe of Qt. Enjoy!

EirikChambe-Eng, co-CEOand founder,TrolltechASA

1 Seehttp://www.thomasandfriends.com/.

16 Foreword to the GermanEdition

Traditionally,gettingapplications to look “justright”ondifferent operating systems andplatforms hasbeen thestuff of nightmares for programmers. Applications on Microsoft Windowslook andfeel different to thoseonMac OS X, which in turn aredifferent for thoseusing Linuxplatforms.Applications written for Linuxeven behavedifferentlyon thevarious free desktopenvironmentsavailable,and much thesamecan be said of thevarious flavorsofWindows. While it is relativelyeasyto write code that works on allofthese platforms,such code is either likelyto feel alienonall butthe oneplatformitwas originallyde- signed for,oritdoesn’t providefeaturesthatusers expect to findinmodern appli- cations. With Qt, ouraimsare muchhigherthanthis. We wantapplications written with Qt to be written in aplatform-independentway,yet workaswellasanyother applicationonMicrosoft Windows, MacOSX,and Linuxdesktops—even on mobile devices.Writing code to make this possible is no easytask;itisone that providesa new—and welcome—challengefor theTrolltechteamofdevelopers each day. ThemostdifficultpartofQttoget right, andalsothe most visible, is thegraphical user interface(GUI). Thefoundationfor anyGUI is thetechnologyused to ren- derthe interface, andQtcontainsits owntechnologyfor this purpose—namely “Arthur thePaint Engine.” Partlyinspired by“Thomasthe Tank Engine,” Arthur has come alongwayin recent years. Itsstoryis agood exampleofhowQt hascontin- uedtodevelop as usersprovidefeedback andsuggest solutionstoproblems they encounter. Arthur began withjustsuchasimple problem: ManyQt usersmake useofOpenGL in theirsoftware, andtheyincreasinglywanted to be able to choosebetween draw- ingwithOpenGLand drawingonconventional widgets,but usethe same applica- tion programming interface(API) for both.Wewanted to providethisabilitywith Qt 4. Easier said than done!The oldpaint devicearchitecture,nearly10years old whenwestarted,could notcope withthischange, andsoalittlerefactoringwas necessarybeforeArthur couldsee thelight of day.Inthe first TechnologyPreview of Qt 4, wewereable to drawgraphicsnot onlywithOpenGL, butalsoonthe relativelynewGDI+ on Windows, withbothanti-aliasing andlineargradients.

17 Foreword to theGermanEdition

Verynice,wethought,and far better than Qt 3, butwehad failedtoreckonwith thehighexpectationsofour users. Manychangeshad been taking placeinthe world of computer graphics. TheXRender extension to X11was becoming ever more powerful, theCairo projectwas getting readyto closethe gap withother systemsonceand for all, theMacintoshhad CoreGraphics, andanewScalable Vector Graphics(SVG) formathad beguntoindicatewhere modern toolkits were going at thebeginning of the21stcentury. Arthur was on theright path, butwestill hadanenormousamount of ground to make up.Thingsthatworkedfastonthe MacwereslowwithGDI+onWindows or withX11, andviceversa.And under X11—ourcoreplatformfor theopensource version—wewerealong waybehind,even whenusing extensionslikeXRender. Team Arthur,headedbyGunnarSletta,thereforeset itself anewgoal:tomatch the featureset offeredbySVG. Theteamwas determinednot to rest until high-quality vectorgraphicscould be displayed equallywellonall platforms,and at high speed, too. Allthishappened,mindyou, after therelease of thefirstQt4Technology Preview. Howwould you havereacted as theproject manager? We knewthat oursolution was notgood enough,but wewerealreadybehind schedule.And howcan you putyourfaith in twodevelopers alonebeing able to write anewrenderer that willcompete withthe giants of vendors? Notonlythis,wealso wanted it to be even better than GDI+,and platformindependent as well. Team Arthur was given threeweekstobuild aprototype to showwhatitcould achieve. Thetwoofthemcameupwiththe goods!The breakthrough success was due to thefantastic scan-lineconverter from theFreeType project, apiece of open source softwarethatisgenerallyused to displaytext. It took just threemoredays to displaycomplexSVGs withfloating-point precision, anti-aliasing,and powerful gradients. What can welearn from this?Qtiscontinuallybeingdevelopedbyhighlymoti- vated (and sometimesquite crazy)programmers who callthemselves Trolls,who are spurredontoprovidethe best product for ourusers. It is also developedbyded- icated peoplelikeDanieland Patricia,towhomour thanks go forproducing this wonderful book you areholding in yourhands,and bymanyothercontributors andusers around theworld. Withoutthispriceless community,Qtwould notbewhere it is today,and thede- velopmentteamisgratefullyawareofthis. This is preciselythereasonwewill continue to strivefor more andbetter.For you,our users—andbecause weenjoyit alittlebit,ofcourse. WelcometoQt!

MatthiasEttrich, Head of Development, Trolltech

18 Introduction

Anumber of years ago,Ihappenedtocomeacrossanarticle on GUI programming withC++. Ihad just started learning C++and was amazed at howlittlecode the author2 needed to produce acomplete game, includingthe menus.Ofcourse, there wereanumber of constructs that needed explanation, butafter ashort time Iwas hooked:The Qt librarythat he used turned outnot onlyto haveaveryextensive collectionofall kindsofuseful widgets (alsoknowntoWindowsprogrammers as controlelements ), butinaddition hadstandardalgorithms,datastructures, and othernongraphic classesthatmade programming withC++ so intuitive, in away that Ihad never seen beforeinanyothertoolkit. Thesoftwarecompany,Trolltech, was also promotingits ownplatform-independent API. This toolkit, which couldproduce programsfor both Windowsand ,sim- plybyrecompilingthe code,attracted myattention.Shortlyafter this,nearlysix years ago to theday,Ijoined theKDE project, which was developing an entire desk- topbased on Qt. Today,together withGNOME, KDE is oneofthe most important desktops under Linux.But Qt is also used byasubstantial number of companies: GoogleEarth is basedonQt, as is thetelephonysoftwareSkype andthe video editingprogram MainActor. When Trolltechpublishedapre-version of Qt 4in2005, Istarted tryingout several of thenewfunctionalitiesand was veryimpressed. Forthe first time therewas auniformlicensing scheme for variationsofQt, which until then weredifferent for LinuxandMac OS X: Quid proquo—those companiesthatpublishaprogram under an open source license mayusethe opensourceversion of thelibrary.But if thecompanyis developing proprietaryprograms, then it paysfor Qt license fees, thus supporting thedevelopmentofthe toolkit, andreceives supportfromthe manufacturer. This structural level is of relevance as far as thelicensing of thecommercialQt version.Trolltechhas threeeditionsofQt4available: Qt Console for nongraphic development, and Qt Desktop Light and Qt Desktop as versionscontainingall features.The opensourceversion in each casecorresponds to thedesktop edition, so it is notrestricted in anywayin terms of size.

2 Thearticle was writtenbyMatthiasEttrich, thefounder of theKDE project.

19 Introduction

This book is basedonthe opensourceedition of Qt, butitcan also be used without problembythosewho havepurchased thecommercialversion.The embedded version of Qt ( QtopiaCore )isnot covered in thebook,because although theAPI is identical,apart from afewextraclasses, thereare so manyitemstobenoted in embeddeddevelopmentthataseparatebook would be needed to describe them all.

Target Audienceand Prerequisites

It is difficulttodefine atargetaudiencefor Qt programming because theareas of applicationfor Qt arealmostlimitless.Ingeneral,however,itisaimed at allthose who wishtohaveplatform-independentresults in amachine-oriented high-level language such as C++, resultsthatcan be compiledintonativecode,not leastfor reasonsofperformance. Thebook assumesthatyou haveafundamentalknowledge of C++. An interested readershouldbefamiliarwiththe concepts of pointersand templates.The book also assumesthatyou knowaboutthingssuchasthe overloading of operators. Knowledge of theStandardTemplateLibrary(STL) in particular is notexpected.Qt provides itsownclassesfor themostcommonalgorithms andcontainers, which areexplainedinAppendixB.

Structureofthe Book

Thebook first explains thebasic structureofthe Qt toolkit, together withits most important specificproperties. Thesubsequentchaptersconcentrate on writing your ownsmall applications.All othertechnologies,presented in thefinalchapters, are demonstrated as short, independentexamples for thesake of clarity.But theyare arranged in such awaythat it should be no problemtouse them in arealprogram at thecorrect position. Nearlyallofthe examples printed in this book arebased on acomplete andcompi- lable testprogram.These examples beginwiththe name of thequoted source text fileinC++ comments, such as

// program_name/file_name.cpp

Forabetter understanding,explanations areoften added between code segments, so that thecode is interrupted.Whenthe code continuesinsuchcases,itisalso marked as acomment:

// program_name/file_name.cpp (continued)

20 Introduction

If you prefer to read theexamples in context, or wanttotrythem outyourself, you can download acomplete archivewithall theexamples describedinthe book.This file, as wellasother hintsand linkrecommendations,isavailable at thewebsite for thebook: http://www.qt4-book.com/

This book is aimedatbeginners andisnot intendedtobeareference. Theexcel- lent onlinedocumentation—which can be calledvia theQtAssistantincorporated in thedistribution or onlineathttp://doc.trolltech.com/—provides adetailedAPI documentationonall theclassesintroducedhere, andwould be hard to beat. Instead, thebook aims to explaincontexts andbasic techniques bytheuse of examples,and to simplifygetting started withprogramming,justasthatmagazine articleback then helped me whenIwas starting to gettogrips withQt.

Note of Thanks

It would havebeen impossible to read this book without anumber of peoplegiving me supportwiththeir advice.Inparticular, Patricia Jung andher colleagues from Open Source Pressmade asignificantcontribution as farasthe formand contents of thebook wereconcerned.Further thanks go to Rainer M. Schmid, who also helped in getting theproject off theground,and SteveTomlin,who didafantastic jobintranslating this book. Iwould also liketothank thepeoplewho proofreadthe book,providingmewith valuable feedback. Thorsten Star¨ kand StephanZeissler deservespecial mention here,aswellasAxel J age¨ , withwhomIhadmanyvaluable discussions. Iwould never havewritten this book,ofcourse, if Ihad notbumped into the KDE projectand metHarri Porten,apatient maintainer who looked after myfirst patches andcommented on them withgreat patience. As the book was beingwritten,Ialso found supportfromthe extremelyhelpful membersofthe #kde4-devel channelatirc.freenode.net who provided important tips andadvice. ThemailinglistarchiveofTrolltechand thecommunityforum http://www.qtcentre.org/ also provided valuable tips,clarifyingsometrickyques- tions. Trolltechitselfdeserves specialthanks, because duetothe dual licensing,the com- panysupports theidealsoffreesoftware. In some cases thesourcetextwas the last possibilityof confirmingcertain technical issues,where gaps weretobeeven in theexcellent APIdocumentation. TheLinux/UnixUsergroup SanktAugustinand theBonnerNetzladenalsodeserve aspecial mention. Theirexcellent provisionofClub-Mate iced tea alwayskeptme wideawake during mywork.

21 Introduction

Manyof myfriendshavegiven me encouragement andmotivationonthisproject, andmanythanks should also go to myfamily.Theyprovided support, particularly in thecritical phases,which was agreat help in mywork. This book is therefore dedicatedtothe best familyIcould askfor.

Daniel Molkentin

22 Preparations

Youshouldalwaysuse themostrecentQtversion from Trolltech 3 to ensure hav- ingasmanybugfixes covered as possible.Linuxusersmayalso usethe precom- piledpackagesfromtheir Linuxdistribution,but theyshould be prepared to meet some—often subtle—problems witholder distributionslikeUbuntuDapper Drake, where problems withthe debuglibrarieshavebeen reported.Inthese cases,itis safertoobtainthe source texts from Trolltech, andcompile thesources yourselfas describedbelow. On OS X, you can choosebetween compilingQtyourselforusing adiskimage (.dmg) archive, which installs precompiledlibrariesto/opt/qt4. Thelatter willinstall only thestaticlibraries, which areeasilyidentifiedvia theirextension (.a).Atlinktime, theselibrariesbecomepartofthe binary.Asecond .dmg archivecontainsthe debug version of allQtlibraries, which should be installedondevelopmentsystems, too. AlthoughQtcan be configuredtobuild static librariesonall platforms,thisis mostlyused on Mac OS X, where static linking is thepreferred wayto avoidli- braryproblems,atthe expenseofdiskspace. If you choosetobuild thesources on Linuxor Mac OS X, it is sufficienttounpack thearchive, and—providedyou haveacompilerinstalled—runthe command

./configure-qt- -debug make from withinthe packagedirectory.Beforehandyou should check, using./configure -help, whether thesystem takesaccount of specificmodulesduringcompilation, such as particular databasedrivers, which you mayrequirefor yourwork. More detailednotes,especiallyon theSQL modules, areavailable in thecorresponding chaptersofthisbook. Theparametersspecifiedherefor configure haveturnedout to be thesmallest commondenominator formanyapplicationcases:-qt-gif addssupportfor theGIF fileformat, disabledbydefaultfor reasonsoflicensing,tobeincludedinthe library. -debug ensuresthataversion withdebugging iconsisbuilt,apart from thenormal

3 Seeftp://ftp.trolltech.com/Qt/qt/source.

23 Preparations

libraries. Fordeveloping,you should alwaysuse thedebug version of thelibraries. If you don’t do this,several of theexamples in this book willprovidenooutput, sincetheyusedebugging functionalityfor this purpose. Amake installinstalls Qt under Unixto thedirectory/usr/local/Trolltech/Qt-version . If you would prefer adifferent directory,you passthe desiredinstallation pathto configure,using the-prefixoption. Qt can be used straight outofthe directory in which it was built,however.Todothisyou just havetoinclude thebin di- rectoryin thepathand thelib directoryunder Unixin theenvironment variable LD_LIBRARY_PATH. Usersofthe Windowsopensourceedition can download aprecompiledarchiveas an file. It willrun an installerthatdoesnot onlyinstallQt, butalso offers to download MinGW.4 MinGW(Minimalist GNUfor Windows )isaportof theGNU CompilerCollection(GCC) to Windows, which producesnativeWindows executableswithout theneed for aUnixcompatibilitylibrary,asisthe casewith Cygwin. Afterasuccessful installation,Qtisavailable from C:\Qt\version .OnWindows, Trolltechprovides both static anddynamicversionsofQt: Thestaticlibrariescan be found in lib, while thedynamicallylinkedones“live” in bin alongwiththe helper tools. Thereasonfor this hodgepodge is that Windowslooksfor librariesonlyin specialplaces(likeinthe system32 directory)and in thepathwhere thebinary resides. If you prefer to build Qt from scratch, you willneed to download andinstall MingGW yourself. Afterhavingdonethissuccessfully,you installQtasdescribed above, except that theconfigure command in this caseisan.exefile,sothatthe configuration command mustbewritten as

configure.exe-qt-gif -debug

Corresponding graphic developmentenvironmentsare presented in thefirstchap- ter.

4 Seehttp://www.mingw.org/.

24 r te ap 1 Ch

Basics,Tools, and First Code

1.1Our FirstQtProgram

Followinginthe tradition of manyprogramming booksand tutorials, this book will startwiththe obligatory“Hello,world!” program.Thisminimal Qt application, which wewillsaveinafilecalledmain.cpp, simplyopens awindowdisplayingthe textHello,world!whenitisrun:

// helloWorld/main.cpp

#include #include intmain(intargc, char * argv[]) { QApplication a(argc, argv);

25 1 ,Tools, and First Code

QLabel label("Hello World"); label.show();

returna.exec(); }

Forthispurpose, thefirsttwolines of code includethe headerfilesfor theQt classesthatwewanttouse in thefollowingcode.Inour casethese headerfiles containthe interfacedescriptionsfor theclassesQApplicationand QLabel. In Qt 4there is preciselyoneheaderfile for each Qt class, which is named without theotherwisestandardfilename extension .h:Its name corresponds exactlyto the classname.1 When you givethe #include directive, make sure that you capitalize theheaderfilename correctly. Thefourthlineofthe listing onwardshowswhatatypical main() function of aQt program lookslike. Firstyou create aQApplicationobject andpasstoits constructor thecommand-lineargumentsthatthe user suppliedwheninvokingthe finished program.NoGUI program can manage without aQApplicationobject,because, amongother things,QApplicationmakesavailable an event loop.Thisloop ensures that theapplicationcontinuesrunning until itswindowis closed. NextwecreateaQLabel object that displaysthe text“Hello,world!”.Initially,this object is invisible.Wemustcallits show() function in ordertomake it appear—as showninFigure1.1—inawindow.

Figure 1.1: ThefirstQtprogram

Finallythecalltoexec() starts theeventloop which is in charge of forwarding ap- plicationevents to theappropriate objects. Such events arecausedbyuser actions, such as clicking abutton. In ourfirstexamplewewillleaveeventhandlingentirely to Qt itself.Section 1.3(page 35) showshowadditionaluserinteractioncan be implemented. Theeventloop is terminated whenthe quit() function of theQApplicationobject is called. In ourexample, this happens indirectlywhenthe last main windowof the application(which in this caseislabel)closesand is deleted from memory.

1 In reality,these files themselves just containadirectivethatloads thecorresponding .h file. Theseare notdocumented,however,sothatyou cannever be quitesurewhether Trolltech mighthavemadeunannounced changes.

26 1.1 OurFirst Qt Program

1.1.1CompilingaQt Program

When compilingthisprogram,you arefacedwithaproblem: Qt is supported on various platforms,and thedetails of thecompilation processdifferfor each type of system.The Qt vendor Trolltechsolves this problemwithasimple program that is used to create projects on across-platformbasis:. qmakegenerates aMakefilefromaprojectfile that describesthe applicationin aformthatisindependent of theoperating system on which theapplicationis actuallycompiled. Thegenerated Makefilecontainsall theinformation required to compile theC++ applicationcode on aspecific platform.(In Windowsitisalso possible to generate Visual Studio projects from qmakeproject files.)

Generating Project Files andMakefiles with qmake

To generate aproject filefor the“Hello,world!” program,itissufficient to call qmakewiththe -project option.2 To do this,openashelland change thecurrent directoryto thedirectorycontaining thesourcefile.Ifthe source textislocated in thedirectoryhelloWorldand hasthe name,asinour case, main.cpp,3 then the command user@linux:helloWorld$ qmake -project willgenerateafilecalledhelloWorld.prowiththe followingcontents:

#helloWorld/helloWorld.pro

##################################### #Automaticallygenerated byqmake #####################################

TEMPLATE =app CONFIG -= moc DEPENDPATH +=. INCLUDEPATH +=.

#Input SOURCES +=main.cpp

2 Pleasemakesurethatyou reallydo usethe Qt 4qmake,which differs significantlyfrom the Qt 3version.Use of thelatteronQt4projects causes errors.ManyLinuxdistributionscontain both Qt 3and Qt 4; in UbuntuBreezy Badgerand DapperDrake,for example, qmakeislinkedby defaulttoqmake-qt3.The Qt 4version of thetool canberun withqmake-qt4;ifyou seldom need thethird edition, change thecorresponding linkto/etc/alternatives. 3 Althoughqmake doesnot requiremain.cpp to containthe main() function,thisconvention has become established.

27 1 Basics,Tools, and First Code

Theinteresting entrieshereare theonesfor TEMPLATEand SOURCES.4 Thevalue of TEMPLATEspecifieswhether wewanttocreateanapplication(app)oralibrary (lib);thatofSOURCES specifies thesourcetextfilesofwhich theproject consists. Simplyrunning qmakeisthensufficient to generate theMakefilefromthe project file:

user@linux:helloWorld$ qmake

In Windowsthiscommand generates threefiles: Makefile, Makefile.Debug, and Makefile.Release. Makefilehereisametafilethatreferstothe twoother files. Makefile.Debugand Makefile.Releasedescribe howthemake programshouldput theproject together. On Unixplatforms (including LinuxandMac OS Xprojectsbuilt withqmake -spec mac-g++, as describedbelow), qmakegenerates onlytheMakefilefile,which cre- ates an executable of theprogram that includes debugoutputafter make is run, unlessthe debugvariantsofthe Qt librariesusedbytheprogram aremissing. 5 To ensure that theseare installed, on Unixsystemsyou should search forthe li- braryfiles with_debug.so in thename, for example, libQtCore_debug.so.4 for the debugversion of theQtCorelibrary.InWindowsyou should look for thecorre- sponding DLLfileswithnames ending in dbeforethe version number,for example, qtcored4.dllfor thedebug version of QtCore. Clicking theentryBuild debugli- brariesinthe startmenufolderProgram ensuresthatQtbuildsits debuglibraries.

Furthermore, in ordertoachievethe same resultswithqmake in Unixas in Win- dows, notonlymustthe debuglibrariesbeavailable,but thefollowinglinemust also be included in helloWorld.pro:

CONFIG +=debug_and_release

Theoperator+=herehas thesamefunction as in C++: It adds afurther option to thevariable,without overwriting theonesalreadyset. The-=operatorisused similarlyto removeindividualoptions. Alternativelyyou can setthe environment variable QMAKEFLAGS to thevalue ’CON- FIG+=debug_and_release’ (don’t forgetthe apostrophes!) beforerunning qmake. Butthe entryin the.profile simplifies developmentifseveral programmers areall working on thecode (orworking withone program on several computers) usingaversion controlsystem such as CVS, Subversion,orVisualSourceSafe.

4 Theother entriesare notrequired; qmakecan be toocautiouswhenautomaticallygenerating aproject.CONFIG=-moc specifies that wedonot need themeta-object compilerfor this project, andinthe entriesfor INCLUDEPATHand DEPENDPATH wecan specifydirectoriesin which thecompilershouldsearch forinclude files. 5 Thedebug librariesmayneed to be installedseparatelyin,for example, (libqt4-debug, libqt4-debug-dev)and SUSE (qt-debug, qt-devel).

28 1.2 Layouts, Object Hierarchy,and Memory Management

If you just wantthe debug-enabledvariation of theexecutable application, the CONFIG variable should includethe value debug. Likewise, you can includerelease if you just wanttogenerateexecutable files without extradebugging support, suitable for releasetothe enduser.

Compilingthe Project

Themake command issued without anytarget or withthe releasetarget, for ex- ample, user@linux:helloWorld$ make release creates arelease version of theproject,whereas user@linux:helloWorld$ make debug accordinglycreates adebug version.Ifyou useMicrosoft Visual Studio,changethe command make to nmake.Ineithercase, theexecutable fileisstoredinthe release or debugsubdirectory,asappropriate. Once theapplicationiscompiledinthiswayandexecuted on Unixsystemswith either ./release/helloWorldor./debug/helloWorld, thewindowopens as shownin Figure 1.1onpage 26.

1.2Layouts,ObjectHierarchy,and Memory Management

1.2.1How to ArrangeWidgets Automatically

In ordertoextendthe “Hello,world!” program so that it doesn’t just showtextin asingleQLabelobject,but arranges twoQLabels oneunder theother,asshownin Figure 1.2, weuse thelayoutsystem included in Qt. This automaticallyarranges theGUI elements,referredtoinQtas widgets or controls.Inlanguage similarto that used in thefieldofprinting, wetalkhereof layouting.

Figure 1.2: Awidgetwithvertical layout

29 1 Basics,Tools, and First Code

Figure 1.2iscreated withthe followingsourcecode:

// layout/main.cpp

#include #include #include

intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QWidgetwindow;

QVBoxLayout* mainLayout =newQVBoxLayout(&window); QLabel* label1 =newQLabel("One"); QLabel* label2=newQLabel("Two");

mainLayout->addWidget(label1); mainLayout->addWidget(label2);

window.show();

returna.exec(); }

In addition to theQApplicationand QLabel classesalreadyused in Section1.1,we nowincludeaclass, QVBoxLayout, that is used to arrangewidgets vertically—the “V”inthe name stands for vertical .Thistime, insteadofthe QLabel object,there is asimpleQWidgetobject for themainwindowof theapplication, which wecall window.Use of this variable name is just aconvention.The object onlybecomes aseparatewindowafter twosteps. First, theQWidgetconstructor is called. If no argumentsare supplied, as in this case, thenewobject hasnoparentwidget andthus itself forms theroot of an object hierarchy.Second, awidgetbecomes a windowonlywhenitisdisplayed usingits show() method. Aftercreatingthe QWidget, wecreateaQVBoxLayoutobject.The reason whythe newoperatorisusedhereinsteadof

QVBoxLayout mainLayout(&window);

is explainedinthe followingSection 1.2.2. So that thenewQVBoxLayoutwillknow that it is responsible for thelayoutofwindow,its constructorisgiven apointer to this QWidgetobject as an argument. Similarly,the twoQLabelobjectswiththe texts Oneand Twoare created.Inorder for thesetobemanagedbythelayoutobject,weadd them to theQVBoxLayout object withthe QVBoxLayoutfunction addWidget().

30 1.2 Layouts, Object Hierarchy,and Memory Management

Otherwisethe rest of theprogram hardlydiffers from thefirstexample. Observe that weonlyhavetomake theQWidgetobject visiblebycallingits show() method. This causesall widgets that theQWidgetcontainstobedisplayed on thescreen as well, which in ourcaseisbothQLabels. Finallywestart theeventloop as beforeand passthe return code from theevent loop back as theapplication’sreturn value.Asimple return 0;would terminate the applicationimmediatelywithout even displayingthe window. This applicationdemonstrates themainadvantage of layouts:You don’t need to worryaboutthe exact positioningofthe widgets.Inaddition,the user can enlarge or scaledownlayoutwindows, andthe layoutautomaticallyensuresthatthe com- ponents of thewindowwill adjusttosensiblyfillthe space available,without the need for theprogrammertowrite explicit code to implementthisbehavior. Theprogrammermayalso definewhatbehaviorisallowed for individualwidgets in alayout: for example, whether acontrol element(such as awidgettodisplay multiplelinetexts)shouldoccupyas muchspaceaspossible or be constrained in size; or howto handle widgets which don’t need more vertical space,suchas checkboxes.Chapter 5(page 141) describesthe possibilitieshereinmoredetail.

1.2.2MemoryManagementinObjectHierarchies

TheapplicationshowninFigure1.2 anddescribedinthe previous sectionnot only introducesautomatic layouts,but also differs from “Hello,world!” from Section1.1 in anotherrespect:Althoughthe variable declaration QWidgetwindow;isusedto allocate theQWidgetobject,the newoperatorisusedtoallocate theQVBoxLayout andthe QLabel objects. We used

QVBoxLayout * mainLayout =newQVBoxLayout(&window); rather than

QVBoxLayoutmainLayout(&window); to create thelayout. We’vetakenthisapproachbecause C++doesnot provideautomatic memoryman- agement.Ingeneral,the applicationprogrammermusttake careofthishimself. However,Qtcan take over some of theworkofmemorymanagement,asfollows. Objectsofclassesthatare derived from theQObject classcan be arranged to form atreestructure:Objectsmaypossess “child”objects. If such an object is deleted, then Qt automaticallydeletes allofthe child objects, andthese “children” in turn delete theirown“offspring,”and so on.

31 1 Basics,Tools, and First Code

Figure 1.3: QWidget Classesinherited from QObject canbe arranged in atree. QVBoxLayout QLabel QLabel

Putanother way,ifthe root of an object tree disappears, Qt automaticallydeletes theentiretree. This relieves theprogrammerfromhavingtotrack downthe de- scendants of theobject andrelease thememorythat theyoccupy.However,in orderfor this automaticmemorymanagement to function,all children(andthe children’schildren, and...) mustlie on the heap,which is broughtabout bycreat- ingthemusing new.Objectsthatare created usingnewarethenreferencedwith apointer into theheap. This is whymainLayoutwas declared as apointer to a QVBoxLayout, rather than as aQVBoxLayout. Not placingobjectsonthe heap (thatis, not allocating them withnew)isacom- monbeginner’smistake:Widgets that arecreated onlyon thestack, for example, in aclass constructor, aredeleted bythecompilerafter processing is finished. Al- though theapplicationdoesgeneratethe widgetbriefly,itisnever visibletothe eye.6 Also,thisdeclaration notonlycreates theQVBoxLayoutobject butalsomakesita child of theQWidgetobject window,bymeansofthe constructorprovided bythe QVBoxLayoutclass.Incontrast, whentheyarecreated,the twolabelsinitiallyhave no parentobject;the QLabel constructorinitializes onlythelabel’s text:

QLabel* label1 =newQLabel("One");

We usethe subsequent QVBoxLayout::addWidget()calls to ensure that theQWid- getobject assumesparentage of each of thenewlabels.(In fact,the GUI elements containedinawidgetmustbethe childrenofthe overlyingwidget. Forthisrea- son, theQWidgetobject becomesthe parentofthe QLabel object,and notthe

6 Of course,all allocatedobjectswill go outofscope afterexec() returns. However,the problem here is that we implicitly createanobject hierarchyvia thelayouts forthe first time ((foo- >addWidget(bar) automaticallyassignsthe widgetthatlayoutfoo managestobethe parent of bar).Whenthe parent widgetgoesout of scope,itwill tryto delete itschildren, which may alreadyhavegoneout of scope,depending on theorder that thecompilerchooses to placethe objectsonthe stack (thisshouldbedeterministicinorder of creations buthas allegedlybeen nondeterministicinspecial situations withsomecompilers). Andeven if you do it correctly, thereisstill alot of stuff to getwrong.Nowif you just createall QObject derivatives (and thus QWidgetderivatives)onthe heap,you don’t havetodeal withthose issues and, as abenefit, thecode is alot easiertorefactorlater on.Thisiswhyit reallyis advisable to createall objects on theheap rather than on thestack, withthe exceptionofthe parent widget.

32 1.2 Layouts, Object Hierarchy,and Memory Management

QVBoxLayoutobject,asmight be assumed.)Atree structureasshowninFigure1.3 is therebycreated. Boththe layoutobject andthe twolabels, which aresubobjectsofthe window object,mustbegenerated on theheapusing new.Onthe otherhand, wegenerate thewindowon thestack usingQWidgetwindow;, so that wedon’t havetodelete it byhand whenthe applicationisterminated.(Youcan do this onlywithobjects that havenoparentobject.) Therefore, in most cases you should create objectsof classesderived from QObject on theheapusing new.

1.2.3Other Layout Types

Theclass QHBoxLayoutisusedtoarrange elements horizontally,inthe same way that theQVBoxLayoutclass is used for vertical layouts.Its interfaceisjustlikethat of theQVBoxLayout. If you replaceQVBoxLayoutwithQHBoxLayoutinthe example from Figure 1.2, theresultwillappear as showninFigure1.4.

Figure 1.4: Thetwo labels arranged horizontally insteadofvertically

Thereisalsoaclassthatarrangeswidgets in agrid, QGridLayout:

// gridLayout/main.cpp

#include #include #include intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QWidgetwindow;

QGridLayout* mainLayout =newQGridLayout(&window); QLabel* label1 =newQLabel("One"); QLabel* label2=newQLabel("Two"); QLabel* label3=newQLabel("Three"); QLabel* label4 =newQLabel("Four"); QLabel* label5 =newQLabel("Five"); QLabel* label6=newQLabel("Six");

mainLayout->addWidget(label1, 0,0); mainLayout->addWidget(label2,0,1);

33 1 Basics,Tools, and First Code

mainLayout->addWidget(label3,1,0); mainLayout->addWidget(label4, 1, 1); mainLayout->addWidget(label5, 2,0); mainLayout->addWidget(label6,2,1);

window.show();

returna.exec(); }

This program is likethe previous example: Insteadofthe QVBoxLayout, nowa QGridLayoutisusedasa“container,” this time for sixQLabel objects. Unlikethe addWidget()function of thehorizontalorvertical layoutclass,QGridLayout::add- Widget()requiresthree arguments: thewidgettobeallocated, as wellasthe line andcolumn number of thegridcellinwhich it should take itsplace.The first cell of thegridhas thecoordinates (0,0)and is locatedatthe topleftcorner. Theresult can be seen in Figure 1.5. If thetextFiveisnot displayed correctly,the typical reason is that theeditorused hassaved thesourcefile in UTF-8 encodedform. In this caseitneedstobecon- verted to theISO-8859-1orISO-8859-15 format. In theKDE editor , this optioncan be found in theSaveas. .. dialog.

Figure 1.5: Aprogram that uses QGridLayout

Youwillfind furtherdetails on thesubject of layoutinChapter 5which explains howyou can design complicated layouts,for example, throughnesting,and also looksatmanuallayout, splitters,and theQStackedLayoutclass. Splittersbehavelikevertical or horizontallayouts,but displayso-called handles in an otherwiseemptyspace.The user can pull thesehandles in either directionto make more space forthe widgetlyingnexttoitonthe side oppositethe motion. When ahandleispulled, thewidgets shrink on thesidetowardwhich thehandle is pulled. TheQStackedLayoutclass,onthe otherhand, manageslayouts withseveral “pan- els” that can each containvarious groups of widgets,ofwhich onlyoneisever visible. Configuration dialogs can be created usingthisclass.When, for example, theuserselects acategoryon aleft-hand panel of such aconfiguration dialog, this causesthe right-hand panel to showthewidgets that can be used to change the configuration of thechosencategory.Whenthe user changesthe categoryon the

34 1.3 Signalsand Slots

left side,the QStackedLayoutobject knowsthatitshoulddisplayadifferent “page” on theright side.

1.3Signals andSlots

Theprogramsdiscusseduntil nowgenerate output only.But if weneed to handle user input, wecannotmanage without communication between objects. ManyGUI toolkits use callbackfunctions or eventlisteners to manage commu- nication between objects, butQtusesthe signal/slotconcept. 7 Comparedwith callback functions, this mechanismhas theadvantage that Qt automaticallydis- mantlesaconnectionifeitherofthe twocommunicating objectsisdeleted.This avoids crashes, andmakesprogramming simpler.

1.3.1The SimplestCase: ASlotResponds to aSignal

Theeasiest wayto explainhowsignalsand slotsallowobjectstocommunicate is withasimple example. Considerthe followingprogram,which displaysasimple buttonwiththe textQuit. If theuserclicks this button, theapplicationends.

// signalSlot/main.cpp

#include #include intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QPushButton button("Quit"); button.show();

QObject::connect(&button, SIGNAL(clicked()), &a,SLOT(quit()));

returna.exec(); }

Comparedwiththe “Hello,world!” program from Section1.1 on page 25, onlytwo things havechanged.First,the QLabel object used therehas been replaced bya

7 Thereare also events andeventhandler functionsinQt. Thedifference between signalsand events is that asignalmaybe connected to as manyslotsasdesired,including slotsfrom different objects. In contrast, an eventhandler handlesevents determined forother objects. It’s akindofeventinterceptor.Chapter 7provides more details of events.

35 1 Basics,Tools, and First Code

QPushButton. This classisusedtodisplayabuttonand processmouse clicks on this button. Theseconddifferenceconsistsofthe calltoQObject::connect(). connect() is astatic function of theQObject classthatcreates aconnectionbetween asignaloriginat- ingfromone object andaslotinadestinationobject.The first twoarguments specifytheobject sendingthe signal andthe signal that wewanttobindtothe receivingslot. Thelasttwoargumentsspecifytheobject that is therecipient of the signal,and thereceivingslot. The&charactersare necessarybecause thefunction expectsthe addressesofthe sendingand receivingobjectsasarguments. Here this function is used to determine theactionthatisexecuted bytheapplicationwhen theuserpressesthe button: Theapplicationterminates. Slots arenormalfunctionsofaclass that arespeciallymarked so that theycan react to signals. Signals on theother hand are“sent”byobjects. Asignalfroman object can be connected to oneorseveral slotsofasingle receivingobject or of several differentreceivingobjects. If an object sendsout asignal, then allthe slots arecalledthatare connected to thesignal. If thereisnomatchinglink, nothing happens. Thecalltothe QObject::connect() function in theexampleconnectsthe clicked() signal of theQPushButtonobject withthe quit() slot of theQApplicationobject. Thebuttonsends outthe clicked()signalwhenever theuserpressesthe button, thus causing thebutton’sclicked()function to be called. In response to this signal, thequit()function of theapplicationiscalled. Callingthisslotendsthe eventloop, andthus theentireapplication. When linking signalsand slotswiththe QObject::connect() function,you mustuse themacros SIGNAL() andSLOT(), as shown. Forits second (signal) andfourth(slot) arguments, theconnect() function expectstobepassedstringvaluesthatcontain aprefixdescribingthe type (signalorslot) andotherwisecomplywithaninternal Qt convention,about which weneed notbeconcerned.Using thetwomacros will ensure that theexpected stringsare generated correctly.

1.3.2Signals Carrying Additional Informationand How They AreProcessed

Thelinkbetween asignaland aslotcan also be used to transmit additionalinfor- mation that controlsthe precisereactionofthe slot.For example, seethe applica- tion showninFigure1.6.Thisprogram consists of threecontrol elements:alabel, which displaysanumericvalue;aspin box,which can be used to change thevalue via thekeyboardormouse (and which also displaysthe value); andaslider,which showsthe currentvalue graphicallyandcan be manipulated to change thevalue.

36 1.3 Signalsand Slots

Figure 1.6: Allthree elements should displaythe same changeable value.

Theaim is for allthree widgets to alwaysdisplaythesamevalue.Ifthe user changes thevalue via theslider,the value mustalsobeadjusted in thespinboxandinthe label. Thesameappliestothe slider andlabel if theuseradjusts thevalue in the spin box.Thisisaccomplishedwiththe followingcode:

// signalSlot2/main.cpp

#include #include #include #include #include intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QWidgetwindow;

QVBoxLayout* mainLayout =newQVBoxLayout(&window); QLabel* label =newQLabel("0"); QSpinBox * spinBox=newQSpinBox; QSlider * slider=newQSlider(Qt::Horizontal);

mainLayout->addWidget(label); mainLayout->addWidget(spinBox); mainLayout->addWidget(slider);

QObject::connect(spinBox,SIGNAL(valueChanged(int)), label, SLOT(setNum(int))); QObject::connect(spinBox,SIGNAL(valueChanged(int)), slider,SLOT(setValue(int))); QObject::connect(slider,SIGNAL(valueChanged(int)), label, SLOT(setNum(int))); QObject::connect(slider,SIGNAL(valueChanged(int)), spinBox,SLOT(setValue(int)));

window.show();

returna.exec(); }

37 1 Basics,Tools, and First Code

We willplace thethree widgets in turn (thatis, from toptobottom) into avertical layout. To do this theQSpinBoxclasscontributes thespinboxelement, andthe QSlider is correspondinglyresponsible for theslider. To ensure synchronizationofthe widgets,weuse fourconnect() calls (Figure1.7): if thevalue of thespinboxchanges, then thelabel andthe slider mustbeupdated; if thestatusofthe slider varies, thelabel andthe spin boxneed to be brought up-to-date. (Notethatbecause thespinBox,label,and slider variablesare already pointer variablesthatreference thewidgets,wedon’t havetotake theiraddresses usingthe &operator, as wedid in theprevious example.)

Figure 1.7: Theexample program QSlider QSpinBox showsthatasignal canbeconnectedto setValue(int) setValue(int) several slots. valueChanged(int) valueChanged(int)

QLabel

setNum(int)

Achangemade to thevalue bytheuserisreported bytheQSpinBoxandQSlider classesvia thesignalQSpinBox::valueChanged(int) or QSlider::valueChanged(int). In each case, theinteger argument indicatedbytheint keyword, which thesignal transmitstothe slot,specifiesthe newvalue of thespinboxor theslider. Anewvalue for thelabel is setusing theQLabel::setNum(int)slot, afunction that is calledwithaninteger value as an argument.The spin boxandthe slider are handledsimilarlyusingthe slotsQSpinBox::setValue(int)and QSlider::setValue(int). ThearrowsinFigure1.7 showthat asignalcan be connected to several slotsand that aslotcan react to several signals. Forexample, if theQSpinBoxobject sends outthe signal valueChanged(int) withthe value 5, both thesetNum(int)slotofthe QLabel object andthe setValue(int)function of theQSlider object arecalledwith thevalue 5. Qt doesnot specifytheorder in which this happens.Eitherthe labelorthe slider can be updated first,and theexact behaviormaybe unpredictable.Still, allthree widgets willeventuallydisplaythevalue 5. In this examplethe signalsand slotsuse thesameargument listbecause no takesplace in signal/slotconnections.Thus thesetText()slotofthe QLa- belobject,which takesastring as an argument anddisplaysit, cannotbeconnected to thevalueChanged(int) signal,since theint argument willnot be converted to a string.

38 1.4 Base ClassesinQt

If atype conversion cannotbeavoided, then you mustmake aderived classand implementacorresponding slot.Thisnewslot performs thetype conversion on thevalue sent bythesignaland then calls theactual, desiredslot. This is possible sinceslots arenormalfunctionsofaclass.However,the reverse is nottrue: You cannotuse anyfunction you likeasaslot,since slotsmustspecificallymarked so that theyaredetected as such byQt. Chapter 2explains in detail howto inherit from QObject anddefine yourownsignals andslots. Even though signal/slotconnections do notautomaticallyadjustargument types, you mayconnect asignaltoaslot that accepts fewer argumentsthanthose sent bythesignal; theslotsimplyignoresthe extraarguments. In this waytheval- ueChanged(int) signal of theQSlider couldbeconnected,say,tothe quit() slot of theQApplicationobject.While this would terminate theapplicationassoon as thevalue of theslider is changed(admittedlynotanespeciallyuseful behavior), it doesshowthat quit() ignoresthe intsentbythesignal. Thetypesofthe argu- mentsusedbytheslotmustmatch thoseofthe signal arguments. Forexample, you can connect thesignalsignalFoo(int, double)tothe slotsslotFoo(), slotFoo(int), andslotFoo(int, double). However,you cannotlinkthe signalFoo(int, double)signal withthe slotFoo(double)slotusing connect(). If you tryto create an invalid signal/slotconnection, neitherthe compilernor the linkerwillcomplain. Onlywhenthe applicationisrun willyou seeawarning that thesignaland slot werenot connected properly.For example, if theerroneous connect() calldescribedinour previous paragraph is executed,the terminalwindow from which theprogram was calleddisplaysthe followingwarning:

Object::connect:Incompatible sender/receiverarguments SomeClass::signalFoo(int,double)--> SomeClass::slotFoo(double)

Aslotthatexpectsmoreargumentsthanthe signal contains cannotprocess the signal.Thus wecan’t connect thesignalFoo(int, double)signaltothe slotFoo(int, double,double)slot.

1.4BaseClasses in Qt

1.4.1ClassesDerivedfrom QObject

Boththe automaticmemorymanagement mechanism(seeChapter 1.2.2, page 31) andthe signal/slotmechanism requirethe classesinvolved to be derived from the QObject class.8

8 In addition to supportfor thesemechanisms, thereare furtherrequirementsdemandedof classeshavingQObject as abaseclass: thetreatment of events andthe translationofstrings from onelanguagetoanother.These areexplainedindetail in Chapters 7and 14.

39 1 Basics,Tools, and First Code

Several Qt classeshaveQObject as abaseclass.Thus allwidgets (thatis, allelements that displaysomethingonthe screen)are derived from theQWidgetclass,which, in turn,isderived from QObject.The layoutclassesare also inherited from QObject, so that theirobjectscan also be formedintohierarchies which derivebenefits from theautomatic memorymanagement.Asnonvisual objects, however,theyarenot derived from QWidget. Other nongraphical classesalsooriginate from QObject,suchasQThread forlight- weightprocesses(seeChapter 12) or QTcpSocket, aclass that providesobjectsfor networkcommunication via sockets.These classeshaveQObject as thebaseclass so that theycancommunicate throughsignals andslots.

1.4.2 QString andOther Classesnot Derivedfrom QObject

However,Qtalsocontainsmanyclassesthatdonot inheritfromQObject,since theyrequireneither signalsand slots, norautomatic memorymanagement.These classesinclude,for example, oneofthe most important classes, QString, which is responsible for strings. Thestrings in QStringare stored andprocessedbyQt in format, enablingtextinalmostall notation systemsinthe world to be used;thatis, notonlyWest European characters, butalsoCyrillic,Arabic,Hebrew, Chinese, andmanymore.For this reason,Qtcan be used veryefficientlyfor pro- gramsthatmustdealwithdifferent languages—provided that you useQString for manipulating textthatthe user maysee. TheclassesQImage (used for loading andsavingimages),QColor (which saves acolor), andmanyothers arealsonot inherited from QObject;theyallworkinavalue-based manner.

Figure 1.8: Implicit sharingusing two QString instances

Qt ensuresthatwhenthese classesare used,twoinstances never havethe same contents.For example, it would be wasteful to havedistinctcopies of thestring

40 1.4 Base ClassesinQt

“value”"Hello,world!",insteadofone copythat is shared amongall of that object’s clients. However,ittakesspecial management to avoidunnecessaryduplication. Figure 1.8displaysthe value-based object management procedureatworkonthe string "Hello,world!" usingthe followingcode example:

QStringstr1="Hello, world!"; QString str2 =str1; str2.replace("world","Qt");

As you can see, wefirstset up theQString instance str1.Thisautomaticallystores thestring’stext, "Hello,world!",inanunderlyingobject,towhich str1 is arefer- ence.Inthe second linethe instance str2 is is “assigned”the “value”ofstr1, but whatactuallyhappens,because stringsare value-based objects, is that str2 gets a referencetothe commonunderlyingobject.Inthe thirdstep wechangestr2, but beforethe str2 object implements thechange, Qt creates anewunderlyingdata object andsets str2 to nowrefertoit, so that str1 remainsthe same afterward, which is the“value-oriented”behaviorone expectsfromstringobjects. This all happens without theintervention of theprogrammer. Qt’suse of this copy-when-needed memorymanagement procedureallowsdataof this kind,for example, stringsorQImage objects, to be passedaround andshared without usingupmuchmemory.

1.4.3The Qt Inheritance Hierarchy

Figure 1.9showsasmallexcerptfromthe inheritancehierarchyof Qt. Notice that QLabel inherits notonlyallthe propertiesofaQObject andaQWidget, butalso thoseofQFrame. This is abaseclass for allwidgets that can haveavisual surroundingthem.

Figure 1.9: QObject QString QColor Notall Qt classesare inherited from QObject.

QWidget QLayout QThread

QGridLayout

QFrame QAbstractButton

QLabel QPushButton

41 1 Basics,Tools, and First Code

Thebaseclass QAbstractButtonisalsoinherited from QWidget. It serves as abase classfor allclassesthatdisplayabutton(that is,anelement that theusercan operatevia mouseclick).Apart from theQPushButtonclass that wehaveseen,this also includes QCheckBoxandQRadioButton. Thelayoutclassesare locatedinaseparatebranch, which doesnot lead back to QWidgetand for which QLayoutisthe base class. QGridLayoutinherits directly from this, 9 whereas QVBoxLayoutand QHBoxLayoutare derived from theQBoxLay- outclass. TheclassesQFrame, QAbstractButton, QLayout, andQBoxLayoutare used directly onlyin veryfewcases.Theymerelysummarizecommonpropertiesand functions of their“children” in abaseclass.The classesQString andQColor, on theother hand,havenobaseclass (except themselves). If you wanttoimplement yourownwidget, you willusuallydo so withaclass derived from QWidget.

1.5QtataGlance

In reality,Qt4is notjust one monolithicprogramming library,but rather seven libraries, supplemented byseveral utilityprograms. qmakeisone of theseutilities.

1.5.1The Qt Libraries

Today,the commonusage of theterm GUI toolkit conveysjustasmallpartofwhat such asystem offers. Qt in particular includes relativelyextensiveclassesfor various aspectsofapplicationdevelopment. Manyof theserelatetothe programming of graphical interfaces,but thereare also classesfor networkprogramming,OpenGL support, databaseand XMLdevelopment, andmanymore.The focusthroughout liesonthe platformindependenceofthe classes: With veryfewexceptions,the same classesare available on allsupported operating systems, withthe same func- tionsand thesamebehavior. Qt 4.0consists of thefollowingprogram libraries:

QtCorecontainsbaseclassesthatdonot generate anyGUI output.

QtGui contains thebaseGUI classes.

QtNetworkcontainsthe networkclasses.

QtOpenGL contains theOpenGLsupport.

9 Seehttp://doc.trolltech.com/4.2/hierarchy.html.

42 1.5 Qt at aGlance

QtSql contains theSQL databaseclasses. QtXml contains theXML andDOM classes(seepage 45). QtAssistantClient allowsyou to useQtassistantasadocumentationbrowserin yourapplication. Qt3SupportincludesclassesthatensurecompatibilitywithQt3.

Qt 4.1addedthe QtSvglibrary,which provides supportfor theSVG vectorgraphics format, as wellasthe QtTestlibrary,alsocalledQTestLib, which contains aframe- workfor writing unittests. Finallyin Qt 4.2, Trolltechaddedthe QtDBusmodule,which provides theQtbind- ings for themessage bussystem from Freedesktop.org.10 Youmayneed to linkanapplication’scode to several libraries, often including QtCoreand QtGui.For this reason,qmake uses both librariesbydefault. Thelibrariestobelinkedare specified bytheqmake variable QT.Bydefaultitcon- tainsthe valuescoreand gui. Forexample, to write aGUI program withnetwork support, you would add thevalue networktothisvariable.Thisisbrought aboutin the.profile withthe line

QT +=network

To write acommand-lineprogram withXML supportthatmerelylinks withQtCore andQtXml,and notwithQtGui,you mustadd andremovethe value gui. This is donewiththe followinglines:

QT -= gui QT +=xml

To useall of thelibrariesinQt4.0,write:

QT +=networkopengl sqlxml support

In addition to specifyingproject files,there is anothertopicfor which knowledge of thecontents of theQtlibrariesisofparticularinterest. Besidesthe headerfilesfor individualclass definitions,whose filenames matchthe namesofthe classesthey describe,Qtalsoprovides headerfilesfor itslibraries. Each of thesefilescontains theinterfacedescriptionsofall of theclassesofalibrary;the name of theheader filematches thenameofthe library.Thus,inthe examples so far,which haveonly used classesfromQtGui,insteadofthe manyseparate#includestatements,we couldsimplyhavewritten

10 Seehttp://www.freedesktop.org/wiki/Software/dbus.

43 1 Basics,Tools, and First Code

#include

However,these libraryheaderfilesare verylong,which considerablyincreasesthe length of thecompilingprocess.Thiswon’t be aproblemifthe compilersupports precompiledheaderfiles, butonlythemorerecentcompilers do (suchaswithGCC from version 3.4on).

TheBaseLibraryQtCore

QtCoreisapartiallibraryrequired byeveryQt program.Among otherthings, it makesavailable thefollowing:

Basic datatypes, such as QStringand QByteArray Basicdatastructures, such as QList, QVector, andQHash Input/output classessuchasQIODevice, QTextStream, andQFile Classeswithwhich multiplethreads can be programmed(includingQWaitCon- dition andQThread) TheclassesQObject andQCoreApplication(thebaseclass for QApplication)

None of theseclassesdepends on GUI components.Thisseparationfromthe GUI allowsQtapplications (suchascommand-lineprograms) to be written that do not implementaGUI In nongraphical programs theQCoreApplicationclass takesonthe role of theQAp- plicationclass in GUI applications:Itmakesaneventloop available.Thisisuseful if you requireasynchronouscommunication,whether between different threads or via networksockets. 11

TheGUI LibraryQtGui

TheQtGui librarycontains allclassesthatare necessaryfor programming graphical user interfaces,including thefollowing:

TheQWidgetclass andclassesderived from it,suchasQLabeland QPushButton Thelayoutclasses(includingQVBoxLayout, QHBoxLayout, andQGridLayout) ClassessuchasQMainWindowandQMenu, which areneeded if you wanttoadd menus to an application

11 QtCore doesnot containanynetworkclasses, butthe QtNetworklibrarycanbeusedwith QtCore if networking is required.

44 1.5 Qt at aGlance

Classesfor drawing, such as QPainter,QPen, andQBrush

Classesprovidingready-to-usedialogs (including QFileDialog andQPrintDialog)

TheQApplicationclass

QtGui requires theQtCorelibrary.

TheNetworkLibraryQtNetwork

Thepartial libraryQtNetworkprovides classesfor writing networkapplications. In addition to supporting simple socketcommunication via theQTcpSocketand QUdpSocketclasses, this libraryalso enablesclient-sideHTTP andFTP withQHttp andQFtp. UnlikeQtGui,QtNetworkrequiresthe QtCorelibrary,but it can,ofcourse, be used together withQtGui andthe otherlibraries.

TheOpenGLLibraryQtOpenGL

TheQtOpenGL libraryenablesOpenGLtobeusedinaQt program.Itprovides the QGLWidgetclass—aQtwidgetinwhich you can drawusingOpenGLcommands. QtOpenGL uses theQtCoreand QtGui libraries.

TheDatabaseLibraryQtSql

TheQtSql libraryclassesprovideaccess to SQLdatabases in Qt programs. This library includes classesthatare used to establishaconnectionwithanSQL databaseand to queryandmodifydata.Qtsupports arange of SQLdatabases,including the opensourcedatabases PostgreSQL,MySQL, andSQLite. QtSqlrequiresthe QtCorelibrary,and it is discussedatlengthinChapter 9(page 257).

TheXML LibraryQtXml

Asimple, nonvalidatingXML parser is provided bythepartial libraryQtXML.Itcan be addresseddirectlythroughaSAX2 interface(SimpleAPI forXML). QtXml also contains an implementation of theDOM standard( ). Thecorresponding classesallowyou to parse an XMLdocument,manipu- late itstreestructure,publishthe modifieddocument againasanXML document, or to create anewXMLdocument withDOM.

45 1 Basics,Tools, and First Code

This libraryrequires onlytheQtCorelibrary,and it is discussedinmoredepth in Chapter 13 (page 353).

TheCompatibilityLibraryQt3Support

Comparedwithits predecessor, Qt 3, Qt 4has undergoneconsiderable develop- ment:Someclassescontain changesthatare incompatible withthe Qt 3versions, andothershavebeen replaced in Qt 4withcompletelynewclasseswithdifferent names. In ordertosimplifytheporting of Qt 3programstoQt4,Trolltechincludes thecorresponding Qt 3classesinthe Qt3Supportlibrary.However,you should not usethislibraryfor newprograms, sincedevelopmentoftheir classeshas stopped. Since this book explains programming withQt4,wewillnot usethese classesand willnot discussthemfurther.

TheVectorGraphicsLibraryQtSvg

TheSVG vectorgraphicsformat, publishedbytheW3consortiumand basedon XML, hasgreat potential.FromQt4.1 onwardthe QtSvglibrarysupports theSVG profiles SVGBasic and SVGTiny, 12which can be used to displaySVGfilesand animations,althoughitcannotasyet create them or,asinXML,manipulatethem throughaDOM tree.

TheQtAssistantClientLibrary

Theassistantclient libraryallowsyou to remotelycontrolthe Qt assistantapplica- tion.Thisallowsyou to usethe assistantasaplatform-independenthelpbrowser for yourapplication. Theheart of themodule is theQAssistantClient class. Customized help pagesfor usewithQtassistantare provided in basicHTMLmarkup, alongwithanXML filethatdescribesthe structureofthe documentation.

TheTestCaseLibraryQTestLib

Originallyreleased outsidethe Qt core distribution for payingcustomers, QTestLib entered theregular Qt distribution starting withthe Qt 4.1.0release. Thelibrary contains facilitiestowrite properunittests for newlywritten classes, andcoversa scope similartoJUnit in .

12 Seehttp://www.w3.org/TR/SVGMobile/.

46 1.5 Qt at aGlance

TheQtDBusLibrary

DBus is amessaging protocol that hasemerged as adefactostandardonLinuxand otherUnixderivates.For instance,the LinuxHardwareAbstractionLayer (HAL)and theupcomingKDE 4are usingDBusfor interprocesscommunication.Even though ports for Windowsand MacOSXexist, Qt 4.2willoffertobuild this libraryonlyon Unix.Thismay,however,changeinfutureversions.

ActiveQt andMigration Classes

Theplatform-specificextension ActiveQt for Windowsmakesitpossible to imple- ment ActiveXcomponents withQtand to usetheminQtprograms. It is available onlyin thecommercialQtdesktop edition, however. Trolltechalsoprovides migrationsolutions for MFC-,Motif-, andXt-basedappli- cations. Like ActiveQt,however,theyareavailable onlyas separatecommercial add-ons for Qt 4, the Qt Solutions , 13 andwillnot be discussedinthisbook.

1.5.2Toolsand Utilities

In addition to allthis, Qt includes threeGUI programsthatcan be used to display Qt documentation, create dialogs accordingtothe WYSIWYGprinciple ( What You See Is What YouGet), or translateprogramsintoother languages. Thetoolkitalso provides aseriesofcommand-lineprogramsfor performing various tasks.

TheDocumentation Browser“Qt Assistant”

TheQtdocumentationconsistsofsimpleHTMLfilesthatcan be viewed withany web browserorwiththe Qt Assistant. Unlikeaweb browser, theAssistantdisplays an indexof theentireQtdocumentationand allowsfor afull-textsearchofthat documentation(Figure 1.10). TheAssistant’skeyworddirectoryis particularlyuseful whenworking withQtdaily. Forexample, if you requiredocumentationonaclass, on QLabel,simplyenter qlabel ✞ ☎ in theindexinputbox.With ✝ Enter ✆ you aretakenimmediatelyto theexcellent class documentation.

13 Seehttp://www.trolltech.com/products/solutions/.

47 1 Basics,Tools, and First Code

Figure 1.10: TheQtAssistant displays theQt documentation.

TheAssistantisanimportant complement to this book because it notonlydoc- umentsnewlyaddedAPI calls (oronesnot discussedinthisbook duetolack of space), butalsoprovides additionalusage examples. 14

TheGUI Editor “QtDesigner”

TheQtDesignerallowsyou to create applicationdialogs andthe main application windowin aWYSIWYG fashion(Figure 1.11),which is particularlyuseful when creating complexdialogs andlayouts.You can addwidgets in theDesignervia drag anddropand settheir properties. Forexample, you can change thetextofa QLabel or itscolor andtypeface. Youcan usethe Designer to combineseveral widgets in alayout, andimmediately seethe effect that thelayouthas on thewindow.You can even setupsignal/slot connections between thewidgets andthe Designer,ifnecessary. TheDesigner’spreviewmode allowsyou to checkthe GUIsyou’vecreated:You can seehowthelayouts react to changesinsize, testthe widgets,and seewhether the signal/slotconnections havethe desiredeffects. TheDesigneralsohas amode that specifies the tabsequence of thewidgets;that ✞ ☎ is,the orderinwhich theuseraccessesthe individualwidgets whenpressing ✝ tab ✆ repeatedly.Thisisanimportant aspect of auserinterface, andone that you should alwayscheck. Aprogram can onlybe operated intuitivelyfrom thekeyboardifthe

14 Forthose who prefer to read it in theweb browser, theQtclass documentationcan be found on theTrolltechwebsite at http://doc.trolltech.com/.

48 1.5 Qt at aGlance

tabsequencemakessense.Notethatifyou do notset thesequenceyourself, Qt sets it automatically,which maynotalwaysleadtodesirable results.

Figure 1.11: Thetoolsofthe Designer as multiple top-level windows

In ordertouse dialogs created withDesignerinanapplicationprogram,you will need aseparateconversion program.The Qt Designersaves thedescription of the draftversionsofaninterfaceinaseparateXML-formatted filewiththe filename extension .ui. To usethisinterfaceinaprogram,C++ code mustbecreated from theXML descriptionwiththe command-linetool uic(UserInterface ). If you useqmake to create aproject,uic can be used to integrate aDesigner-created interfaceveryeasily:Each .uifile to be used is added to theFORMSvariable byaline in the.profile.For example, thefollowinglineinthe .pro fileadds thedescription of thedialogfrommydialog.ui to theproject:

FORMS +=mydialog.ui qmakethencreates acorresponding rule that generates theC++ fileui_mydialog.h from mydialog.ui,using uic. Thelatter contains theinterfacedescription of the code that implements thedialog. (Weexplainthisfile andits useinthe rest of the code for theapplicationprogram in more detail in Chapter 3onpage 91.)

TheTranslation Tool “QtLinguist”

Qt Linguist is used to translateapplicationprogramsfromone language to another. As aseparateGUI tool it allowsyou to integrate language translatorsinthe work processofasoftwareproject more easily.LikeQtDesigner, Qt Linguist is used in conjunction withexternalcommand-lineprograms, namelylupdateand lrelease, to updatethe binariesofasoftwareproject andreplace thewords andphrases

49 1 Basics,Tools, and First Code

that aredisplayed to theuserwiththeir equivalents in adifferent language.lup- dateextractsthe texts to be localized from thesourcecode of theprogram and generates translationfilesaccordingtoadefinition given in theproject file:

TRANSLATIONS =application_fr.ts \ application_nl.ts

TheGUI applicationLinguistserves as agraphical utilitywhentranslating (i.e., edit- ing) thetranslation files generated in this way(Figure1.12).Finally,lreleasecreates additionalbinaryfiles containing thetranslationsthatthe application, on request, willload at startup, andhence appear translated.Byusinglupdate, Linguist,and lrelease together in this way,the applicationcode doesnot havetoberewritten andrecompiledinorder to produce arelease that supports anotherlanguage. In orderfor allthistowork, thesourcecode of theapplicationmustfollowcertain conventions. Specifically,strings representing textthatistobetranslated mustbe passedontothe functionsQObject::tr()orQApplication::translate(). This accom- plishestwothings.

It allowsQttochangestrings dynamically.Ifyou specifyonlythestring"Hello, world!" in thesourcecode,thenonlythis will be used whenthe application runs.But if you send thestringfirstthrough theQObject function tr() or to the translate()function of theQApplicationclass,thenthisfunction willlook up the translationand return astringcontainingit, which willbeusedinsteadofthe original "Hello,world!".

It allowsthe lupdateutilityto look for such function calls andthus identify passagesinthe source code that aretobetranslated.

Figure 1.12: TheQtLinguist enables applications to be translatedinto other languages.

50 1.5 Qt at aGlance

Unfortunately,the simple “Hello,world!” program from Section1.1 cannotbe translated into otherlanguages, sinceweusedneither tr() nortranslate()for the string "Hello,world!".Torectifythis shortcomingwereplace theline

QLabel label("Hello, world!"); with

QLabel label(QApplication::translate("MyLabel","Hello, world!"));

Thefunction QApplication::translate()takesasthe first argument acontextlabel for thetext, whereas theQObject::tr()function theclass name of thewidgetin question is automaticallyused as thecontextlabel. 15 If, for example, you callup tr() for aQLabelobject,Qtautomaticallyuses thecontextnameQLabel. This is possible because QLabel is derived from QObject as thebaseclass andtherefore inherits thetr()function. Thecontextlabel is important because thesametextmayappearinseveral places, withdifferent meanings.Ifthe target language uses distinct terms for thesevaria- tions, theappropriate translations for theinstances of original textwill depend on thecontext. Forexample, theEnglishtextOpenmayoccurinone dialog withthe meaning openfile,but in anotherdialogwiththe meaning openInternetconnec- tion;the German version of theprogram should render thefirstinstanceasÖffnen andthe second as Aufbauen. When thetwoinstances aregiven different context labels,QtLinguistcan distinguishthem. Youshouldsendall textinyourprogram throughthe tr() or translate()function. Youwillfind that theprogram can generallybe translated without greatdifficulty whenitisdoneduringcode development, andthatitisverytedious to go through theentiresourcetextofalargeprogram byhand andadd tr() or translate()calls once thecoding is finished. Theexampleprogramsinthe remainderofthisbook thereforewilluse tr() rightfromthe beginning. (You’ll finddetails on internationalizationand localizationinChapter 14.)

Creating theProject

As demonstrated in Section1.1.1,qmake creates platform-specificMakefiles from system-independent projectfiles. Therules stored in theMakefile, make,ornmake arethenusedtocompile andlinkthe application. nmake,however,onlyworks with thecommercialQtversion,not withthe opensourceedition. 16 TheGPL variant of

15 Seealsopage380. 16 TheEULAofVisualStudioisincompatible withthe GPLanywayonce thelibrariesfromVisual Studio areincluded.

51 1 Basics,Tools, and First Code

Qt 4for Windowsusesthe GCCportMinGW,which insteadofnmake provides the GNUmake knownfromLinux. qmakeproject files don’t requireustoworryabouteitherthe compilerorlinker options.For this reason wewilluse qmakefor allthe examples in this book. In Windowsyou can also create projectfilesfor withqmake, if you ownacommercialQtlicense.(TheQtintegration doesnot function in thefreevariant of Visual Studio Express; theseusers arealsodependent on the command-line–basedversion,qmake.)

Figure 1.13: Code::Blocksbuilds our“Hello,world!” program.

Foropensourcedevelopers in Windows, thedevelopmentenvironment Code:: Blocks17 provides ausefulalternative. It works together withthe MinGWincluded in Qt, andeven hasatemplate for Qt 4projects. In orderfor it to worktogether withqmake,however,you first havetostopitfromgeneratingthe Makefileitself. To do this,selectProject→ Propertiesand mark theoptionThisisacustom Makefile. Then look for thedialogunder Project → Build options andactivatethe Commands tab. Then,inthe Pre-build stepsfield, enter thefollowingcommands:

qmake -project qmake make.bat

17 Seehttp://www.codeblocks.org/.

52 1.5 Qt at aGlance

To ensure that qmake is calledeven whenother programs(such as theQtDesigner) add newfiles,selectthe optionAlwaysexecute, even if target is up-to-date. Figure 1.13 showsCode::Blocks after the“Hello,world!” program hasbeen compiled. It is useful to storethe menu itemsfor starting theDesigner, theAssistant, or make in theToolsmenu. Youcan also usethis, to acertain extent, to quicklystartyour ownprograms, which you can specifyin thesubitem Configure tools. ...

Figure 1.14: Apple’s IDE enables efficient projectmanagement, forwhich qmake generates the necessary files.

On MacOSX,the preferreddevelopmentenvironment is theXcode IDE. Apple provides this softwarefreeofchargesince OS Xversion 10.3 (Panther), butitneeds to be installedseparately.qmake on theMac convenientlycreates projectfilesfor Xcode insteadofMakefiles.However,ifyou prefer to avoidXcode andrelyon command linetoolsonly,simplyappend-spec macx-g++ to generate aMakefile: qmake-spec macx-g++

In contrast, qmake -spec macx-xcode willcause qmaketogenerateMac OS Xproject files for Xcode in allQteditions. qmakegenerates an Xcode projectfroma.pro file, which then turnsupinthe projectmanagement tool of Xcode. qmake -spec macx-g++ creates aMakefilefor direct usewithGCC. When creating yourapplicationunder Linux,the developmentenvironment KDe- velop18 is probablythebestchoice. Version3.4 provides supportfor Qt 4projects. KDevelop includes both,aprojecttemplateand agraphical management utilityfor qmakeproject files,which can be integrated seamlesslyinto theIDE.

18 Seehttp://www.kdevelop.org/.

53 1 Basics,Tools, and First Code

Upon first startup, KDevelop willpresent an almost blank main window.Choosing Project → NewProjectwillstart theproject wizardwhich guides throughthe initial stepsofcreatingaKDevelop-basedproject.TocreateaQt 4project that uses qmake, select C++ → QMake project → BasicQt4 applicationfromthe treeviewas showninFigure1.15. Forthe first to complete, thewizardalsoneedsaname for theapplication, as wellasadirectorylocation to storeall files in.

Figure 1.15: KDevelop provides a qmake -based project support.

In thenextstep,KDevelop asks for thenameofthe defaultauthor, an initialversion number,and thelicense for theproject.Alsoitisimportant that you specifythe full pathtothe Qt 4versionsofqmake anddesignerinthisstep.Thisensures that KDevelop willnot pick theQt3version byaccidentifbothare installedinparallel. Thenextstep allowsyou to pick asourcecode control. If you arenot usingsource code management system,justkeep theNonedefault.The remainingstepsallow for customizingthe templates that areinserted into allheaderand implementa- tion files.These usuallycontainthe license as wellasthe author’s name.Figure 1.16 showsamainwindowwiththe examplemain.cppfile after thewizardhas completed.

54 1.5 Qt at aGlance

✞ ☎ Pressing ✝ Shift+F9 ✆ willbuild andexecuteagiven project; thetextual output is visible in theMessagesand Applicationtab on thebottom, which willautomaticallyopen during build andexecutionphases, respectively.

Figure 1.16: TheKDevelop main window afterthe setup

Theqmake-project manager(QMakeManager)ishiddenbehindatabonthe right bearingthe Qt symbol.Upon expansion,you can useittographicallyadd,re- move, or openfiles. Theproject managerviewseparates between different types of sources: KDevelop opens anewtabfor commonsourcefiles, while ui files are automaticallylaunchedwiththe Qt Designer.

Figure 1.17: TheKDE editor Kate canquickly be convertedtoa powerfulsourcecode editor.

Agood alternativefor thosewho dislikeafullblownIDE is theKateeditor(KDE Advanced Text Editor), which includes apull-downmenufromwhich you can run

55 1 Basics,Tools, and First Code

thecompilerdirectly,asshowninFigure1.17. Dependingonyourdistribution,you maywanttoinstall thekate-pluginspackage first which—among others—provides apluginfor code completion.Equippedthisway,Kategives an overviewof meth- ods andmembervariablesinCandC++ files,and even allowsfor code snippet administrationunder Settings → Configure Kate→ Application → Plugins. Kate’ssetupdialogprovides asubitem,ExternalTools, in which you can storeyour owncommands, as withCode::Blocks,which then appearunder Tools → External Tools. Settings → Configure Shortcutslists keyboardshortcuts.

TheMeta-object Compiler moc

Thesignal/slot conceptinQtisnot pure C++, butratheranextension of theC++ standard. Forthisreasonthe command-lineprogram moc(meta objectcompiler ) is used to convertthe signal andslotconstructsintostandardC++. mocgener- ates additionalC++ code for each classderived from QObject.Thisensures that signal/slotconnections can be dynamicallygenerated at runtime. It also allowsthe namesofclassesthathaveQObject as abaseclass to be dynamicallydeterminedat runtime, andeven to determine whether aclass is abaseclass for anotherclass. 19 Qt also includes a propertysystem,for which mocgenerates thenecessarycode. Propertiesare specialcharacteristics of aclass that can be queriedand set. For example, theQLabelclass hasatextproperty,whose value is astringcontaining thetextthatthe labeldisplays. Foreach propertythereare twofunctions: onethat reveals itscurrent value,alsocalledthe getmethod ,and onethatchanges it,also knownasthe set method.Inthe caseofQLabel, text()isthe getmethod returning thelabel’s text, andsetText()isthe setmethod providingthe labelwithanewtext. Thesetwofunctions, marked in theclass definitionasproperties, allowthetext to be queriedvia QObject::property() andbeset withQObject::setProperty(). Both requireastring,namely,the name of theproperty,asanargument.The Property Editor of theDesignerdeterminesthe value of propertiesatruntimeand allows thesepropertiestobechanged.Ifyou requirepropertiesinaseparateclass,these mustbederived from QObject. In short, mocisneeded whenever aclass uses QObject as abaseclass.The meta- object compilermustpreprocesseveryfilethatimplementsthe definitionofsuch aclass beforethe C++compilerisrun. In each casethiscreates afile beginning withthe prefixmoc_. Forexample, if you write yourowndialogclass MyDialog withQObject as abase class, withthe classdefinition in thefile MyDialog.h andthe actualimplementation

19 By baseclass wemean notjustthe classfromwhich aclass is directlyderived,but all classesin theinheritancesequencebeginning from aroot of theclass hierarchy.Thus QVBoxLayouthas threebaseclasses(QBoxLayout, QLayout, andQObject), becauseQLayoutinherits from QObject, QBoxLayoutfromQLayout, andQVBoxLayoutfromQBoxLayout.

56 1.5 Qt at aGlance

in MyDialog.cpp, themeta-object compilerhas to processMyDialog.h,thengener- atethe filemoc_MyDialog.cpp, andintegrate thegenerated fileintothe complete project. qmakedoesall of this automatically.

TheQtResources Compiler rcc

Almost everyprogram uses externalresources such as imagesorgraphics. These resourcescan either lie in separatefilesorbeembeddeddirectlyin theexecutable files to be generated.Qt4uses theresources compiler rcc for generated files. Theresourcecompilerobtains itsinformation from resource descriptionfiles, the namesofwhich endinthe extension .qrc.A.qrc filespecifiesfilesystem paths to resourcesusedbytheprogram,beginning from thedirectoryin which the.qrcfile resides. If you includethe resource fileinthe qmakeproject,Qtautomaticallygenerates arraysencodedinhexadecimalform, in which it stores thecontents of theresource files.The Qt resource system ensuresthatthe applicationprogram can accessthe resourcesencodedinthiswayusingthe olddirectoryandfile names. A.qrcfile describes(usingXML)which files areneeded bythefinished program.Itlooks somethinglikethis:

pics/symbols/stop.png pics/symbols/start.png pics/symbols/pause.png

Thepathdetails arealwaysunderstood as relativetothe directoryin which the resource fileislocated.For this exampleweassume that theaboveresourcefile is calledsymbols.qrcand that thedirectorypics/symbols, which contains therequired images, is beneaththe directorywiththe source code (including theresourcefile). In orderfor qmaketobeable to take theinformation from theresourcefile into account,acorresponding RESOURCESdirectivemustbeaddedtothe projectfile:

RESOURCES =symbols.qrc

Theimage filestop.pngunder pics/symbolscan nowbe referenced in theapplica- tion code as follows: myLabel->setPixmap(QPixmap(":/pics/symbols/stop.png"));

That is,inorder to refertoaresource, you need onlyplaceacoloninfront of the pathdetails specified in the.qrcfile.(Theleading slashisnot atypo; relativepaths

57 1 Basics,Tools, and First Code

in thefile system arespecifiedinthe logical pathnotationasabsolutepaths,with thecode directoryas theroot.) If theresourcedescription fileisproperlyintegrated into theproject,acalltoQPixmap()can correctlyresolvethe path, andthe label willdisplayastopicon. Afile in aresourcecan also be addressedwithalogical pathcompletelydifferent from itsactualfile system path, as shownhere:

pics/symbols/stop.png ...

Theprefixattribute for theqresource tagspecifiesaprefixto be used beforethe pathdetails,whereas thealiasattribute specifies an alternativenameorpaththat can be used insteadofthe actualpathdetails.Withthe combinationshownabove, thestopiconcan nowalso be addressedinthe applicationcode as follows:

myLabel->setPixmap(QPixmap(":/player/stop.png"));

With thehelpofthe lang attribute andalternativeqresource entries, thesystem can load othergraphicsdepending on thecurrent language setting:

pics/symbols/stop.png ... pics/symbols/de/stop.png ...

Beginning withversion 4.1, Qt DesignerincludesaResource Editor (see page 99). Unfortunately,inQt4.1.0this doesnot displaytherelativepaths to theindividual resources, norwillithandlethe aliasattribute. Therefore, you should alwayscheck theresourcedescription filethatisgenerated.

1.5.3Examplesand Demos

Acomplete Qt installation contains aseriesofexampleprogramsinthe examples directoryandseveral demo programsinthe demo folder.

58 1.6 HowtoUse the Documentation

Theexampleprogramsare of particular help if you haveproblems usingspecific Qt classes, whereas thedemoprogramsmainlydemonstrateall thethingsthatQtcan do andare notappropriate as areference for howto usethe classlibraries.

1.6How to Usethe Documentation

TheHTMLdocumentationincludedbyTrolltechisrecommended as aconstantcom- panioninQtprogramming,especiallybecause it describesall Qt classesindetail. Also,you mayfinditusefulwhenreading this book to look up thedocumentation for theclassesusedinthe various examples. When Qt Assistantstarts, theprogram automaticallyloads thestart page doc// index.html(Figure 1.10on page 48);itcan also be viewed in anyweb browser, and is available online.20 In addition to theprecise documentationofthe Qt classes alreadymentioned, thedocumentationincludesintroductorytexts;overviewsof thesignal/slot concept, layouts,and theSQL,network, XML, andOpenGLmodules; anddetaileddescriptionsofthe toolsand utilities.

Figure 1.18: Theclass documentationofthe QWidget class

Theclass documentationiswhatismostfrequentlyused in day-to-dayworkwith Qt. If you knowtheclass names, you can enter them in theIndextabofthe Assistant. From thestart page you can also accessalistofall classesand alist groupedaccordingtotopics.

20 ForQt4.1,see http://doc.trolltech.com/4.1/.

59 1 Basics,Tools, and First Code

Thedocumentationofeach classbeginswithashortdescription of whatthe class does, followed bythenameofthe headerfile that needstobeintegrated in order to useit. ThekeywordInherits reveals from which direct base classesthe classis derived,Inherited bylists theclassesthatinheritfromthisone (Figure1.18). This information is followed byalistofthe functionsofthe classwhich is divided into several categories. Thesecategoriesinclude theget andset methods for prop- erties of theclass,public andprotected functions, signals, andslots. Onlythefunctionsthatare defined in theclass itself appearinthislist; thedoc- umentationdoesnot discussmethods that theclass obtainsthrough inheritance from base classes. Remember that if you arelookingfor aspecific function and do notfind it in thelist—it maybe documented in abaseclass.Alternatively, thelinkListofall members, includinginherited membersatthe beginning of the classdocumentation, leads to alistofall functionsofthe class, includinginherited functions. Thefunction listisfollowed byadetaileddescription of theclass.Inaddition to a descriptionofthe tasksthatthe classcarries out, it also explains some typical ways in which theclass is used.

60 r te ap 2 Ch

TheToolsNeeded to CreateDialogs

Nowthat you haveanoverviewof Qt, wewillturntoamore practical exampleto seehowtheclassesworktogether.Our first extensiveprogram willconvertnum- bers between decimal, hexadecimal, andbinarynotation;it’sshowninFigure2.1.

Figure 2.1: Ourexample program converts numbers between decimal, hexadecimal, and binary notation.

61 2 TheToolsNeeded to CreateDialogs

Theuserofthisprogram can enter anyone-byte number (from0to 255) in anyof thethree inputfields.The program updates theother two Line-Edits inputfields withthe converted value.

2.1What’sthe DifferenceBetween Dialogsand Widgets?

Theprogram’s main() function is almost identical to themain() function of the “Hello,world!” program discussedinSection 1.1:

// byteConverter/main.cpp

#include #include "ByteConverterDialog.h"

intmain(intargc, char * argv[]) { QApplication a(argc, argv);

ByteConverterDialog bc; bc.setAttribute(Qt::WA_QuitOnClose); bc.show();

returna.exec(); }

Thereisjustone exception: Theclass QLabel hasbeen replaced byByteConver- terDialog.Thisclass inherits from QDialog,and itsclass definitionisplacedin theheaderfile ByteConverterDialog.h. 1 The#includedirectivethatintegrates this headerfile into theapplicationcode uses quotationmarks (”)insteadofangle brackets (<>),since thefile is in thesamedirectoryas main.cpp. We’vealsoaddedthe WA_QuitOnClose attribute to thedialogtoensurethatthe program ends whenthe dialog is closed.Thiswas notnecessaryin theprevious examples,because wedid notuse anyclassesinherited from QDialog as themain window.Since dialogs usuallyonlyprovidein-between information,the attribute is notactivebydefaultfor QDialog.After all, closingadialogshouldn’t terminate theapplicationunlessthere’s aserious bug. We surround thecontents of thefile ByteConverterDialog.h with includeguards , consisting of thethree preprocessorinstructions#ifndef label ,#define label and #endif:

1 Forheader files that wecreateourselves,weuse theC/C++standardfile extension.h, to make thefile type clear.

62 2.1 What’s theDifferenceBetween Dialogsand Widgets?

// byteConverter/ByteConverterDialog.h

#ifndef BYTECONVERTERDIALOG_H #define BYTECONVERTERDIALOG_H

#include class QLineEdit; class ByteConverterDialog :public QDialog { Q_OBJECT public: ByteConverterDialog(); private: QLineEdit * decEdit; QLineEdit * hexEdit; QLineEdit * binEdit; } ;

#endif

Using includeguardsisastandardtechniqueinC/C++programming to avoidprob- lems that occurifmorethanone source filetries to #include aheaderfile,which can happeninlarge programs withmanyindependentlydevelopedmodules. Here, thefirsttimeByteConverterDialog.h is processed, thekeywordBYTECONVERTER- DIALOG_Hisdefined. If alater source fileattempts to #include ByteConverterDia- log.hagain, the#ifndef ...endif (“if notdefined”)directivecausesthe preprocessor to skip theheaderfile’s contents.Without theinclude guards,the compilerwould notice that thekeywords andclassesare beingmultiplydefined andsignalanerror. We includethe headerfile QDialog,since theByteConverterDialog classinherits from QDialog.Inorder for thefunctionsofQDialog to be available outsidethe ByteConverterDialog classweuse theaccess controlpublic. Theclass declaration classQLineEdit; is a forwarddeclaration.Objectsofthe ByteConverterDialog classcontain threeprivatevariablesthatpoint to QLineEdit objects, andsothe C++compilerneedstoknowthat QLineEditisaclassinorder to processthe ByteConverterDialog declaration, butitdoesnot need to knowthe exact classdefinition at that point.2 TheQ_OBJECT macromustbeusedinall derivations from theQObject base class, includingindirectones, because it defines functionswithout which thesignal/slot conceptcannotwork. (MoreonthisinSection 2.1.1.)

2 Alternatively,you couldinclude theQLineEdit header filebeforethe declarationofByteCon- verterDialog, butthenthe parser would need to read this,which would slowdowncompiling considerably,especiallyon slower machines.For this reason,wetry,inthisbook,tooptimize theheader files so that onlythenecessaryones areincluded.

63 2 TheToolsNeeded to CreateDialogs

Theconstructor is theonlypublic function of theclass.Wewillstore pointersto theQLineEditobjectsdisplayed bythebyte converter widgetinthe threemem- bervariables(decEdit,hexEdit, andbinEdit) because wewishtoupdatethe input fieldsinwhich theuserdoesnot enter dataimmediatelyto ensure that allthree lineedits displaythesametext. Because this is an implementation detail of our ByteConverterDialog class, wedeclare them as privatevariables.

2.1.1Inheriting from QObject

As mentioned previously,you mustalwaysuse theQ_OBJECT macrowhenaclass inherits,directlyor indirectly,fromQObject. 3 This macrodefinesseveral functions that implementthe signal/slotconcept.Unfortunately,ifthe macroismissing in thedefinition of aclass that inherits from QObject,neither thecompilernor the linkerwillreportanerror.Instead, thesignals andslots of theclass willremain unknowntoQt, andatruntimethe corresponding connections willnot work. Applications compiledwithdebugging information willwarnatruntime(in ater- minalwindow)thatasignal or slot doesnot existwhenever code is executed that triestoaccess an unknownsignalorslot. Theerror message is:

Object::connect:Nosuch slotQObject::decChanged(QString)

However,thiserror message is abit non-specific. Youwillalsosee it if you have written thenameofthe signal or slot incorrectlyor if theargument listisincorrect. Everyfilethatusesthe Q_OBJECT macromustbesubmitted to thecommand-line program moc(seepage 56). This tool automaticallygenerates thecode converted into pure C++code bythesignal/slot concept.4 If you useqmake to create yourproject,the qmaketool searches allheaderand source textfilesnamed in the.profile for theQ_OBJECT macro. When it finds one, qmakeautomaticallygenerates thenecessarybuild instructions for mocbased on thecontents of thosefiles.5 Forthistoworkyou must, of course,specifytheproject’s headerfilesinthe .pro file. To do so,use theqmake variable HEADERS,asyou would theSOURCES variable for source textfiles:

3 Some issueerrorsifthe Q_OBJECT macroisterminatedwithasemicolon, which is why,for reasonsofportability,werecommend that you alwaysomitit. 4 mocdoesnot modifyyourfiles; it provides thenewcode in separate files which you haveto take care of whenwriting yourMakefilesbyhand.Ifyou useqmake as werecommend,you don’t havetocare. 5 qmakewill notautomaticallynotice if theQ_OBJECT macroisinsertedintoafilelater on.

64 2.1 What’s theDifferenceBetween Dialogsand Widgets?

#byteConverter/byteConverter.pro

TEMPLATE =app

SOURCES =main.cpp \ ByteConverterDialog.cpp HEADERS =ByteConverterDialog.h

If mocisnot invoked for files containing theQ_OBJECT macros,the linkercom- plains of undefinedsymbols, andGCC issues this errormessage: ld: Undefined symbols: vtable forByteConverterDialog ByteConverterDialog::staticMetaObject

If you seethiserror message,check thefollowing:

Havethe qmakevariable HEADERS been properlydefined?

Is theproblemresolved if theMakefiles areregenerated withqmake?

2.1.2MoreComplex Layouts

We nowturn to theimplementationofthe ByteConverterDialog class. When cre- atinginstances of this class, theconstructor function generates allthe QLineEdit widgets displayed bythenewByteConverterDialog object andinserts them into a layout. However,thisisnolongerassimpleasbefore: In orderfor theapplication to behaveinanintuitivemannerwhenthe user changesthe sizeofthe dialog, we need to usenested layouts.Figure2.2 showshowQt ensuresthatthe inputfields alwaysappear at thetop of thewindowandthatthe Quit buttonalwaysappears at thelower rightcornerofthe window.

Figure 2.2: HowQtlayouts react to asize change in thedialog

Butdon’t panic:Even though thesourcecode for theconstructor becomesquite long,itusesonlysimple functions:

65 2 TheToolsNeeded to CreateDialogs

// byteConverter/ByteConverterDialog.cpp

#include "ByteConverterDialog.h" #include #include #include #include #include #include

ByteConverterDialog::ByteConverterDialog() { // Generatethe necessary layouts QVBoxLayout* mainLayout =newQVBoxLayout(this); QGridLayout* editLayout =newQGridLayout; QHBoxLayout* buttonLayout =newQHBoxLayout;

mainLayout->addLayout(editLayout); mainLayout->addStretch(); mainLayout->addLayout(buttonLayout);

// Generatethe labelsand line-edits and add them // tothe objectpointed atbyeditLayout QLabel* decLabel =newQLabel(tr("Decimal")); QLabel* hexLabel =newQLabel(tr("Hex")); QLabel* binLabel =newQLabel(tr("Binary")); decEdit=newQLineEdit; hexEdit=newQLineEdit; binEdit=newQLineEdit;

editLayout->addWidget(decLabel, 0,0); editLayout->addWidget(decEdit,0,1); editLayout->addWidget(hexLabel, 1, 0); editLayout->addWidget(hexEdit,1,1); editLayout->addWidget(binLabel, 2,0); editLayout->addWidget(binEdit,2,1);

// Createthe Quitbutton and add ittothe objectpointed // atbybuttonLayout QPushButton* exitButton =newQPushButton(tr("Quit"));

buttonLayout->addStretch(); buttonLayout->addWidget(exitButton); ...

Figure 2.3showswhich layouts areinvolved withwhich widgets.Keep an eyeonit whenwenowwalkthrough thecode above. ThemainLayoutobject,avertical boxlayout, is responsible for thelayoutofthe entire dialog. Therefore, wepassapointer to theByteConverterDialog object when wecallits constructor. To do this weuse thethispointer,since weare in afunction of theByteConverterDialog classitself.

66 2.1 What’s theDifferenceBetween Dialogsand Widgets?

Figure 2.3: Thelayouts as used by ByteConverterDialog

TheeditLayoutobject is responsible for thelayoutofthe labels andline-edit widgets. In ordertobeable to stack theseelementsneatly,and to organizethe widgets in a single column, weuse agridlayout. ThebuttonLayout, which wecreatewiththe thirdnewcall, will be responsible for managing theQuitbutton. However,beforewecan generate widgets likethis buttonand add them to editLayoutand buttonLayout, wemustadd thosetwo layouts to themainLayoutusing addLayout(), which is thelayoutequivalentof addWidget(). If you add widgets to alayoutnot yet associatedwithawidget, you willreceivethisruntimeerror in aterminalwindow:

QLayout::addChildWidget:add layout toparentbeforeadding children to layout. andthe widgets willremaininvisible. Therefore, you should alwaysgeneratethe basiclayoutfor yourclass first,thencontinue withthe nextlayout“layer,” andso on. To ensure that inputfields arealwaysplacedatthe topofthe ByteConverterDialog andthatthe Quit buttonisalwayspositionedatits lower right, weuse stretches .

Figure 2.4: Thedialogafter a change in size when stretches arenot used

Stretchesoccupythespacenot required bythewidgets andthus create empty spacesinyourdialog. If you weretoomitstretchesinour example, thewidgets

67 2 TheToolsNeeded to CreateDialogs

would occupytheentirespace. Were theusertoenlarge such adialog, without stretch, he would seesomethinglikeFigure2.4. To avoidthisbehavior, weadd astretchbetween theeditLayoutand thebutton- Layoutwiththe addStretch() function. Nowwecan generate thelabelsand lineedits andentrust them to theeditLayout. We savethe lineeditobjectsinthe privateclass variablesdecEdit, hexEdit, and binEdit, because wewanttochangetheir contents throughcode stored in other functions. Forall otherobjects, wecan manage without corresponding pointers because wedonot need to accessthemoutside theconstructor. To ensure that theQuitbuttonisalwaysdisplayed at thefar bottomright of the dialog, wefirstfill thehorizontallayoutbuttonLayoutwithastretchbeforewe adjustthe buttonitself. Byaddingall thewidgets andsublayouts to themainLayoutobject or itschildren usingQObject::addWidget()and QObject::addLayout(), weensurethatall objects generated bytheconstructor withnewareinherited from theByteConverterDialog object.Since theynowformaheap-allocatedobject hierarchythat Qt’smemory management willhandlefor us,wedonot need to delete anyof them manually. When theByteConverterDialog object is deleted,all itschildrendisappearauto- matically. Areyou becoming slightlydisillusioned because of thenot insignificantamount of code that wehad to write just to create areallysimple dialog? Help is on theway in Chapter 3, which explains howadialogcan be created usingthe Qt designer, andcode automaticallygenerated.Moredetails andbackground information on layouts is provided in Chapter 5.

2.1.3Increasing Usability

Despitethe improved layout, thedialogdoesnot yet behaveideallyin certainre- spects:

Thewindowtitleatthe moment showsthe program name byteConverter.Some- thingmoredescriptivemight be better.

TheQuitbuttonshouldbecomethe default button of thedialog. Thedefault ✞ ☎ buttonisactivated by ✝ Enter ✆ even if it currentlydoesnot havekeyboardfocus. Most widgetstyleshighlight thedefault buttoninaparticular way.

Currentlyyou can enter anynumbersinthe line-edit widgets.Weshouldrestrict this to valid values, that is,onlywhole decimalnumbersbetween 0and255, hexadecimalnumberswithamaximum of twodigits, andbinarynumberswith amaximum of eightbits.

68 2.1 What’s theDifferenceBetween Dialogsand Widgets?

We can solvethese threeproblems byaddingthe followinglines to theconstruc- tor:6

// byteConverter/ByteConverterDialog.cpp (continued)

... exitButton->setDefault(true);

// Limitinput tovalid values QIntValidator * decValidator= newQIntValidator(0,255, decEdit); decEdit->setValidator(decValidator);

QRegExpValidator * hexValidator= newQRegExpValidator(QRegExp("[0-9A-Fa-f] { 1,2 } "),hexEdit); hexEdit->setValidator(hexValidator);

QRegExpValidator * binValidator= newQRegExpValidator(QRegExp("[01] { 1,8} "),binEdit); binEdit->setValidator(binValidator);

setWindowTitle(tr("ByteConverter"));

...

Setting theWindowTitle

Thefirsttwoproblems areeach solved withasingle lineofadditional code.Tosolve thefirstproblem, weuse thefunction setWindowTitle(), which sets thewindowtitle of awidgetifthe widgetoccupies atop-level window.Thisfunction is amethod of theQWidgetclass.Since ByteConverterDialog hasQWidgetasits base class, it inherits this function,and wecan simplycallitup.

Specifyingthe DefaultButton

Thedefault buttonfor adialogisspecifiedbyinforming thebutton(rather than the dialog, as you mightexpect)thatitisindeed thedefault button. (Note, however, that callingsetDefault(true) on aQPushButtonobject onlyhasaneffect if the buttonisusedinadialog—in amainwindowthereare no defaultbuttons.Ifyou tryto defineadefaultbuttonfor amainwindow,Qtwillmake it look likeone,but ✞ ☎ it doesn’t activateitwhenthe user pressesthe ✝ Enter ✆ key.)

6 To compile theresulting code,pleasealsoadd themissing #include lines (omittedabovefor the sake of clarity)for theclassesusedfor thefirsttimehere, QIntValidator andQRegExpValidator!

69 2 TheToolsNeeded to CreateDialogs

Checking User Input

Thethird problem, restrictingthe inputinthe line-edit widgets to valid values, re- quires somewhatmorework, butcan be resolved through validators .These inherit from QValidator as thebaseclass.Avalidator is associatedtoaparentobject that receives inputand informs that object whether or notitshouldaccept thecurrent inputvalue. To checkthe validityof thedecimal number,weuse aQIntValidator object.Itis created byinvokingthe constructorand passing to it,asthe first andsecondargu- ments, theminimum andmaximum inputvaluesallowed.The thirdargument,here decEdit,isapointer to theline-edit object that wewanttomake theparentobject of thevalidator.Thisinvocation, besidesbinding thevalidator to theinput widget, also makesitsubject to automaticmemorymanagement,sothatthe validator will be deallocatedwhenthe widgetis. ThesetValidator() callthencausesthe validator to keep an eyeonthe inputgiven to theobject pointed to bydecEdit.Nowthe user can type onlywhole numbersbetween 0and255 in theinput field. To checkthe validityof hexadecimalnumbers, wemustmake useofanother type of validator:QRegExpValidator.Thiscompares theinput,viewed as astring, against a regularexpression.Inour case, theregular expression is [0-9A-Fa-f]{1,2}. Thefirst subexpression in square brackets specifies thecharacterspermitted in theinput string:the digits 0to 9and thelettersAto F(written either in upperorlower case).The followingsubexpression,{1,2}, restrictsthe length of theinput string to at leastone,and at most two, characters. RegularexpressionsinQtare related to thosefromPerl, butthere aresomesig- nificantdifferences.For example, it is necessaryto escapeabackslash (\)ina Perl-style regularexpression withanother backslash to getthe corresponding Qt- style expression,because asinglebackslashalreadyactsasanescape character in C/C++. QRegExpthenrecognizes thedouble backslash as asimplebackslash. It followsfromthisthatweneed to type in fourbackslashesifwewanttospecifya literal backslash withinaQt-style regularexpression. We also useaQRegExpValidator withthe regularexpression [01]{1,8} as avalidator forthe inputfieldfor binarynumbers. This expression allowsonlythecharacters 0and1inthe inputstring, butthe string can be anywhere from onetoeight characters in length.

2.1.4ImplementingSlots

Finally,weneed to implementthe functional connections that make theQuitbut- tonworkasexpected andsynchronizethe threeinput fieldswithone another. To ensure that clicking theQuitbuttonwillclose thebyte-converter dialog, we extendthe ByteConverterDialog constructor to associate theclicked()signalofthe

70 2.1 What’s theDifferenceBetween Dialogsand Widgets?

buttonwiththe accept()slotofthe dialog. Theslotisprovided byQDialog,which theByteConverterDialog classinherits from:

// byteConverter/ByteConverterDialog.cpp (continued)

... connect(exitButton, SIGNAL(clicked()), this,SLOT(accept())); ...

Theaccept() method, wheninvoked,simplycloses thedialog. Ouruse of accept() here followsageneralconvention:Alargenumber of dialogs haveanOkand a Cancel buttonatthe bottom; Ok corresponds to theaccept() slot,Canceltothe reject() slot.Bothslots closethe dialog, thefirstexitingwithapositivereturn value,the second withanegativeone (see Chapter 6, page 161).Inthisexample weonlyhaveone buttonand thereforeare notinterested in thereturn value,just theaction. However,the real event-processing logicofour byte converter applicationconsists of augmenting thecustomarysignalsand slotswithseveral custom-built connec- tions, specifictothe functionalityof ourByteConverterDialog class. Thesesig- nal/slot connections should come into actionwhenanyoneofthe QLineEditob- jectssends outthe signal textChanged(), indicating that thetextinthatobject’s inputfieldhas changed. Forthispurpose, weexpandour classdefinition as fol- lows:

// byteConverter/ByteConverterDialog.h(continued) class ByteConverterDialog :public QDialog { ... privateslots: void decChanged(const QString&); void hexChanged(const QString&); void binChanged(const QString&); } ;

Slots aredeclaredinthe same wayas normal functions, except that foraccess controlweuse thedesignators public slots:,protected slots:,and privateslots:, insteadofthe usualpublic:, protected:, andprivate: protection modes. Each of ourthree slotsacceptsanargument of thetype constQString&. In this waythetextChanged() signal of thefunction can passthe newtextofthe lineedit. As theargument type for thesignals/slots to be,wedonot choosesimplyQString, butareferencetoaconst QString. Thereare tworeasons for this.First,byus- ingcall-by-reference rather than call-by-value,the QStringobject containing the

71 2 TheToolsNeeded to CreateDialogs

updated inputtobepassedtothe signals/slotswillnot be copied whenthe sig- nals andslots areinvoked,and thecode becomesmoreefficient. However,use of call-by-reference allowsthe function to modifytheactualparameter,which the signalsand slotsshouldnot do, so theparameter is also declared to be areference to constdata. This second step is arecommended “defensiveprogramming”prac- tice whenever afunction should notchangeanactualparameter that is passedby reference. Even though thedeclaration of aslotdiffers slightlyfrom that of otherfunctions, it is still an ordinaryfunction,which is implemented andcan be calledinthe usual way.Hereisthe definitionofthe decChanged() slot in thefile ByteConverterDia- log.cpp:

// byteConverter/ByteConverterDialog.cpp (continued)

void ByteConverterDialog::decChanged(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok); if (ok) { hexEdit->setText(QString::number(num, 16)); binEdit->setText(QString::number(num, 2)); } else { hexEdit->setText(""); binEdit->setText(""); } }

Thefunction receives thenewstring displayed bythedecimal line-edit widgetas theactualvalue for itsnewValueparameter,and it updates thestrings displayed bythehexadecimaland binaryline-edit widgets.First,weneed to determine the numericvalue that corresponds to theinput string.Asanobject of theQString class, newValueknowsseveral functionsthatconvertstrings to numbers. We will usethe toInt()function,asthe inputisastring representing an integer value. toInt()acceptsabool pointer as an optional argument:Ifthisargument is specified, thefunction sets thevariable to which it pointstotrueifthe string is successfully converted to anumericvalue,and to false if theconversion fails,thatis, if the string doesnot representaninteger value. If theconversion is successful,weset thetexts displayed bythetwoother line edits(hexEditand binEdit) to thehexadecimaland binaryequivalents of thenew value.Todothis, weconvertthe number to astringthatrepresentsthe newvalue in hexadecimalformand to astringthatrepresentsthe newvalue in binaryform. Forthispurposethe QStringclass hasthe static function number(), which returns therepresentationofanumber as astring. Thenumber itself is itsfirstargument. As asecondargument,number() expectsthe base for thenumber system used,in ourcase16for hexadecimaland 2for binary.The second argument is optional,

72 2.1 What’s theDifferenceBetween Dialogsand Widgets?

andifitisnot specified,number() assumesbase10(the decimalsystem), which is themostcommoncase. If thetoInt()function couldnot convertthe string that was entered in thedecimal line-edit widgetintoanumber,wewrite an emptytexttothe othertwoline-edit widgets,withsetText(). Thanks to thevalidator weusedfor thedecEditobject, which ensuresthatonlynumbersinthe range0to 255 can be entered,the con- version willonlyfailinone single case: if theuserdeletes theinput completely. We implementthe tworemaining slotsinthe same way:

// byteConverter/ByteConverterDialog.cpp (continued) void ByteConverterDialog::hexChanged(const QString&newValue) { ... if (ok) { decEdit->setText(QString::number(num)); binEdit->setText(QString::number(num, 2)); } else { ... } } void ByteConverterDialog::binChanged(const QString&newValue) { ... if (ok) { decEdit->setText(QString::number(num)); hexEdit->setText(QString::number(num, 16)); } else { ... } }

In thesefunctions, whentransforming thestringtoaninteger value,wespecify thebaseinanoptionalsecondargument to toInt(); likeQString::number(), toInt() uses base 10bydefaultifthisargument is omitted. In orderfor theseparts of ourapplicationtoworktogether according to ourdesign, wemustconnect thetextChanged() signalsofeach of ourQLineEditobjectswith thecorresponding slots. To do this,weextendthe constructorfor thelasttime:

// byteConverter/ByteConverterDialog.cpp(continued)

... connect(decEdit,SIGNAL(textChanged(const QString&)), this,SLOT(decChanged(const QString&))); connect(hexEdit,SIGNAL(textChanged(const QString&)), this,SLOT(hexChanged(const QString&)));

73 2 TheToolsNeeded to CreateDialogs

connect(binEdit,SIGNAL(textChanged(const QString&)), this,SLOT(binChanged(const QString&))); }

Thecode for theconstructor of ourByteConverterDialog classisnowcomplete, andperforms threedifferent tasks:

It generates allthe widgets of adialog, incorporates them into theappropriate layouts,and sets up theobject hierarchyof thedialog. It restrictsthe user inputtosensible values. It sets up allthe necessarysignal/slotconnections.

Theentirelogicofthe applicationiscontained in thecode for slotsand in their connections to thecorresponding signals.

2.2SeparationofGUI andProcessing Logic

2.2.1AlternativeDesign

In theprevious exampleprogram,wedefinedasingleByteConverterDialog class that implements both thegraphical interfaceand theprocessing logicfor theap- plication: If theuserchanges thevalue in oneofthe line-edit widgets,the Byte- ConverterDialog classcalls thecorresponding slot,which adjuststhe value of the twoother lineedits.Figure2.5 depictsthis. Such adovetailingofthe GUI andthe applicationlogicbringsthe risk of aprogram design that is confusingand difficulttomaintain. If, for example, crucialapplica- tion logichas been embeddedinmethods responsible for setting up layouts and widgets,and it’s later decidedtochangethe look andfeel of theinterface, then the code responsible for thefunctionalitywill havetobepainstakinglyfactoredout. This problemcannotbecompletelyavoided, butitcan at leastbeminimized by separatinguserinterfacecode anddataprocessing code,asshowninFigure2.6. Thepossibilityof usingsignals andslots simplifies theabstractionhere, because unnecessarydependencies of thedialogclass on theprocessing class(andvice versa)can be avoided. In this design,the ByteConverterDialog classisresponsible onlyfor theGUI;con- version of thenumbersistakenover byan additionalclass,ByteConverter.This classhas theslots setDec(), setHex() andsetBin(). If you callthe setDec() slot with astring, theclass sendsout thesignals hexChanged()and binChanged()withthe corresponding valuesinhexadecimalorbinaryform, andsimilarlyfor theother twoslots.

74 2.2 Separation of GUI and ProcessingLogic

Figure 2.5: ByteConverterDialog GUI elements and processinglogicof

decEdit the ByteConverterDialog textChanged() classuntil now setText()

hexEdit

textChanged()

setText()

binEdit

textChanged()

setText()

decChanged()

hexChanged()

binChanged()

We can connect thesignals andslots of theline-edit widgets from theByteCon- verterDialog to thesignals andslots of theByteConverter class, for example, the hexChanged()signalofthe decEdit object in adialogtothe setDec() slot of the associatedByteConverter.Ifthe user entersanewdecimalvalue,the line-edit widgetsends outthe textChanged() signal andsetDec() is applied. This slot in turn sendsout thesignals hexChanged()and binChanged(). Since wehaveconnected them to thesetText()slotofthe hexEditorbinEditobject,the program updates the hexadecimaland binaryvaluesinthe . TheByteConverter class“knows” nothingabout theGUI components.Ithas a clearlydefined interfaceand can still be used if theappearance of theapplica- tion changes. Separatingdataprocessing from theGUI in this wayshould alwaysbeconsidered if theprocessing logiccan be separated naturallyfrom theuserinterface. If, on theother hand,you onlywanttosynchronizeindividualGUI elements withone another, you should decide against such asplitting-up: In this caseyou willnot attain anyindependence,but onlyshiftresponsibilityto anewclass.

75 2 TheToolsNeeded to CreateDialogs

Ourexampleprogram is aborderlinecaseinthisrespect:Its dataprocessing task consists of converting numbersfromone base to another—afunctionalitynot linkedtoaparticularuserinterface. If you weretowrite ahexeditor,onthe otherhand, whose outputscan be switched between decimal, hexadecimal, and binarynotation,itwould probablynotbejustifiable to separatethe GUI from the calculation logicfor thesynchronizationofthe corresponding lineedits.

Figure 2.6: SeparationofGUI elements from the processinglogic

It can alreadybe seen that thereisnoeasyanswer as to whatyou should separate andwhatyou should leavetogether.Toacertainextentthe answer dependson theprogramming style andthe projectorganization. Qt provides thenecessary freedom for both methods.

2.2.2Declaring andSending OutSignals

ThenewByteConverter classhas signalsand slots, andmustthereforeultimately inheritfromQObject.Since it displaysnothing on thescreen,isnot awidget, andrequiresnoother functionalitythat Qt makesavailable in othersubclassesof QObject,itcan inheritdirectlyfrom QObject:

// byteConverter2/ByteConverter.h

#ifndef BYTECONVERTER_H #define BYTECONVERTER_H

#include

class ByteConverter:public QObject { Q_OBJECT

public: ByteConverter(QObject * =0);

public slots: void setDec(const QString&);

76 2.2 Separation of GUI and ProcessingLogic

void setHex(const QString&); void setBin(const QString&); signals: void decChanged(const QString&); void hexChanged(const QString&); void binChanged(const QString&); } ;

#endif

Againitisimportant here nottoforgetthe Q_OBJECT macro, otherwiseQtwill not knowaboutthe signalsand slotsdeclared. Theconstructor acceptsapointer to aQObject object as an argument.Thisbecomes the“father” of thenewobject in theobject hierarchy.Asthe defaultvalue (exactly as in thesignature of theQObject constructor) is 0,the zeropointer is used—a corresponding ByteConverter object thereforehas no parent. Theclass hasthree slots, setDec(), setHex(), andsetBin(). This time wewantto accessthemfromoutside theclass,namelyfrom theByteConverterDialog class, andweallowthis withthe keywordpublic. Signals aredeclaredwiththe signals: designator.There is no accesscontrol mode specified—theyarealwayspublic.Anyprivateorprotected signalswould be in- visibleoutside theclass,and thereforewould be uselessfor communicating be- tween different classes. Within asingleclass (asinour previous implementation), straightforwardfunction calls can be used.Apart from thesignals:designator, signal declarationslook just likefunction declarations. In contrast to member functionsand slots, however,the implementation of the classomits definingthe signals, sinceall theydo is callthe slotstowhich theyare connected. 7 TheByteConverter constructorisquicklyimplemented:

// byteConverter2/ByteConverter.cpp

#include "ByteConverter.h"

ByteConverter::ByteConverter(QObject * parent) : QObject(parent) { }

We passonlytheparentargument to theQObject constructor(that is,the con- structor of thebaseclass).

7 Of course,signals are also implementedautomaticallybymoc. Thecode generatedfor asignal calls thecorresponding slots, butitisnot possible to write aseparateimplementationfor signals.

77 2 TheToolsNeeded to CreateDialogs

Theimplementationofthe slotscorresponds more or less to that from Section2.1.4 on page 70:

// byteConverter2/ByteConverter.cpp (continued)

void ByteConverter::setDec(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok); if (ok) { emithexChanged(QString::number(num, 16)); emitbinChanged(QString::number(num, 2)); } else { emithexChanged(""); emitbinChanged(""); } }

void ByteConverter::setHex(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok, 16); if (ok) { emitdecChanged(QString::number(num)); emitbinChanged(QString::number(num, 2)); } else { emitdecChanged(""); emitbinChanged(""); } }

void ByteConverter::setBin(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok, 2); if (ok) { emitdecChanged(QString::number(num)); emithexChanged(QString::number(num, 16)); } else { emitdecChanged(""); emithexChanged(""); } }

Againweconvertanumerical value into each of thethree number systemswiththe QStringfunctionstoInt()and number(). However,the slotsdonot change thevalue of theline-edit widgets themselves,but merelysend thecorresponding signals. To do this,wesimplycallthe signal likeafunction. To make it clearthatthisisnot anormalfunction call, weprefixthecallwith theemitdesignator. This is notnecessary,but merelyintendedasanaid for the

78 2.2 Separation of GUI and ProcessingLogic

, who can immediatelyseefromthisthatasignal is beingsent. It is good programming practice to consistentlymark signal emissionswithemit. Nowweonlyneed to enter thenewheaderand source textfilesinthe .pro fileso that qmake can generate thenecessarymoccalls:

#byteConverter2/byteConverter2.pro

TEMPLATE =app

SOURCES =main.cpp \ ByteConverterDialog.cpp \ ByteConverter.cpp HEADERS =ByteConverterDialog.h \ ByteConverter.h

If you forgettoprocess afile that declares aclass withsignals usingmoc,the linker willcomplainofundefinedsymbols; GCCissues thefollowingerror message,for example: ld:Undefined symbols: ByteConverter::binChanged(QString const&) ByteConverter::decChanged(QString const&) ByteConverter::hexChanged(QString const&)

If theclass onlydeclares slots, however,and you do notprocess it withmoc,then you willunfortunatelyonlyreceiveanerror message at runtimethatthe signal/slot connectioncould notbecreated,since theslotisnot known.

2.2.3Using Your OwnSignals

With theByteConverter class, theByteConverterDialog classhas been simplified— werequire neitherclass variablesnor slots; theconstructor is sufficient:

// byteConverter2/ByteConverterDialog.h

#ifndef BYTECONVERTERDIALOG_H #define BYTECONVERTERDIALOG_H

#include class ByteConverterDialog :public QDialog { Q_OBJECT public: ByteConverterDialog();

79 2 TheToolsNeeded to CreateDialogs

} ;

#endif

We generate thewidgets as in theprevious exampleand passtheminthe same wayas beforetothe careofthe layout, andthere arealsonodifferences in the adjustments,including thevalidators. We merelyrequiredifferent signal/slotcon- nections:

// byteConverter2/ByteConverterDialog.cpp

ByteConverterDialog::ByteConverterDialog() { ... // Signal/slotconnections connect(exitButton, SIGNAL(clicked()), this,SLOT(accept()));

ByteConverter * bc=newByteConverter(this);

connect(decEdit,SIGNAL(textChanged(const QString&)), bc, SLOT(setDec(const QString&))); connect(hexEdit,SIGNAL(textChanged(const QString&)), bc, SLOT(setHex(const QString&))); connect(binEdit,SIGNAL(textChanged(const QString&)), bc, SLOT(setBin(const QString&)));

connect(bc, SIGNAL(decChanged(const QString&)), decEdit,SLOT(setText(const QString&))); connect(bc, SIGNAL(hexChanged(const QString&)), hexEdit,SLOT(setText(const QString&))); connect(bc, SIGNAL(binChanged(const QString&)), binEdit,SLOT(setText(const QString&))); }

We connect theclicked()signalofthe Quit buttontothe accept()slotofthe dialog, which closes thedialog. Theremaining signal/slotconnections correspondtothose showninFigure2.6 on page 76. In theByteConverter constructorweenter thethispointer as an argument so that thenewobject willbecomeachild of ByteConverterDialog.Thiscausesthe auto- maticmemorymanagement to delete theByteConverter object as soon as theGUI is deleted.Inaddition,thisparent/child relationship ensuresthatthe ByteConverter object is available for theentirelifetime of theByteConverterDialog object. Theexampledemonstrates that whether you connect slotswithyourownsignals or withsignals of theQtclasses, it makesnodifferenceasfar as thesyntaxis concerned.

80 r te ap 3 Ch

GUI DesignUsing the Qt Designer

While simple graphical interfaces such as theone for theconverter created in Chapter 2can be programmed“manually”without toomanyproblems,there is aneed for agraphical interfacedesigntool,especiallywhendesigning dialogs in which manyGUI elements mustbeplaced. Qt provides this verythinginthe form of the Qt Designer.

3.1Dialogs “By MouseClick”

Belowwewillcreatethe ByteConverter dialog from theprevious chapter usingthis GUI tool. Thefact that manydifferent windowsopenwhenthe Designer starts is something to which some Windowsusers areunaccustomed.Ifyou wanttouse thedock

81 3 GUI Design Usingthe Qt Designer

windowmode instead, which is thedefault mode in Visual Studio,for example, you can switch this on under Edit → UserInterfaceMode→ DockedWindow. Oneofthe windowsisthe “NewForm”dialog, which expectsatemplate to be selected.Templates aregenerallyavailable here for main windows, dialogs,and widgets.Qt4makesadistinctionbetween dialogs that place thebuttons for the user actions OK (confirm) andCancelonthe bottomedge andthose that place them in theright corner.Weselectone of theseasatemplate for theByteCon- verter dialog; Figure 3.1showsthe Dialog withButtons Bottomtype.

Figure 3.1: Thedialogtemplate with standard buttons arranged below

We no longer requirethe buttons specified.Todelete them,wedraw,using theleft mousebutton, aselection framesurroundingthe buttons andthe space marker ✞ ☎ ( spacer). Pressing the ✝ Del ✆ keyremoves thewidgets that aresurplus to require- mentsinour case. Thenextstep is to add theinput lines andlabelstothe dialog framework. These can be found in theWidgetBox,which theprogram normallyplaces on theleft side of thescreen.Tocreateanewlabel, welook for theDisplayWidgets group(at thebottomofthe box)and pull theLabel entryonto thedialogvia drag anddrop.

Figure 3.2: Thedialogcontains thefirstwidgets.

82 3.1 Dialogs“By MouseClick”

Nowweadd theline-edit widgets.Asaninput element, this user interfacecom- ponentbelongs to thecategoryof InputWidgets andisalsomoved into position via drag anddrop. In addition to thethree textlabelsand lineedits,weneed a button(Buttons→ Push Button) andbothahorizontaland vertical spacer. These placeholders in theDesignerworklikestretches; theyadjust thedistancebetween widgets whenthe windowcontaining them is resized. AGUI elementthathas been positionedcan be repositionedbydragging it withthe left mousebutton. Figure 3.2showsthe formfor theByteConverter dialog after all required Widgets havebeen placed. Users of Qt 4.2and newer mightnot findthe Designer templateasdescribedabove, as Trolltechhas slightlyaltered thedefault dialog template. Themotivationwas to overcome problems withregardtothe buttonorder on different platforms:The currentstyle defines theorder of thebuttons.OnMac OS Xand GNOME, the destructiveaction(e.g.,Cancel) is locatedonthe left side,while theconstructive action(e.g.,OK) is locatedonthe righthandside. On Windowsand KDE,the button orderisthe otherwayaround. Trolltech’ssolutionwas to introduce anewclasscalledQButtonBoxin Qt 4.2, which automaticallyprovides whatthe user hadtoset up manuallybefore: aset of de- fault buttons andaspacer. ByusingQButtonBox,the applicationwillautomatically pick theright orderfor thestyle chosen.Figure3.3 showsthe defaulttemplatein Cleanlooks(GNOME) andPlastique (KDE)style.

Figure 3.3: QButtonBox adapts thebuttonorder to theenvironment’s styleguide.

If Qt Designercan make useofQButtonBox,the best solution is to neitherremove it as advisedfor thebuttons abovenor add anewbutton. Instead, wemodifythe standardButtons propertyin theDesigner’sPropertyEditorinawaythat it will onlyusethe Close button. This is donebydeselectingbothactiveentries from the drop-downboxof thestandardButtons propertyandthenselecting QDialogBut- tonBox::Close. Users unfamiliarwithpropertyeditors, e.g.,fromother graphical GUI builders, should first read section5to obtain ashort introduction.

83 3 GUI Design Usingthe Qt Designer

3.1.1Making LayoutsWiththe Designer

Thewidgets do notyet haveacleanarrangement.Toavoidhavingtoexplicitly positioninterfaceelementsdowntothe last pixel,the Qt Designerprovides some standardlayouts.Togroup anumber of widgets together,you can first highlight them bydrawingarectangle around them allwiththe left mousebutton, and then choosethe desiredlayout, either from thecontextmenuwhich appearsin theselection if you clickthe rightmouse button, or from thetoolbar. Thelatter is recommended specificallyfor Macusers withaone-buttonmouse. In thecaseofthe lineedits andlabels, a grid layout is thebestchoice; this is selected in thecontextmenuthrough theitem Layout → LayOutinaGrid. The layoutisthenoutlined in redand theobjectsappear,grouped together,astheywill be in thefinalapplication. When applyingalayout, theDesignertries to tolerate anypixel imprecisions that thedevelopermayhavecausedinplacing thewidgets.Ifthe selected layoutdoes notarrange thewidgets as you intended, or if thereare thewrong number of elements for thelayout, thearrangement can be canceledthrough thecontext menu entryLayout → BreakLayout. If wechose nottouse QButtonBoxabove, wechooseahorizontallayoutfor the spacerand thebuttons,asQButtonBoxalreadyincludes ahorizontallayoutwitha spacing (Layout → LayOutHorizontally).

Figure 3.4: Layoutsgroup widgets together.

Finally,webring together both layouts,along withthe until nowungrouped vertical spacers, byselectingthe contextmenuentryLayout → LayOutVerticallyinto a single,vertical layout. This “global” layoutdoesnot need to be specificallyselected: TheDesignerdoesnot highlight it withaseparateframe,and itscontextmenu opens if you clickanemptyspace in thedialog. It affectsall thepreviouslyformed layouts anduntil nowungrouped elements (e.g., thevertical spacer).

84 3.1 Dialogs“By MouseClick”

Theresult, showninFigure3.4,isclose to ourdesired GUI.The labels,however,are notyet correct. This can be changedvia thecontextmenuentryChange text. .. or via the PropertyEditor to theright of thescreen.Tobetter understandthe latter, it is worthtaking alook at thepropertyconceptofQt.

3.1.2The PropertyEditor

QObject-based classeshavespecial properties that can be setwithsetProperty() andqueried usingproperty(). Examples of user interfaceinformation that can be represented bypropertiesinclude size, labeling, formatting details,helptexts,and manyotherthings.

Figure 3.5: ThePropertyEditor classifies each propertyofaclass accordingtothe class in which it wasfirst defined (either the classitself, or oneof itsparents).

This is doneinthe Property Editor (Figure3.5). It lists thechangeable properties, arranged bytheclass in which each propertywas first implemented—eitherthe classitself, or oneofits parents.For example, aQLabelinherits from theQFrame class, which in turn is adescendantofQWidget; accordingly,one can getand set notonlyalabel’s QLabel-specificpropertiessuchasthe labelingtext(textproperty), butalsoits QFrame propertiessuchasthe frameShape1 andits QWidgetproperties such as thesize(geometry). Since allwidgets inheritfromQObject,you can alwaysget andset theQObject propertyobjectName, which provides an internaldescription andshouldnot be confused withalabeltobedisplayed on thewidgetinthe user interface—for ex- ample, thetextonalabelorbutton(which is specified withthe textproperty). The name of theobject variable andother things arederived from objectName. Since wedon’t wanttochangethe labels in thecode weare going to write later, theirvariable namesplayno furtherrole. That is whywecontinue usingthe object namesgenerated bytheDesigner.

1 Labels arenormallywithout frames,which is whyframeShape is settoQFrame::Nonebydefault.

85 3 GUI Design Usingthe Qt Designer

On theother hand,weneed to manuallyaccessthe line-edit widgets later on, which is whywegivethemthe same namesasthe ones theyhadinChapter 2 (thatis, decEdit,hexEdit, andbinEdit) usingthe PropertyEditor. This ensuresthat in thecode that willbegenerated bytheUserInterfaceCompiler, thecorrespond- ingpointersalsohavethe same names. Howwecan accessthe line-edit widgets created bytheDesignerisexplainedonpage 92. To change propertiesinthe Designer,you highlight therelevantwidgetwitha(left) mouseclick. Thecontents of thePropertyEditorwindowareadjusted accordingly, andyou can change thepropertiesofthe highlighted widget. Thepropertieswhose valuesare (already)different from thedefault areshowninboldtype.

ChangingWindowTitles

To change thewindowtitleofthe entire dialog, weclickanypoint in thewidget construction windownotcovered bychild widgets or layouts,suchasthe area between thelayoutframe andthe dialog margin.The PropertyEditornowdisplays thewindowTitle propertyin theQWidgetsection.Clicking thecorresponding line enablesyou to change thevalue of theproperty,for example, to turn theDialog into anumber converter. 2 Thebuttonwiththe smallred arrow,nexttothe value of theproperty,allowsthe propertyto be resettothe defaultvalue. Althougheverywidgethas thewindowTitle property,itbecomes visibleonlyin the caseof top-level widgets,thatis, windowsand dialogs.

Adjusting Lettering

Ourdialogstill doesnot displaythecorrect textstrings on itslabelsand buttons. In ordertodothis, thetextpropertyis required.

Figure 3.6: Thelabels containthe correcttext.

2 Fornewprojects,the Designer onlydisplaysthe changedwindowtitleafter theuserhas saved thedialog. Before thedialogissaved forthe first time,the titlebar contains onlytheword untitled .

86 3.1 Dialogs“By MouseClick”

We setthispropertyin thethree QLabels, in turn,todecimal,hexadecimal, and binary.For thebutton, weset thepropertyvalue (inthe QAbstractButtonsection of theparentclass) to Exit.Figure3.6 showsthe result.

Definingthe DefaultButton

If you want, you can also switch on thedefault propertyfor theQPushButton. If ✞ ☎ it is settotrue, pressing the ✝ Enter ✆ keyanywhere withinthe dialog activates the button. However,thishardlymakessense,because whenthe buttonisactivated, theapplicationcarries outadestructiveaction(that is,itcloses),which would probablyirritate theuserifheactivated it bymistake. Althoughthe Designer allowsthe defaultpropertyof several buttons in awidget to be settotrue, onlyoneofthemcan function as thedefault button. Qt treats thelastbuttonofthe widgettohaveits defaultpropertysettotrueasthe widget’s actualdefault button. If thewidgetinvolved is adialog, Qt from version 4.1onwardalsoautomaticallyen- ablesthe autoDefault propertyfor allbuttons arranged on it.Thispropertycomes ✞ ☎ into effect if theuser“jumps” from onewidgetparttothe nextusing the ✝ Tab ✆ key (see page 89):Ifhereaches alineeditwhendoing this,for example, pressing the ✞ ☎ ✝ Enter ✆ keyactivates thenextbuttoninthe tabsequence, provided itsautoDefault propertyhasbeen set. When usingQButtonBox,the defaultpropertyis automaticallyassigned to con- structivebutton. Close,being adestructiveaction, cannotbecomethe default buttoninthiscase.

Changingthe Window Size

Onlyonedetail nowspoils thepicture:The dialog as awhole is muchtoo large. It is possible,ofcourse, to clickthe plus sign in frontofthe geometrypropertyand definethe width andheightprecisely,downtothe pixel.

Figure 3.7: Adjust Size provides thedialogwiththe correctsize.

Alternatively,the dialog can be scaled downtothe required sizeusing themouse. Butasarule it is simplertoselectthe Adjust Sizefunction from theFormmenuor

87 3 GUI Design Usingthe Qt Designer

from thetoolbar(via theiconwiththe diagonal arrowon thefar right),provided you activated thedialogyourselfbeforehand. This will nowshrink thedialogtoa suitable sizecalculated bytheDesigner(Figure 3.7).

3.1.3The Preview

To checkthe result,you can usethe previewfunction provided in theFormmenu of theDesigner. If you want, you can even viewthedialoginother widgets’styles, via thePreviewin submenu. Figure 3.8showsthe previewunder Linux.Trolltech defines the Plastique style as thedefault,which is similartothe defaultstyle of KDE 3. UnderMac OS X, Qt uses thenativeAquastyle,using thedrawingroutine of Mac OS X. Likewise, theWindowsXPstyle uses WindowsAPIs to drawthestyle. Therefore, theAquaand XP stylesare available onlyon thoserespectiveoperating systems.

Figure 3.8: Apreview of the finished widget

3.1.4Signal/SlotConnections

Besidesthe interfacedesignmode,the Designer also contains aviewin which the signalsofwidgets in an existing design can be graphicallylinkedtoslots.Press the ✞ ☎ keyor select theentryEditSignals/Slots from theEditmenutoswitch to this ✝ F4 ✆ ✞ ☎ mode;you can leavethismode withEdit → EditWidgets or the ✝ F3 ✆ key. Connectingsignals andslots in theDesignerisatwo-step process. First, you pull aconnectionfromthe widgetwiththe desiredsignalontoawidgetwithacor- responding slot.The backgroundsofthe widgetordialogcan themselves be drop targets here.Connections that land thereare provided withaground icon bythe Designer;all otherconnections endwithanarrowon thetargetwidget(Figure 3.9 demonstrates both cases).

88 3.1 Dialogs“By MouseClick”

Figure 3.9: Signal/slot connectionsare createdinthe Designer viadragand drop.

Step twoconsistsofspecifyingthe desiredsignaland slot pairfor thetwowidgets. As soon as you releasethe mousebuttonover thetargetwidget, theDesigner opens adialog, as showninFigure3.10:Onthe left it showsamenu of themost frequentlyused signals. If thesignalyou arelookingfor is notthere,clickthe Show allsignals andslots checkboxto displayallpossible signalsofthe source widget. Theright selectionboxwill showallthe slotsofthe target widgetmatchingthe signal selected on theleft. If you confirm thechoice, theconnectionisestablished.

Figure 3.10: Signals andslots of twoselectedwidgets areconnectedbythe developerinthis dialog.

✞ ☎ Aclickonthe connectingline, followed bypressing the ✝ Del ✆ key,willremovethe connection.

3.1.5The TabSequence

Theso-called tabsequence is important for keyboardusers. This function allows ✞ ☎ theinput focustobeshifted,via the ✝ Tab ✆ key,tothe nextwidgetthatexpects input. TheDesignerspecifiesthe tabsequencesothatinitiallythefirstwidgetin thedialoghas thekeyboardfocus.The focusismoved to thenextinserted GUI ✞ ☎ elementwhenthe ✝ Tab ✆ keyis pressed. When designingauser interface, you should

89 3 GUI Design Usingthe Qt Designer

payattention to thedefault tabsequenceand modifyit as necessaryin orderto make yourapplicationasuserfriendlyas possible.

Figure 3.11: Howthe focusis passedo✞ nw☎ hen pressingthe ✝ Tabk✆ ey is specifiedinthe Tab Ordermode

To do this,you switch to the TabOrder mode,via Edit → EditTab Orderorthe icon withthe numbers123 andanarrowin thetoolbar. NowtheDesignerdisplayseach widget’scurrent positioninthe tabsequenceinabluebox(Figure3.11).Aclickon thecorresponding boxincreasesthe rank in thesequencebyone.

3.1.6Shortcutsand Buddies

Thosewho prefer keyboardcontrol willthank you if theycanjump directlyto as manycommonlyused widgets as possible.GUI elements that displayauser- defined text, such as buttons,are assigned akeyabbreviation byplacingan am- persand (&)beforethe characterthatwillserveasthe keyboardshortcut. If the textitselfcontainsareal ampersand, it is masked byduplicatingit: &&. ✞ ☎ ✞ ☎ If theuserpressesthe combinationof ✝ Alt ✆+ ✝ character ✆ from nowon,the widget obtainsthe focusand is activated.InFigure3.12weuse this techniquewiththe Quit button. QLabel objectsformanexception, however.Since theyusuallyoccurinalayout for thepurposeofdescribinganadjacent“partner” widget, theythemselves do notaccept afocus.However,the Buddypropertyof alabel can be used to spec- ifyakeyboardshortcuttobeassociatedwiththe partnerwidget, as though the descriptivetextofthe labelweredirectlyattached to thepartner elementitself. In theDesignerviewmode EditBuddies,you can nowspecifywithwhich widget alabel is apartner.Todothis, clickthe future Buddylabel, which willthenlight up in red. Holdingdownthe mousebutton, you nowpull aconnectionover to the widgetthatinfutureshouldbeassociatedtothe label.

90 3.2 IntegratingDesigner-generatedFiles into Your

Figure 3.12: Labels arefriendsto other widgets:The Buddy allocationscan be found in theBuddy mode of theQt Designer.

In theexamplefromFigure3.12, therespectivelineeditnowhasthe focusifthe user pressesthe lettersunderlined in thelabel inscriptionwhile holdingdownthe ✞ ☎ ✝ Alt ✆ key. Alternatively,while in thenormaldesignmode,you can setthe name of thede- siredBuddywidgetinthe PropertyEditor, usingthe Buddyproperty. 3 Using this ap- proach,wewould setthe value of theBuddypropertyof theQLabelobject that dis- playsthe Decimaltextinour byte converter dialog so that it matchesthe value of theobjectNamepropertyof thecorresponding line-edit object,namely,the string decEdit. To undothe relationship,all you need to do is clickthe connectionlineinthe Buddy ✞ ☎ mode andpress the ✝ Del ✆ key.

3.2IntegratingDesigner-generated FilesintoYour Qt Project

When savingwiththe menu item File→ SaveFormorSaveFormAs. ..,theDesigner generates a.ui filefromthe information it hasfor each widgetinthe form.4 This .uifile is specified in theqmake projectfile,asshowninthe followingline:

FORMS=byteconverterdialog.ui

In ourcase, qmaketakesintoaccount theuserinterfacefile byteconverterdialog.ui; several files can be specified,separated byaspace, or otherlines can be added according to thepatternFORMS+= file.ui .

3 Althoughthispropertyhasbeen theresince Qt 3.x ,the Designer forQt4.0doesnot displayit. Onlyin version 4.1doesitappear again. 4 Using thethird menu item,SaveFormAsTemplate. .., you cansaveyourformasatemplate, which then appearsinthe selectiondialogfor new Forms .

91 3 GUI Design Usingthe Qt Designer

When buildingthe project, make then reliesonthe user interface compiler uic to convertDesigner-generated .uifilesintoC/C++headerfiles.5 Thereisafixed naming convention in this step:for example, if theclass represented bythe.ui file generated bytheDesigneriscalledByteConverterDialog (the value of theobject- Name propertycan be examined to determine theclass name), then theresulting headerfile is given thenameui_byteconverterdialog.hbyuic. It is important here that at leastone otherfile in theproject includes this generated headerfile.You mustadd theappropriate #include statements before qmakeis run. Otherwise, make won’t calluic withthe relevantinterfacedescription fileas an argument on itsnextrun. Notice that thegenerated headerfile contains onlyahelpclass withtwomethods: setupUi(), which generates theGUI,and retranslateUi(), which can be calledifthe program is to allowtheusertochangethe language while it is running. Bothmethods expect (asanargument)apointer to thewidgettowhich theGUI object describedinthe Designer is to be bound.Even if you havealreadychosen a template in theDesigner, you can freelychooseatthispoint thewidgetclass for which theinterfaceisintended. TheMainWindowtemplate is theonlyonethat mustbeusedtogether withaQMainWindow. 6 Theclass generated bytheuic is nowavailable as Ui::ByteConverterDialog or Ui_ ByteConverterDialog,ingeneral as Ui::classname or Ui_ classname ,wherebythe classnamecorresponds to theobjectNameattribute of theformcreated in the Designer. Thereare nowthreewaysofusing andfunctionallydeveloping thewidgetcreated. Whichofthese is best to usedepends on theparticularcontext.

3.2.1Using Designer-generatedClassesasHelperClasses

If you onlywanttodisplayaDesigner-created user interfaceonce, without touch- ingthe corresponding object againafter it is initialized,itisappropriate to directly instantiatethe generated classand bindthe instance to apreviouslycreated widget withsetupUi(). This method fixes theGUI elements describedinthe .uifile on to thewidgetand anchorsthem—provided this was specified in theDesigner—with layouts. We shalldemonstrate this techniqueusing ourDesigner-generated ByteConverter- Dialog:

5 Note forQt3users: uicnolongergenerates acompleteQObject-based classinQt4,but merely aframework which canbeappliedtothe widgetofthe matching type. 6 Thewidgetcreated in theDesignerisusedinthiscaseasthe centralwidgetfor theQMainWin- dowinstance, anditispositionedwithsetCentralWidget(), instead of withthe help of alayout, as normal.Inaddition,the Designer menu bars andtoolbars aretreated separatelyfrom Qt 4.1, afunctionalitythat is equallyavailable onlyforQMainWindowinstances.

92 3.2 IntegratingDesigner-generatedFiles into Your Qt Project

// simple/main.cpp

#include

#include "ui_byteconverterdialog.h" intmain(intargc, char * argv[]) { QApplication app(argc, argv); QDialog dlg; Ui::ByteConverterDialog ui; ui.setupUi(&dlg); dlg.setAttribute(Qt::WA_QuitOnClose); dlg.show(); returnapp.exec(); }

Since thewidgets of theDesigner-generated dialog areavailable as publiclyac- cessible membersofthe UI class, theycanbefine-tunedinthe code later on by callingthe methods of therespectivewidgets.Their signalsand slotscan partici- pateinsignal/slot connections.Whether theclass Ui::ByteConverterDialog is now instantiated in themain() function or in theconstructor of aclass inheriting from QDialog makesnodifference. In ourexample, however,the approach showninthe listing abovecausesproblems: We couldconnect theQuitbutton’sclickedsignaltothe accept()slotofthe dialog, andwewould then be able to connect theslots binChanged(), hexChanged(), and binChanged()tothe textChanged() signalsofthe respectiveQTextEditwidgets.But then wewould notbeable to accessthe pointer to anyuic-generated widgetin theslotitself. Forthisreason, theuse of directlycallingsetupUi()isverylimited:Ifwedoso, weshall restrict ourselves to applyinginstances of theclass generated byuicto instancesofastandardclass likeQWidgetorQDialog.However,insomesituations this procedurecould be completelysufficient, for example, in simple modal input dialogs which arecalledwithexec(). Theexec callstartsaseparateeventloop andreturnsonlyif accept(), reject(), or anothermethod closes thedialog. Since thedialogobject doesnot ceasetoexistwhenthe dialog hasbeen closed,the subsequent code can fetchthe valuesofthe widgets placed inside thedialogby setupUi()without anydanger, so that you can getbywithout theQDialog subclass in thosecases. It is important that you alwayscallthe setupUi()method of an instance of a Designer-generated classfirst, beforetryingtoaccess member variablesofthe in- terfaceobject (inthe currentexample, thoseofui).Otherwise, theprogram will mess around withuninitialized pointersand crash. An argument for notinstanciating Designer-generated classesdirectlyresultsfrom thefact that public-membervariblesare accessible from theoutside,causing a

93 3 GUI Design Usingthe Qt Designer

violation of secrecy,one of themostimportant principles of object-oriented pro- gramming.Secrecyenforcesabstractionbyonlygranting theclass membersaccess to theirownmethods. Theinternaldetails of theclass arethus “cut off” from theother classes, andyou can change theinternaldesignofthe classwithout havingtoadjustthe rest of the program that uses this class. As long as you useonlytheUIclass as ashort-term setupclass,the infringement of theencapsulationprinciple is notreallyof any consequence.

3.2.2AlwaysHavingDesigner-generatedWidgets Available

In ordertodealwiththe shortcomingjustdemonstrated,itisagood idea to include theclass generated byuicasamember variable.Todothis, wefirstinheritfrom thedesired class, which in ourcaseisQDialog. Themain() function matchesthe onefromChapter 2, sinceByteConverterDialog from itspoint of viewis againa“black box.” Thecrucial differenceisinthe declaration of theclass.Wedeclare theclass gen- erated byuicasaprivatememberofaQDialog subclass.Thisallowsfor abitrary accesstothe widgets withinthe Designer-generated classvia this newlycreated ui member variable of theByteConverterDialog classinherited from QWidget:

// member/byteconverterdialog.h

... #include #include "ui_byteconverterdialog.h"

class QLineEdit;

class ByteConverterDialog :public QDialog { ... private: Ui::ByteConverterDialog ui; } ;

Theconstructor andall slotsnowaccessthe generated classvia theuimember variable:

// member/byteconverterdialog.cpp

... ByteConverterDialog::ByteConverterDialog(QWidget * parent) :QDialog(parent)

94 3.2 IntegratingDesigner-generatedFiles into Your Qt Project

{ ui.setupUi(this);

connect(ui.decEdit,SIGNAL(textChanged(const QString&)), this,SLOT(decChanged(const QString&))); connect(ui.hexEdit,SIGNAL(textChanged(const QString&)), this,SLOT(hexChanged(const QString&))); connect(ui.binEdit,SIGNAL(textChanged(const QString&)), this,SLOT(binChanged(const QString&)));

} void ByteConverterDialog::decChanged(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok); if (ok) { ui.hexEdit->setText(QString::number(num, 16)); ui.binEdit->setText(QString::number(num, 2)); } else { ui.hexEdit->setText(""); ui.binEdit->setText(""); } } ...

Theoverlyingprinciple also applieshere: It is essentialthatsetupUi()iscalledfirst beforewecan usethe UI classinanywayat all. Thedisadvantage of this method is itsindirectness, via themembervariable.But theadvantage of this approach is that it defusesthe encapsulation problem, limitingthe problemtoscope of the dialog class. Anysinceaccess from outsideofthe dialog is notpossible under any circumstances.Afurtherbonus:Itisclear from thecode which widgets weregen- erated in theDesigner. In addition,thisapproachisparticularlysuited for widgets in librariesthathavetoremainbinary-compatible,because onlythepointer to the instance of thegenerated classchanges thebinarylayoutinthe compileroutput.7

3.2.3MultipleInheritance

As the idealsolution, Trolltechrecommendsmultipleinheritance. Butlikethe pre- vious solution,thisworks onlyif you plan yourownsubclass. In this method, thenewwidgetinherits notonlyfrom QWidget, butalsofromthe UI classgenerated byuic. Aparticularhighlight is theuse of theprivatekeyword in theinheritanceinstruction.Thisensures that allmethods from theUIclass

7 More details of binarycompatibilityin C++havebeen compiledbytheKDE projectat http://developer.kde.org/documentation/other/binarycompatibility.html.

95 3 GUI Design Usingthe Qt Designer

aregiven thestatusofprivateclass variablesinthe newclass, although theyare actuallypubliclyaccessible in theformerclass itself:

// inherit/byteconverterdialog.h

... class ByteConverterDialog :public QDialog, privateUi::ByteConverterDialog ...

This method thus solves several problems at onestroke: We can usethe widget pointersgenerated byuicasstandardmembervariables, without going thelong wayround,via ahelpobject,and theyremain private, so that encapsulation to the outsideismaintained. Forour example, this meansthatthe constructorchanges as follows:

// inherit/byteconverterdialog.cpp

... ByteConverterDialog::ByteConverterDialog(QWidget * parent) :QDialog(parent) { setupUi(this);

connect(decEdit,SIGNAL(textChanged(const QString&)), this,SLOT(decChanged(const QString&))); connect(hexEdit,SIGNAL(textChanged(const QString&)), this,SLOT(hexChanged(const QString&))); connect(binEdit,SIGNAL(textChanged(const QString&)), this,SLOT(binChanged(const QString&)));

}

void ByteConverterDialog::decChanged(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok); if (ok) { hexEdit->setText(QString::number(num, 16)); binEdit->setText(QString::number(num, 2)); } else { hexEdit->setText(""); binEdit->setText(""); } }

...

As before, weonlyneed to callthe setupUi()method in first position, andasthe argument weagainuse apointer to thewidgetthatisour currentclass scope.

96 3.3 AutomaticSignal/SlotConnections

Caution: In this approach theinheritancesequenceisimportant.First theclass mustinheritfromQDialog,and then from theDesignerclass.Ifthisisnot thecase, thecompilerwillthrowan errorthatisdifficulttounderstand, andwhich quickly brings theprogrammertodespair: moc_byteconverterdialog.cpp:43:error:‘staticMetaObject’ isnota memberof type‘Ui::ByteConverterDialog’ moc_byteconverterdialog.cpp:Inmemberfunction ‘virtualvoid* ByteConverterDialog::qt_metacast(const char * )’: moc_byteconverterdialog.cpp:60:error:’class Ui::ByteConverterDialog’ hasno membernamed ’qt_metacast’ moc_byteconverterdialog.cpp:Inmemberfunction ‘virtualint ByteConverterDialog::qt_metacall(QMetaObject::Call, int,void**)’: moc_byteconverterdialog.cpp:66:error:’class Ui::ByteConverterDialog’ hasno membernamed ’qt_metacall’ make: *** [moc_byteconverterdialog.o]Error1

Thereasonisthe behaviorofthe meta-object compiler, which checks onlyin the first parentclass of theinheritancelistwhether this inherits from QObject or not. This also meansthatitisgenerallynotpossible to inheritfromseveral classesthat allhaveQObject as abaseclass.

3.3Automatic Signal/SlotConnections

Developers versedinVisualBasic or who startonQt/C++developmentfind thesignal/slot conceptunusual, andtheymiss theeventhandler.Qt4allowsthem to sticktothe semanticstheyareusedto, permitting slot declarationsofthe form void on objectname signalname(); that areconverted into connect() instructions that uicsaves in setupUi(). Inciden- tally,thisnamingconvention increasesthe readabilityof thesourcetext. Thewhole point of this functionalityis thestaticQMetaObject::connectSlotsBy Name() method: It expectsapointer to aQObject andsearchesthrough it for slotswithmatchingnames.ThenQMetaObject::connectSlotsByName() connects thefound slotswiththe appropriate signal.Todothisitusesinformation from the meta-object generated bythemeta-object compiler, moc. This meta-object adds thecapabilityknowninC++ as introspection (alsoknowninJavaas reflection )to allclassesinheriting from QObject.Atruntimethe classtherefore“knows” itsmeth- ods,signals,and slots. connectSlotsByName() recursivelylooksatthe slot namesof theobject behind thepointersand allits children, connectingthe respectivesignals to them. Trolltechrecommendsthe semanticsshownaboveonlywiththe Designer-genera- ted classes, sinceinthiscasethe object name andthe name of theuic-generated

97 3 GUI Design Usingthe Qt Designer

pointer to thewidgetmatch,and because thesetupUi()method subsequentlycalls connectSlotsByName(). Butfor thosewho findthisconsistentnamingpatternirre- sistible,all therelevantobjectsmustbeassigned anamevia setObjectName(), must be calledinthe constructororfromoutside QMetaObject::connectSlotsByName(), andmustpassapointer to thecurrent class(this)tothiscall. Because theshownsemantics areverypronetoerrors,8 you should useautomatic connectiononlywithDesigner-generated widgets withmultipleinheritance. We willmodifyourexamples from abovesothatthe slot namesfollowthecon- ventionsfor automaticconnection. At thesametimethe connect() calls in the constructorcease to apply,sothatonlythesetupUi()instruction is left:

// autoconnect/byteconverterdialog.h

... privateslots: void on_decEdit_textChanged(const QString&); void on_hexEdit_textChanged(const QString&); void on_binEdit_textChanged(const QString&); ...

// autoconnect/byteconverterdialog.cpp

...

ByteConverterDialog::ByteConverterDialog(QWidget * parent) :QDialog(parent) { setupUi(this); }

void ByteConverterDialog::on_decEdit_textChanged(const QString&newValue) { bool ok; intnum=newValue.toInt(&ok); if (ok) { hexEdit->setText(QString::number(num, 16)); binEdit->setText(QString::number(num, 2)); } else { hexEdit->setText(""); binEdit->setText(""); } }

...

8 Remember that onlytheobject name is relevantand that in this procedure, Qt cannotissue warningsabout connections that fail at runtime.

98 3.4 IncludingDerived Classesinthe Designer

3.4Including DerivedClasses in theDesigner

It is sometimesnecessaryto make minormodificationstoaQt standardwidget. In such cases you can no longer usethe Designer without registeringthe newwidget thereasaso-called custom widget ,which involves afairamount of work.9 To still be able to usesuchawidgetinthe Designer,you select itsQtbasewidget in theDesignerand clickitwiththe rightmouse buttonafter it hasbeen adjusted. From thecontextmenu, you nowselect theentryPromotetoCustomWidget. In thedialogthatappears(seeFigure3.13),you specifythenameofthe newclass andthatofits headerfile.Althoughthe Designer continuestoshowtheoriginalQt widget, thefinished program uses themodifiedwidget; so in theimplementation you obtain apointer to an object of thetype of theinherited widget.

Figure 3.13: Usinginherited classesinthe Designer is very simple, thanks to widgetpromotion.It is oftenall youneed.

To undosuchapromotion, theentryDemote to baseclass canbefound at the same positioninthe contextmenu. Formorecomplexmodifications, such as fundamentalchanges to thelayoutbe- haviororadding properties, this procedureisnot suitable,however,since theDe- signer doesnot take them into account.

3.5The Resource Editor

From Qt 4.1on, theDesignersupports thesettingupand administrationofthe resourcesalreadydiscussedonpage 57.The editor integrated in this (Figure3.14) can be calledfromthe entryTools → Resource Editor,incaseitisnot already visible. Navigatinginittakessomegettingusedto, however.The drop-downbox nexttothe NewandOpenentries showsalistofalreadyopenedresourcefiles. It doesnot includeasaveaction, as this is performedimplicitlybytheeditor.

9 Notesonthisare provided in theonlinedocumentationfor Qt.

99 3 GUI Design Usingthe Qt Designer

Figure 3.14: Theresources example from page 57 in theResource Editor of theDesigner

In addition,the listofresources displayed in theDesignerisindependent of those in the.profile.Thisiswhyit is important to ensure that allthe resourcesreallyare entered thereunder thekeyname RESOURCES. Bysubsequentlyrunning qmake, theresources become apartofthe project. To assign an image from aresourcetoaQLabel in theDesigner, for example, you first search in thePropertyEditorfor thepixmappropertyandclickthere on the foldericon. In thefollowingdialogyou reach theResourceEditorbyselecting Specifyaresource, where you can chooseone of theimages. To displaythedesired graphicsinthe currentwidgetsize, thescaledContents propertyin theProperty Editormustbeset to true;otherwiseitwill remain in theoriginalsizeofthe image.

100 r te ap 4 Ch

Developing aGUI Application BasedonaMainWindow

In thefollowingsection wewilldevelop an applicationstep bystep,one which displaysall thetypical features of agenuine graphical applicationand which also performs ausefultask: asmall texteditorcalled CuteEdit. We design its main window usingthe Designer,which allowsthe basicgraphical frameworkofmostapplications to be puttogether “bymouseclick” in Qt versions 4.1and later.The basisofthisisthe QMainWindowQt class.

4.1The Anatomy of theMainWindow

TheQMainWindowclassforms thebasis of an applicationwindow:Menubar,sta- tusbar,toolbars, anddockwindowscan be broughtintothismainwindow.Figure 4.1showsthe individualcomponents.The centralwidget provides theworkspace for theuser.

101 4 Developing aGUI Application BasedonaMainWindow

Figure 4.1: Anatomyofamain window

Abaremainwindow,asshowninFigure4.2,initiallyconsists onlyof thecentral widgetand frame, plus atitle bar.1 In ordertoconjure this minimalarrangement onto thescreen,nothing more than asimpleprogram that instantiates aQMainWindowobject andsets alabel an- nouncingitasthe centralwidgetisrequired. So that theletteringisdisplayed with centered alignment bythelabel,weuse the

tag: QLabel interprets certain HTML tags as markup,ratherthanastext:

// mainwindow/main.cpp

#include #include #include

intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QMainWindowmainWindow; QLabel * label =newQLabel("

CentralWidget
"); mainWindow.setCentralWidget(label); mainWindow.show();

returna.exec(); }

This exampleisthereforedifferent from theone introducedinChapter 1.1(page 25),inparticularbecause wedisplayalabel withinaQMainWindowinstance.The result is showninFigure4.2.

1 UnderX11, thereare afewwindowmanagers that do notshowanydecoration around the window.

102 4.2 Derivingfrom QMainWindow

Since thelabel is no longer thetop-level widget, it is vital that it is created on the heap, withnew.Otherwise, theprogram maytryto delete it twiceafter themain() function hasended:First thecomputer would removethe labelfromthe stack, andonlythen removethe main window,which in turn would also liketodelete thelabel,which it toohas adopted as achild throughsetCentralWidget(). Under certaincircumstances this can cause theprogram to crashafter it hasrun normally.

Figure 4.2: OurMainWindow example program—without menubar,statusbar, toolbar,and dock window

4.2Derivingfrom QMainWindow

Moreserious applications usuallyinheritfromQMainWindow,adding features that provide more control. In contrast to theaboveexample, weshall deriveaseparate classcalledMainWindowfrom QMainWindow,onthe basisofwhich weshall con- struct CuteEdit. At thesametimewewillget to knowotheressentialwidgets,such as QTextEdit, aflexible editor widget.

// cuteedit1/main.cpp

#include #include "mainwindow.h" intmain(intargc, char * argv[]) { QApplication a(argc, argv);

MainWindowmainWindow; mainWindow.show();

returna.exec(); }

103 4 Developing aGUI Application BasedonaMainWindow

Themain() function is almost identical to theone from our“Hello,world!” pro- gram from Section1.1.Insteadofthe QMainWindowclassfromthe mainwin- dowexampleonpage 102, wenowuseour ownMainWindowclass, derived from QMainWindow.The corresponding classdefinition can be found in theheaderfile mainwindow.h. 2 The#includedirectivewhich incorporates thecontents of this headerfile uses quotationmarks insteadofangle brackets,since thefile is nota standardheaderfile. We againsurround thefile contents of mainwindow.h with an #ifdefconstruction providingthe includeguards to avoidcompilation errors if this headerfile is in- cluded bymore than onesourcefile. 3 MAINWINDOW_Hwillbedefinedwhenthe headerfile is processedfor thefirsttime, andthe preprocessorignores theentire filecontents for allsubsequentinclusion attempts:

// cuteedit1/mainwindow.h

#ifndef MAINWINDOW_H #define MAINWINDOW_H

#include

class MainWindow:public QMainWindow { Q_OBJECT

public: MainWindow(); } ;

#endif // MAINWINDOW_H

Since theMainWindowclassisderived from QMainWindow,wefirstissueadi- rectivetoinclude theheaderfile for theQMainWindowclass. To ensure that the QMainWindowmethods remain accessible even outsidethe MainWindowclass, we grantthe derivationpublic access. Because ournewclassalsoinherits from QObject as abaseclass,wemustnot forget theQ_OBJECT macro. Otherwisethe linkerwillcomplainofundefinedsymbols, which,inthe caseofself-defined signals, resultsinanerror message.Inthe case of aTool class, which defines asignalcalledswitchTool(Tool*),thiswillappear as follows:

tool.o: Infunction ‘Tool::activateTool(bool)’: tool.cpp:(.text+0x5f):undefined reference to‘Tool::switchTool(Tool* )’ collect2:ldreturned status 1 2 Forheader files that wecreateourselves,weuse thefilename extensioncommoninC/C++, .h, to make clear thefile type.Wedonot useuppercaseinanyfilenames. 3 Seepage62.

104 4.2 Derivingfrom QMainWindow

In theMainWindowclassitself, weonlyhavetodefine theconstructor.For this reason,the source textfile mainwindow.cpp is also rather short:

// cuteedit1/mainwindow.cpp

#include "mainwindow.h" #include

MainWindow::MainWindow() { setWindowTitle(tr("CuteEdit")); resize(600,400);

QLabel* label =newQLabel(tr("CentralWidget")); setCentralWidget(label); label->setAlignment(Qt::AlignCenter); }

In theconstructor wethe first callthe QWidgetfunction setWindowTitle(). Since theMainWindowclassisderived from QWidgetasthe base class, it inherits this function,and wecan useittoset thetextdisplayed bythetitle barofthe window. If you leavethisstep out, Qt uses theprogram name as thetitle text. We setthe textfor thetitle barvia thetr()method, which inherits MainWindow inherits from QObject.Ifthe user wants,thiswilltranslate thetexttoanother language at runtime; if not, it returnsthe string unchanged. 4 Theresize()function that MainWindowalso inherits from QWidgetspecifiesthe sizeofthe window.The twoargumentsdetermine thewidth andheightofthe windowin pixels. If thesizeisnot setexplicitly,Qtwilldetermine it automatically, basedonthe contenttobedisplayed.But in ourcasethiswould be toosmall, since wewillsoon fillthe windowwithmorecontent. In ordertodisplaysomethinginthe main window,wecreateaQLabel object with thecentral widgettextand make it thefocal point of theapplicationwiththe set- CentralWidget()function,which MainWindowinherits from QMainWindow.With this callthe MainWindowobject adoptsthe newQLabel.Accordinglywemustal- locate it on theheapwithnew,fromwhich it will ultimatelybe deleted bythe memorymanagement provided byQt for instancesofQObject. ThesetCentralWidget()callpacksthe QLabel object into alayoutsothatitfills the entire space in thewindow.Bydefaultthe QLabel classarrangestextcentered ver- tically,and horizontallyalignedatthe left margin.Tocenter textinbothdirections, wechangethe alignment withsetAlignment(). This function takesasanargument valuesfromthe enumeration type (enum) alignment,which is defined in theQt namespace 5 —hence thevalue AlignCenter is prefixed withQt::.

4 Seealsopage49and Chapter14frompage375 foradetaileddiscussion. 5 Qt uses thenamespace Qt foralargenumber of enumeration types, in ordertoavoidconflicts whenthe same symbolic namesare used in several contexts.

105 4 Developing aGUI Application BasedonaMainWindow

So that qmake can unite theexisting files into aproject,weuse thefollowing.pro file:

#cuteedit1/cuteedit1.pro

TEMPLATE =app SOURCES =main.cpp mainwindow.cpp HEADERS =mainwindow.h FORMS =mainwindow.ui

Apartfromthe alreadyknownvariablesTEMPLATEand SOURCES, which weuse to specifythat weare compilinganapplicationand to specifythesourcetextfiles, theHEADERS variable is also used.Thisspecifiesthe headerfilestobeusedinthe project: qmakesearchesthrough thoseheaderfilesfor theQ_OBJECT macroand creates appropriate rulesfor themoc calls.

4.3CreatingaMain Window withthe Qt Designer

Ever sinceQt4.1,the Qt Designerhas enabledthe user to design main windows as wellasdialogs.Whenusedfor this purpose, allthe descriptions from Chapter 3 apply.Inparticular, just as explainedthere,the user interfacecompileruic creates aclass from the.ui filegenerated bytheDesigner; thesetupUi()method then “decorates”amain windowto acertain extent. After theDesignerhas started,weselectthe Main Windowitem from thetemplate menu. When designingour editor window,weborrowideasfromthe designsof othereditors. Thecentral widgetwillbeawidgetthatenablestexttobedisplayed andedited.Qtprovides aclass calledQTextEditfor this purpose. Accordinglywepullanemptytexteditelement from theinput widgetcategory to themiddleofour newmain window,and then clickthe griddedwindowback- ground. We nowselect alayoutstrategy,eitherfromthe Contextmenuorfromthe Form menu. It is completelyirrelevantwhich onewechoose. The9-pixel-wide margin that is created,which makesavailable thenecessaryspace in dialog widgets,isout of placeinthe main window,however.Toremoveit, weselectthe centralwidget entryin theobject inspectorwindowandenter amarginvalue of 0to itslayout. Forthe texteditoritselfitisrecommended that thefonttype be changedtoa monospacedfont, usingthe PropertyEditor. To do this weopenthe fontentryin thePropertyEditorand select theCourier fonttype,for example. In addition weset thelineWrapMode mode to NoWrap, sincelinewraps areseldom wanted in editors. If you do wantthem, an actionisfeasible that would switch on thelineWrapMode property.

106 4.3 CreatingaMain Window with theQtDesigner

In addition weequip theeditorwithamenu barfromwhich theprogram functions can be controlled. To guarantee rapidaccess to themostimportant functions, such as loading andsaving, wealsoinsertatoolbarbeneath this containing an icon for each of thesecommonlyinvoked actions.Astatus barprovides space fordisplay of permanentand/or contextual information,suchasthe currentpositionofthe cursor or thepurposeofthe currentmenuentry.

4.3.1Adding MenuBars

Firstwewilllook at themenubar.Weprovideitwiththe standardentries that we areaccustomed to from standardapplications:the File menu, which takescareof thefile to be edited,the Editmenu, which controls manipulation of thetext, anda Help menu. To do this,weselectthe Type here entryin thealreadyexisting menu barand create thethree entries. When doing this weshouldremembertoplace an amper- ✞ ☎ sand (&)beforeeach entry,sothatthe keywill callupthe respectivemenuin ✞ ☎ ✞ ☎ ✞ ☎ ✝ Alt ✆ combinationwitha✝ F ✆, ✝ E ✆,or ✝ H ✆ key. The&instructsthe menu to defineawindow-wideshortcut(called accelerator ), ✞ ☎ which,incombinationwiththe ✝ Alt ✆ key,jumpstothe corresponding menu item. It is appropriate to take thefirstletter of amenuentry,but thesameletter may notbeusedtwice, so you mayneed to useanother letter for theshortcutwhen twomenuentries beginwiththe same letter.The letter in theentrychosen as the shortcut charactershouldbeasintuitiveaspossible.

Figure 4.3: Nowour editor has an inputwindow and amenu bar.

Accelerators likethisallowexperiencedusers to operate theapplicationwiththe keyboard, which can often be muchquickerthanusing themouse andcan improve

107 4 Developing aGUI Application BasedonaMainWindow

theuserfriendlinessofthe software. Theyshould thereforebeprovided as amatter of course in thedesignofuserinterfaces.Withthe acceleratorsinplace,the design viewof theeditorshouldcorrespondtothatshowninFigure4.3. To definethe subitemsofanindividualmenuentry,weselectthe entryin themenu bar. Adrop-downmenuthenappearsand,for each desiredsubitem,weselectType here... andenter itsname. Let’sstart in theFile menu, to which wewillassign thesubentriesNew,Open. .., Save, Saveas..., andQuit. We recommend that you add aseparator beforethe Quit entry,sothatthisspecial action hasvisual distance from theother entries. Thereisareason behind thefact that onlysome entriesend withdots(...)—these denote entriesthatrequire furtheruserinteractionthrough adialog. In thesamewayweequip theEditmenuwiththe entriesUndo, Repeat,and after aseparator,Cut,Copy,and Paste. TheHelpmenugets bywiththe Info. .. item,the implementation of which wewilldealwithonpage 117.

4.3.2RecyclingActions in theToolbar

If you wanttomake themostimportant entriesinthe menu bareasilyaccessible for mouseusers, this raises thefollowingquestion: Is it possible to recyclethe en- triesfromthe menu entries? Luckilytheanswer is yes,since Qt encapsulates menu andtoolbarentries in so-called actions ,for which theQAction classisresponsible. When wecreated theentries in themenus of themenubar,the Designer created a separateactionfor each entry,which wewillnowreuse. An overviewof allexisting actions is provided bythe Action Editor.Ifitisnot alreadydisplayed as shownin Figure 4.4, you can make it visible withActions→ Action Editor.

Figure 4.4: The Action Editor lists allavailableactions that canbeadjusted in thePropertyEditor likewidgets.

At the moment, no iconsare assigned to theactions listed in it,inwhich casethe full textisdisplayed insteadofanicon, taking up significantlymore space.Icons

108 4.3 CreatingaMain Window with theQtDesigner

can also be of greathelpherebecause thehumanbrain can rerecognizethem more easily,since it can applyasimplepatternmatchinginsteadofhavingtoparse atextual description. In generalthere aretwowaysofrectifyingthe lack of icons. Firstweselectthe appropriate actioninthe Action Editor. Itspropertiesnowappearinthe Property Editor. We areinterested in theIconproperty,and weselectthe Open icon in the value column. Thedialognowallowsustochoosewhether wewanttosearch for an icon from aresource(seepage 99)oruse an image filedirectlyfrom the filesystem. Forour example, wecopytheitems from theCrystal IconsseriesasusedbyKDE 3 andcombinethemintoaresource,using theResourceEditorinthe Designer.For each action wecan nowselect amatchingicon. We savethe resource fileinthe same directoryas the.ui file.

Figure 4.5: Thetoolbar provides quickaccess to important actions.

To add anewtoolbar, wemovethe mousecursortothe status baratthe bottom of thewindowandselectAdd Tool Bar from thecontextmenu. We nowdrag the actions New,Open, andSavefromthe Action Editorintothe barthatnowappears. Cut, copy,and paste arealsofrequentlyused actions in editors. If you wantto includetheminthe same toolbar, as is thecaseinFigure4.5,you should separate them from theother entriesinthe File menu withaseparator (right mousebutton → Insert Separator). Actionshaveother propertiesthatthe PropertyEditorallowstobeset. These includethe application-wideshortcut(theso-called shortcut). In contrast to ac- ✞ ☎ celerators, shortcutsare activated withthe ✝ Ctrl ✆ key.The user can often gettothe actionhewants more quicklywithshortcuts than withaccelerators.

109 4 Developing aGUI Application BasedonaMainWindow

✞ ☎ ✞ ☎ This becomesclear in theexampleofthe Open fileaction: Ctrl + O is quicker ✞ ☎ ✞ ☎ ✞ ☎ ✞ ☎ ✝ ✆ ✝ ✆ to type than ✝ Alt ✆+ ✝ F ✆,followed by ✝ Alt ✆+ ✝ O ✆.For thesake of clarity,you should notuse an excess of shortcuts, butexperiencedusers highlyvalue shortcutsfor frequentlyused operations. TheQtdocumentationprovides an overviewof the standardshortcuts for programsinEnglish.6 It is interesting that theentryfor ashortcutinthe Designer is astring. Thereisno syntaxcheckwhenthisisdone, so you should alwayscheck theentries yourself. TheformatisCtrl+key. ThereasonQtinterprets shortcutsasstrings is duetointernationalization: The code generated bytheDesignerand theuserinterfacecompilerpassesthe string to thelocalizationroutine tr(), so that theshortcutcan be customized.Thisisa useful feature, sinceabbreviationsthatnoone can remember (onesheldover from an implementation in anotherlanguage,for example) arejustnot used,whereas manyuserswillrememberthemifthe abbreviationsare mnemonics for theaction to be triggered. Anotherfeature of theQAction classisthe tooltip. Tooltips,which theapplication displaystothe user as a“paleyellownote” if themouse cursor is held over amenu or toolbarentry,are setinthe code usingsetToolTip(). Thetextset via thestatusTextpropertyshowsupinthe status bar(if thecurrent windowhasone)asthe mousehoversover therespectionaction. Finallythewhat- sThis propertyallowslongerhelptexts on individualwidgetparts to be displayed.

4.3.3Integrating theMainWindow with Your Source Code

It is nowtime to turn theGUI generated in this wayinto aprogram.Wecan save ourselves abit of workdoing this anduse thefile main.cpp from theexampleon page 103:

// cuteedit2/main.cpp

#include #include "mainwindow.h"

intmain(intargc, char * argv[]) { QApplication a(argc, argv);

MainWindowmainWindow; mainWindow.show();

returna.exec(); } 6 Seehttp://doc.trolltech.com/4.1/accelerators.html.

110 4.3 CreatingaMain Window with theQtDesigner

In theimplementation, wenowmake amultiplederivationfromboththe QMain- Windowclassand from thehelperclass Ui::MainWindowclassgenerated from the uic; thelatter is aprivatederivationsothat—as alreadydescribedinChapter 3—the objectsgenerated in theDesignerare made available as member variablesfor the newMainWindowclasswiththe correctvisibility:

// cuteedit2/mainwindow.h

#ifndef MAINWINDOW_H #define MAINWINDOW_H

#include #include "ui_mainwindow.h"

class MainWindow:public QMainWindow, privateUi::MainWindow { Q_OBJECT public: MainWindow(QWidget * parent=0); ˜MainWindow();

protected: void setupActions(); ...

Beforediscussing therestofthe declaration on page 112, wewillfirstturntothe implementation of theconstructor.Itisimportant that wecorrectlyinitializethe parentclass.C++ doesguarantee theautomatic initializationofQMainWindow,so thechain of inheritancehas notbeen interrupted.The parentobject is no longer passedonwhenthisisdone, however,which can lead to memoryleaks andprob- lems whenusing layouts.Details areexplainedinSection 1.2.2onpage 31. Thefirstthing theconstructor itself contains is thesetupUi()call, which guarantees theinitializationofall member variablesfromUi::MainWindow:

#include #include "mainwindow.h"

MainWindow::MainWindow(QWidget * parent) :QMainWindow(parent) { setupUi(this); setupActions(); }

111 4 Developing aGUI Application BasedonaMainWindow

Linking ActionstoFunctionality

Thenextstep is to linkanumber of actionsmanuallyto slotsand providethem withfunctionality.Toachievebetter clarity,wewillmovethistasktoaseparate method calledsetupActions(). Here wewillbreathe abit of lifeintothe actions throughasignal/slotconnection. If theusersets off an action,suchasclicking themenuentry,thiswillsendout the triggered(bool)signal. Theparameter doesnot interestus, sinceitisonlyrelevant for alternating (“toggled”) or groupedactions.Wemustinclude it neverthelessso that connect() can findthe signal:

// cuteedit2/mainwindow.cpp

void MainWindow::setupActions() { connect(action_quit,SIGNAL(triggered(bool)), qApp,SLOT(quit())); connect(action_open, SIGNAL(triggered(bool)), this,SLOT(loadFile())); connect(action_save, SIGNAL(triggered(bool)), this,SLOT(saveFile())); connect(action_saveas,SIGNAL(triggered(bool)), this,SLOT(saveFileAs()));

connect(textEdit,SIGNAL(copyAvailable(bool)), action_copy,SLOT(setEnabled(bool))); connect(textEdit,SIGNAL(undoAvailable(bool)), action_undo, SLOT(setEnabled(bool))); connect(textEdit,SIGNAL(redoAvailable(bool)), action_redo, SLOT(setEnabled(bool)));

connect(action_copy,SIGNAL(triggered(bool)), this,SLOT(copy())); connect(action_undo, SIGNAL(triggered(bool)), this,SLOT(undo())); connect(action_redo, SIGNAL(triggered(bool)), this,SLOT(redo()));

connect(action_about,SIGNAL(triggered(bool)), this,SLOT(about())); }

We linkthe quit actionwiththe quit() signal of theQApplicationobject,which is accessible from theentireapplicationvia theglobalpointer qApp. This causesthe applicationtoleavethe eventloop andterminate itself. In orderfor otherconnections to work, westill need to declareanumber of slotsin mainwindow.h,the contents of which arediscussedonthe followingpages. Since weonlyusetheminthe MainWindowclassitself, wedeclare them as protected methods:

112 4.3 CreatingaMain Window with theQtDesigner

// cuteedit2/mainwindow.h(continued)

... protected: bool mayDiscardDocument(); void saveFile(const QString&); protected slots: void newFile(); void loadFile(); void saveFile(); void saveFileAs(); void undo(); void redo(); void copy(); void about(); private: QString mFilePath; } ; #endif // MAINWINDOW_H

Thevariable mFilePath specifies thepathtothe currentfile.Ifthe document has notbeen saved until now,thisstringisempty. Since wedonot need to destruct anythingmanually,the destructor remainsempty. AllWidgetdestruction is takencareofbytheQObject hierarchywhenthe Main- Windowinstance is destructed at theend of main().

OpeningFiles

Thefirstfunction that CuteEditshouldmaster is theloading of afile,usuallycalled a document in theterminologyof texteditors. To do this,wefirstrequire afile- name,for which wequerytheuservia an object of theQFileDialog class. It is normallycompletelysufficienttouse thestaticmethods of this class, which merelyrequireapointer to theparentwidget, as wellasanoptionalwindowtitle andafilter for various filetypes. We willuse thegetOpenFileName() static method, which returnspreciselyonefilename as aQString.Amore detaileddescription of various dialog typesisprovided in Chapter 6. To openafile, Qt uses theQFile class, which allowsplatform-independentaccess to files.Thisispartofthe Qt input/output conceptthatisexplainedinmoredetail in Chapter 11 andoccurs in placeofthe FILE pointer familiarfromC. Theopen() method, similartothe Cfunction fopen(), opens thefile,inthiscasein read-onlymode:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::loadFile() {

113 4 Developing aGUI Application BasedonaMainWindow

QString filename =QFileDialog::getOpenFileName(this); QFile file(filename); if (file.open(QIODevice::ReadOnly|QIODevice::Text)) { textEdit->setPlainText(QString::fromUtf8(file.readAll())); mFilePath=filename; statusBar()->showMessage(tr("File successfullyloaded."),3000); } }

We usethe QIODevice::Textflag so that theeditorcan cope withthe differences between UnixandWindowswithrespect to textfiles. Unixuses just alinefeed (\n) to separatelines,whereas Windowsinaddition requires thecontrol characterfor a carriage return (\r\n). Qt classesare internallybasedonUnixconventionswherever possible,which is whyQTextEditonlyworks withlinefeeds, andsowehaveQFile removeall thecarriage returnswhenitopens atextfile on Windowsplatforms by specifyingQIODevice::Text. NowthereadAll()method reads theentirecontents of thefile into aQByteArray. We couldimportthisdirectlyinto thetextWidget, usingsetPlainText(), butwedo notknowtheencoding formatofthe files.QByteArraycontains thetextinits 8-bit encoding,while QStringuses16-bit Unicode characters. In Windows, textfilesare normallysaved in UTF-8 format. This mirrorsthe Unicode characters in 8-bits, and is compatible to ASCII encoding.InLinux,textfilesare available either as UTF-8 or in country-specific encoding,suchasISO Latin1(alsoknownasISO 8859-1). Forthe sake of simplicity,CuteEditassumesthatfilesare alwaysencodedinUTF-8 andthereforeconverts thetextcontents usingQString::fromUtf8()intoaQString. 7 We willrememberthe filenamefor later operations, for exampletosavethe file againwhenweneed to. To reportthe successful opening of theselected file, weuse thestatusbar.Its showMessage() method in this exampleshowsthe message File successfullyloaded. for threeseconds,and then removes it.

SavingFiles

Nowwehavetoimplement theCuteEditfunction that makesthe program usable in thefirstplace,namely,the abilityto savethe currentdocument:

// cuteedit2/mainwindow.cpp (continued)

void MainWindow::saveFile()

7 In areal editor theprogram should first askthe user about theencoding of hisfile or—even better—findout theencoding method itself. Valuable workinimplementationisprovided bythe QTextCodecclass,which also provides alistofthe available codecs,withthe availableCodecs() staticmethods.

114 4.3 CreatingaMain Window with theQtDesigner

{ if(mFilePath.isEmpty()) saveFileAs(); else saveFile(mFilePath); } void MainWindow::saveFile(const QString &name) { QFile file(name); if (file.open(QIODevice::WriteOnly|QIODevice::Text)) { file.write(textEdit->toPlainText().toUtf8()); statusBar()->showMessage(tr("File saved successfully."),3000); } }

void MainWindow::saveFileAs() { mFilePath=QFileDialog::getSaveFileName(this); if(mFilePath.isEmpty()) return; saveFile(mFilePath); }

If no fileiscurrentlyopenedand mFilePath is thereforeempty,the saveFileAs() methodcomes into play.Itisalsodirectlycalledfromthe menu item File→ Save as..., butserves thesamepurposeinbothcases:tostore thefile under aname specified bytheuser. InternallysaveFileAs() uses theoverloadedmethod, notdeclaredasaslot,save- File(const QString&name), which takesonthe actualwork: To do this,itmakes useofthe toPlainText()method of theQTextEditinstance, which returnsaQString. Theresulting textisencodedbytoUtf8()againasan8-bit text. Beforethis, it opens thefile,asloadFile() didbefore, withthe QIODevice::Textflag, in orderto guarantee thecorrect conversion in Windows. Butthistimeweonlyopenthe filefor writing (QIODevice::WriteOnly). Afterward, this method also reports the successful completion of theactionvia thestatusbar. Even if he hasalreadyloadedafile,the user should be able to create anothernew document.Since CuteEdit, for thesake of simplicity,can onlymanage oneopen fileatatime,wehaveaproblemifthe first document hasbeen modifiedbut not yet saved. Whether this is thecaseornot is knownbythedocument object which is managed byQTextEdit:

// cuteedit2/mainwindow.cpp (continued) bool MainWindow::mayDiscardDocument()

115 4 Developing aGUI Application BasedonaMainWindow

{ if (textEdit->document()->isModified()) { QString filename =mFilePath; if (filename.isEmpty()) filename =tr("Unnamed"); if (QMessageBox::question(this,tr("SaveDocument?"), tr("Youwanttocreateanewdocument,but the " "changesin the currentdocument’%1’havenot" "been saved.Howdo youwanttoproceed?"), tr("SaveDocument"),tr("DiscardChanges") )) saveFile(); returntrue; } returnfalse; }

void MainWindow::newFile() { if (!mayDiscardDocument()) return; textEdit->setPlainText(""); mFilePath=""; }

BeforeCuteEditopens anewdocument in thenewFile() slot,itshouldask the user whatheintends to do withthese changes. Since this function can be used universally,weshall moveittothe mayDiscardDocument() method, which returns atruevalue. Forthe actualrequeststothe user,weuse aQMessageBox.Inasimilarwayas for QDialogBox,for this classweuse mainlythestaticmethods that areappropriate for most situations andonlyneed to be provided withthe corresponding argu- ments. Althoughatitleand themessage contents would be sufficientinthiscase, theexamplereplacesthe standardresponsesofYes andNowithmoredescriptive responses, for thesake of better usability. Ideally,message boxes should clearlyinformthe user whatactionistobecarried out—unfortunatelythenumber of applications withdialogboxtexts that lead to misunderstandingsisveryhigh.Since buttons specifyresponsesdirectly,thisre- ducesthe probabilitythat theuserwillselectthe wrong action. If mayDiscardDocument() returnstrue, wedelete thetextofthe currentdocument andreset thefile pathsothatother functionsdonot access thefile just edited by mistake.Aneven more efficient method would be to setanewdocument through textEdit->setDocument(), butsince allsignal/slot connections to thedocument are lost,these would havetoberecreated afterwards. In addition themethod here hasthe side-effect that theundobufferremains.This couldbeanadvantage for peoplewho can clickfaster than theycanread, butit couldalsoinvolvepossible dataprotection problems (suchasifseveral usersare usingthe same workplace).

116 4.3 CreatingaMain Window with theQtDesigner

TheUndo/Redo Function

Programs that do notallowtheusertoundoactions can be frustrating. This applies particularlyto texteditors. Since QTextDocument alreadyprovides an undostack, wecan equipCuteEditquicklyandeasilywithanundofunction:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::undo() { textEdit->document()->undo(); }

Theredoistoacertain extentthe oppositeoperation to undo: It recreates astatus that hasbeen undone. This hasalsobeen implemented already,sothatwejust need to make it accessible as aslot.

// cuteedit2/mainwindow.cpp (continued) void MainWindow::redo() { textEdit->document()->redo(); }

Thecopymethod, withwhich theusercopies highlighted texttothe temporary buffer, is also made available directlybyQTextEdit, so wejustneed awrapper method here:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::copy() { textEdit->copy(); }

Theundo(), redo(), andcopy() slotsthereforesimplycallthe matching methods of theQTextEditorQTextDocument classes. If theundostack is empty,the first twoactions in themenuand icon bars aregrayed out, sincewedisable them in setupActions() throughsignals undoAvailable() andredoAvailable() of QTextEdit, if QTextEditorQTextDocument consider them to be notapplicable.Wediscussed setupActions() on page 112.

Information on theProgram

Of course,the obligatoryinfo boxmustnot be missing in anyprogram.QMessage- Box(see page 166) provides itsownstaticmethod for this,calledabout(), which expectsaheading andashorttext:

117 4 Developing aGUI Application BasedonaMainWindow

// cuteedit2/mainwindow.cpp (continued)

void MainWindow::about() { QMessageBox::about(this,tr("About CuteEdit"), tr("CuteEdit1.0\ nAQtapplication example. \ n" "(c)2006 Daniel Molkentin, Open Source Press")); }

Thefirstparameter is apointer to theparentwindowtowards which theinfobox should behaveinamodal manner. If you pass0here,Qtgenerates anon-modal box.

BuildingaProject

Finally,todisplaytheprogram,wegeneratethe projectfile withqmake -project andbuild theprogram withqmake andmake.

#cuteedit2/cuteedit2.pro

TEMPLATE =app SOURCES =main.cpp mainwindow.cpp HEADERS =mainwindow.h FORMS =mainwindow.ui RESOURCES =pics.qrc

4.4Making theMostofthe Status Bar

Qt Designer–generated main windowsalreadyhaveastatus bar. This is used both to announceshort-termresponses, as wehavealreadydoneinsaveFile() (page 114) andloadFile() (page 113),aswellasmorepersistentmessagesofanapplication. In this way,CuteEditcould displaywordstatisticsfor thecurrent document there, for example. We willdiscuss this andhowto implementitinmoredetail from page 121.

Figure 4.6: Thesize grip at the bottomright Thebar also provides a size grip (outlined in bold in Figure 4.6) for thewindow. This is theserrated triangle at thelower rightedge of thewindow,which changes itssize. Even if you don’t wanttohaveanystatus messagesdisplayed butonly requirethis“sizechangehandle,”itisrecommended that you displaythestatusbar in themainwindow.Ifrequired, setSizeGripEnabled(false) can be used to hide the sizegrip.

118 4.4 Making theMostofthe Status Bar

Theclass that is responsible for thestatusbar,and which also contains themethod just mentioned, is calledQStatusBar. QMainWindow::statusBar() returnsapointer to theQStatusBarobject used bythemainwindow.Ifthe windowdoesnot yet haveanystatus bar, then this function generates oneand addsittothe main window. If, in an instance of QMainWindownotgenerated byDesigner,weadd theline statusBar(); theapplicationwindowis given astatusbar that showsonlythesizegrip. On the otherhand, theline statusBar->hide(); causes an undesired status bar(in this case: statusBar) to disappear again. Thestatusbar presents threedifferent typesofstatusmessages:

Temporarymessages Theseare used for information that should onlybe visible for ashort time (Figure4.7). This includes,for example, theURL of alinkover which the mouseinthe web browseriscurrentlylocated, or progressdetails during a download.

Figure 4.7: Theleftsideofthe status bar is often used to briefly display messagestothe user, as shownherein CuteEdit.

Normal messages Theseare alwaysshownbyan applicationunlessatemporarymessage is

119 4 Developing aGUI Application BasedonaMainWindow

shown. This coversover normal messageswhich areusedfor generalstatus information,suchasthe coordinates of themouse in aCAD application. Permanentmessages Thesealwaysmonopolizethe status barand can never be covered bytem- porarymessages. Theyareusedfor messagesthatshouldalwaysbevisible, such as theconnectionstatusofanetworkapplication.

Temporaryandnormalmessagesappear on theleftinthe status bar, permanent messagesonthe right. Astatusbar can displayseveral normal andpermanent mes- sagesatthe same time,but it is notpossible to displaymore than onetemporary message:The newonealwaysdisplaces theold one.

4.4.1TemporaryMessages

Temporarymessagesare activated withthe QStatusBar::showMessage() slot and aredeleted withQStatusBar::clearMessage(). Themessage textispassedtoshow- Message() as an argument in theformofaQString.Inour exampleweadd the line

statusBar()->message(tr("File successfullyloaded."));

to theconstructor so that thetemporarymessage is displayed in thestatusbar. 8 Thetemporarymessage remainsvisibleuntil weeitherset anewmessage with showMessage(), delete thecurrent textwith clearMessage(), or overwrite it byin- vokingshowMessage() again. If you wanttodisplaythetemporarymessage onlyfor aspecific length of time, theshowMessage() slot acceptsanoptionalsecondparameter.Ifspecified, the QStatusBar-Object automaticallydeletes themessage after aspecifiedtime. So thecall

statusBar()->message(tr("File successfullysaved."),3000);

displaysthe message File successfullysaved. for 3,000 milliseconds (thatis, three seconds).Thenthe normal messagestake over again, provided anyexist.

4.4.2NormalMessages

Normal messagesare notsoeasyto handle,unfortunately.You mustuse widgets for them. If you wanttoshowasimpletextasanormal message,for example,

8 To compile theprogram successfully,you mustalsoinclude theQStatusBarheader with#in- clude.The entire code is available from http://www.qt4-buch.de/examples.tar.gz.

120 4.4 Making theMostofthe Status Bar

then you generate aQLabelobject andadd this to thestatusbar,withthe QStatus- Bar::addWidget()function.Thisisabit more complicated,but it hasthe advantage that it is notrestricted to textmessages. Youcan also useicons,for example, or a progressbar foranoperation that takesmorethanabriefamount of time. In accordance withits signature, addWidget(QWidget* widget, intstretch=0), the addWidget()function requires twoarguments, oneofwhich is optional. Firstitis passedapointer to thewidgettobeadded. TheQStatusBardestructordeletes this automatically;for this reason it musthavebeen created on theheapwithnew. Thesecondargument doesnot havetobespecifiedifyou aresatisfiedwiththe defaultvalues. It determineshowseveral widgets dividethe space in thestatus baramong themselves.The value 0meansthatthe widgethas as muchspaceas necessary.Another value specifies theproportionsofthe widgets to each other. If, for example, you haveawidgetwithastretchvalue of 1and thesecondone with avalue of 2, then theyjointlyoccupytheentirespaceinthe status barsothatthe second oneistwiceaswideasthe first.Inthe exampleofFigure4.8,whose five widgets havebeen assigned stretchvaluesof1,2,3,4,and 5, thefirstwidgetis onlyallocatedthe space it requires.The otherwidgets occupytheremaining space so that thethird onehas twiceasmuchspaceasthe second,and so on.

Figure 4.8: Different stretch values in thestatus bar 4.4.3PermanentMessages

As withaddWidget(), thereisthe addPermanentWidget()function for permanent messages. Widgets inserted withthismethod appear on theextreme right: These areappropriate for permanentstatusdisplays, for example. Permanentmessages areguaranteed nottobeinterrupted,even for ashort time,bymessagesthatare displayed as describedabovevia showMessage(). To expandour MainWindowclasssothatitdisplayswordstatisticsinthe status bar, wefirstinsertalabelintothe status bar. Since weneed to accessthislater,we create amembervariable calledmStatLabelinthe classdefinition:

// cuteedit2/mainwindow.h(added)

... class QLabel; class MainWindow:public QMainWindow, privateUi::MainWindow { ... private:

121 4 Developing aGUI Application BasedonaMainWindow

QString mFilePath; QLabel * mStatLabel; }

Thelineclass QLabel;isaforwarddeclaration of theQLabelclass—in this waywe do notyet need to includethe QLabel headerfile.Because of this,the parserneeds less time for mainwindow.h andfor thefilesthatlater includethisfile. In aheaderfile containing aforwarddeclaration of aclass,one can onlydeclare variablesofpointer andreference typesderived from theclass;9 variablesrepre- sentinginstances of theclass can’t be declared because thecompilerdoesn’t know howmuchspacetheyshould occupyuntil it sees theclass’sdefinition. However,the source textfile needstoinclude theQLabelheaderfile,ofcourse, sincethe compilerwould otherwisenot recognizethe interfaceofthe class. In theconstructor weinclude code that generates aQLabelobject anddisplaysthis in thestatusbar as apermanent message;the message should onlytake up as muchspaceasitneeds, so that westill haveroom for temporarymessages. Since astretchof0,which would cause this,isthe default, wecan just leaveout the second argument of addPermanentWidget()altogether in this case. We placethe QLabel object on theheapwithnew,since theQStatusBarobject willdelete it in thedestructor.10

// cuteedit2/mainwindow.cpp (continued)

#include #include "mainwindow.h"

MainWindow::MainWindow(QWidget * parent) :QMainWindow(parent) { setupUi(this); setupActions();

mStatLabel =newQLabel; statusBar()->addPermanentWidget(mStatLabel); connect(textEdit,SIGNAL(textChanged()),this,SLOT(updateStats())); updateStats(); }

As mentioned before, statusBar()not onlyreturnsapointer to thestatusbar,but also generates it if it doesnot yet exist. To nowupdatestatisticseach time whenever theedited document is changed, we listen to thetextChanged() signal andconnect it to theslot(still to be implemented)

9 In this case thecompilerknowsthe sizeofthe memoryto be reserved,since an addressoneach platform alwayshas thesamesize, forexample, four bytesonIA32architectures. 10 TheQStatusBarobject is deletedinturnbyitsparentobject,the QMainWindowinstance.

122 4.4 Making theMostofthe Status Bar

updateStats(), which is responsible for updatingthe wordstatisticslabel.Inthis wayweensurethatQtreallydoesupdatestatisticscorrectly.Finally,wecallthe newslot manuallyso that thelabel can obtain an initialstatus. Afterwehaveentered updateStats() as aslotinthe classdeclaration of MainWin- dow,wemustnowfindawayof havingthe statistics delivered to thelabel in the status bar. Thetext, withall itsmanyproperties—QTextEditcan even handle simple HTML constructionsand allowsthe user to preciselyformattexts—is stored in an instance of QTextDocument:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::updateStats() { QString text =textEdit->document()->toPlainText(); intchars =text.length(); text =text.simplified(); intwords=0; words=text.count(" "); if (!text.isEmpty()) words++; QString output =tr("Characters:%1, Words:%2").arg(chars).arg(words); mStatLabel->setText(output); }

Each QTextEditpossessesexactlyonedocument,which is encapsulated in theQText- Document class. What at first glance just seemstobeanimplementationdetail is, in reality,averypowerfultool for displayingand manipulating text. Butherewe just useone method to accessthe currenttextofthe document:Withthe call document()->toPlainText(), QTextDocument generates aQString from itscurrently saved text, butthisismorethanenoughfor ourrelativelysimple analysis. This searches thestringfor individualwords andcharacters. To count thechar- acters, wesimplyqueryitslength, taking into account that linebreaks arealso counted as spaces. If you don’t wantthis, you can removethe linebreaks from the string beforehandusing text.replace(’\n’,""). When determining thenumber of words in thetext, wealsodonot need amore complexparser, sinceour statistics arerelativelysimple (and even so can be quite slow,depending on thetextlength): It defines awordasastring separated bya space.Thus thenumber of words is onemorethanthe number of spaceswhich lie between them.Sothatsuperfluousemptyspacesdonot getinthe way,we usethe QStringmethod simplified(), which removes allwhitespace(that is,spaces, wordwraps,etc.) at thebeginning andend of thestringand reducesthe white spacesbetween individualwords to exactlyonespace. Thestring, which weare nowexaminingfor emptyspaces, exactlymatchesour desireddefinition,and the number of spacesplusone resultsinthe number of words.The onlytrickydetail is that because an emptytexthas no words,wemayonlyincrease thespacecount byoneiftextisnot empty.

123 4 Developing aGUI Application BasedonaMainWindow

We constructthe letteringfor thelabel withtr(”Characters: %1,Words:%2”). arg(chars).arg(words);.The tr() function returnsthe text Characters:%1, Words: %2 as aQString,and if necessarytranslates this into adifferent language.The QStringclass hasanarg() method, which searches for thestrings %1,%2, ..., %9,and from thoseitfinds, replaces theone withthe smallest number withthe function argument.Inour casewewillthus obtain thedesired result,because the contents of charsand words,converted into astring, replacethe placeholders %1 and%2, in that order. Using this method, you can create stringscontainingdynamictext. This hasthe ad- vantage that theorder of thestrings in translations maychange if this is necessary from agrammatical point of view. Finally,weset thetextofthe labelwiththe QLabel function setText(), so that the status bardisplays Characters:0,Words:0,provided that thefunction for the variableschars andwords returnsthe value 0.Our expandedapplicationnowlooks likeFigure4.9.

Figure 4.9: Theeditorprogram CuteEdit,now with an addedstatusbar with apermanent message

Thestatusbar showsmessagesthatdonot interrupt theuser’swork, in contrast to notificationdialogs,which obtain thekeyboardfocus andwhich theusermust closeexplicitly.For messagesthatare notsoimportant,you should weigh whether theyreallyrequireaseparatedialog, or whether atemporarymessage in thestatus barissufficient,and whether themessage is indeed relevantenoughtothe user to be shownatall. An applicationthatistoo “talkative” can quicklygetonyour nerves.

124 4.5 Toolbars

4.5Toolbars

Each toolbarhas aso-called handle withwhich it can be moved (see Figure 4.10), so that ausercan rearrangethe toolbars in hisapplicationand movethemto different positions in thewindow.Ifyou wanttopreventthis, you should use setMovable(false);.

Figure 4.10: Atypical toolbar with ahandle(on theleft)

To locate atoolbaratapositionother than horizontallybeneaththe menu bar, such as directlyon theleftmarginofthe window,wemustmake aslight change to ourexample. Thereisasecond version of theaddToolBar() method that requires positioning details as thefirstargument.Wethereforechangethe callinthisway:

QToolBar * mainToolBar=addToolBar(Qt::BottomToolBarArea, tr("Main Toolbar"));

Nowthemaintoolbarappearsonthe bottommargin; Table 4.1specifiesthe pos- sible positions.Inpractice, though,thisonlymakessense for toolbarselectionsin thestyle of PhotoshoporGIMP, which areusuallyimplemented anywayas dock windows(seepage 130). Users arecreatures of habit,and tendtolook for toolbars in theuppersection of thewindow.

ValuePosition Table4.1: Qt::LeftToolBarArea Vertically,onthe left side of themainwindow. The ToolBarAreas enumerator Qt::RightToolBarArea Vertically,onthe rightsideofthe main window. Qt::TopToolBarArea Horizontally,ashighupaspossible butbeneath themenubar andaboveanydockwindows. Qt::BottomToolBarArea Horizontally,aslowdownaspossible,but above thestatusbar andbeneath anydockwindows. Qt::AllToolBarAreasAll previous positions.Not permissible for ad- dToolBar().

Using setAllowedAreas(), it is also possible to allowtoolbars onlyat certainposi- tions. Table 4.1provides thevaluesvalid for this,which can be linkedwithalogical OR operator. Thefollowingcode restrictsthe placementofthe mainToolBar toolbar to beneaththe menu barand abovethe status bar: mainToolBar->setAllowedAreas(Qt::TopToolBarArea| Qt::BottomToolBarArea);

125 4 Developing aGUI Application BasedonaMainWindow

Since theusercan removethe tool selections,itisthe dutyof theprogrammerto ensure that theycanbefound againatanytime.Asuitable placefor thetoolbar retrieveinstruction is themenubar,since theusercan normallynothidethis. The toggleViewAction() method provides apointer to aQAction andensures that the toolbarreappears—or disappears, if that is whatthe user wants.The visibletextof theactionisoriented towards thewindowTitle of theiconbar,which is whythis should be setinthe Designer.Ifyou don’t like it,you can defineyourowntextfor theaction, usingQAction::setText(). Menu entrieswhich allowan option to be switched on or off, that is,tobe toggled, arederived from toggleactions. To make thetoolbarinour exampleprogram disappear or reappear, webuild an actionlikethisintothe (yet to be implemented)Settings→ Toolbars submenuofthe menu bar. To do this,weadd thefollowingcode to theend of thesetupActions() function on page 112:

QMenu * toolBarMenu=settingsMenu->addMenu(tr("&Toolbars"));

toolBarMenu->addAction(mainToolBar->toggleViewAction()); ...

Forthe code to work, wemustfirstinsertanaddtionalmenuwiththe titleSettings in theDesigner, which wewillcallsettingsMenu. TheSettings menu here belongs in frontof theHelpmenu, sincethe latter should alwaysbethe last entryin the menu bar. This resultsinaselectable entrybeinglocated in thetool bars submenu. Allvisible bars areprefixed withacheckmark. Youcan seeherewhytoolbars should always be given aname.

4.6How Do Actions Work?

We havealreadymade contact withthe QAction class. So far however,the Designer generated theappropriate code.Sotoget abetter picture, weshouldnot forget to mentionhowactions workand howyou can usethem“manually.” Thosenot interested in thesedetails can continue reading on page 130. Each QAction object encapsulates information involvingauser interfaceaction.11 Allimportant propertiesare summarized in Table 4.2onpage 129.

11 In Qt 3, actions(also calledQActions) wereactiveobjectsthatselectedthe matching display method themselves,dependent on theobject in which theywereincluded. In Qt 4Trolltechhas reversedthisprinciple:Nowwidgets decide on thedisplayform of theactions presented.

126 4.6 HowDoActions Work?

4.6.1How to Instantiate QAction Manually

We do nothavetouse theDesignereverytime,ofcourse, to create QActionobjects. Thefollowingexampledemonstrates howyou can create actions yourselfinthe constructorofaQMainWindowsubclass. Firstweinstantiate thedesired actionwithaniconand name,and assign it to the main windowas aparentobject.The QIcon() classencapsulates an icon:

QAction * action_open; action_open =newQAction(QIcon(":/pics/fileopen.png"), tr("&Open"),this); action_open->setShortcut(tr("Ctrl+O")); action_open->setStatusTip(tr("Opensanexisting file."));

In contrast to QPixmap, an action can take in imagesfor various states (normal, active, andgrayed out) andstages(selected or notselected). If theclass contains just oneimage—asinthiscase—it triestocalculate theicons for theother states andstagesfromthe icon specified. BymeansofsetShortcut()wecan setthe corresponding keybinding as astring. Together withthe translationfunction tr(), this hasthe advantage that thetrans- lators can select an appropriate shortcut for theOpenaction in therespective language.setStatusTip()sets thetextthatappearsinthe status barifthe mouse is held over theaction. To integrate actions directlyinto themenubar,the followingcode suffices: menuBar()->addAction(action_open); menuBar(), amethod of QMainWindowreturnsthe main windowsmenubar.The procedurejustshownisratherunusual, however.Normallymenus arefirstinserted into themenubar,and then theactions areintegrated.The QMenuinstancerepre- sentingthe menu can be generated via theaddMenu() FactoryMethod .Weinsert theOpenactionintothisasfollows:

QMenu * menu_Datei =menuBar()->addMenu(tr("&File")); menu_Datei->addAction(action_open);

Foractions that areusedasinformation carriersfor various widgets simultaneously, you should particularlythinkabout theparenthood in theobject modelofQt. The main window,asthe fatherofall otherwidgets,isalwaysthe last to be deleted. So that Qt willdelete actionobjectsaslateaspossible,itisbesttoturntheminto direct descendants of themainwindow. If actionsare used in several windowssimultaneously,there arebasicallytwoal- ternatives:Eitheryou duplicateall actions for each window,oryou turn them into childrenofQApplication. In this caseyou can simplypassqAppasaparenttothe QAction constructor.

127 4 Developing aGUI Application BasedonaMainWindow

4.6.2Selectable Actions

Some actions containabinarystate. Atypical exampleofthisisaword-wrap function in CuteEdit. So that theusercan seewhether this is activeornot,we convertittoaselectable actionwithsetCheckable():

QAction * action_linebreak; action_linebreak=newQAction(tr("&Line break"),this); action_linebreak->setCheckable(true);

Itsstatuscan be queriedwithisChecked(). Although aselectable actionautomati- callychangesits stateeverytime it is selected,you can alternativelychange it with setChecked()instead.

4.6.3Grouped Actions

Several selectable actions can be groupedtogether so that theusercan alwaysac- tivateonlyoneofthem. This function is particularlyfamiliarfromwordprocessing programs, in which you can exclusivelyselect from textalignment to theleft, to the right, or centered.Ifyou select oneofthese actions,the others areautomatically deactivated. To implementthisinQt, allstates mustbeavailable as QAction instances. Butfirst wecreateaQActionGroupobject,which wepasstothe QAction constructorasa parentwidget. This causesthe actiontobeautomaticallyinserted into theaction group. If wenowmake each individualelement in thegroup selectable,the actions arelinkedtoeach other:

QAction * act_alignleft; QAction * act_alignright; QAction * act_aligncenter; QActionGroup * aligngroup =newQActionGroup(this); act_alignleft=newQAction(tr("Align &right"),aligngroup); act_alignright=newQAction(tr("Align &left"),aligngroup); act_aligncenter=newQAction(tr("&Center"),aligngroup); act_alignleft->setCheckable(true); act_alignright->setCheckable(true); act_aligncenter->setCheckable(true);

Since QActionGroupisjustanadministrationclass,wemustinsertthe individual actions manually,withaddAction(), into theappropriate menu or toolbar. If the user nowselectsanaction, theclass emitsthe triggered()signalwiththe selected actionasanargument.Withthis, amatchingslotcan decide howto react to the corresponding action, such as bycomparingthe pointer to thepassedonQAction instance withthe actions produced.

128 4.6 HowDoActions Work?

Property Getmethod Setmethod Description Table4.2: texttext()setText(const Shortdescription of ac- Importantproperties QString&) tion,usedasamenu of QAction text, for example icon icon() setIcon(const Icon that symbolizes the QIcon&) action iconTexticonText()setIconText(const Textthatfits in theicon QString&) or underneaththe icon; if notset, text()isused shortcut shortcut() setShortcut(const Shortcut QKeySequence&) statusTipstatusTip()setStatusTip(const Longer textthatthe sta- QString&) tusbar showswhenthe mousepassesover it whatsThis whatsThis() setWhatsThis(const Extensivehelptext QString&) that is displayed in the What’s This? mode12 toolTiptoolTip()setToolTip(const Textdisplayed floating QString&) belowthewidgetthat hasrecordedthe action fontfont()setFont(const Specifies thefontprop- QFont&) erties for menu entries enabledisEnabled()setEnabled(bool)Ifthisisfalse,the action is grayed andcannotbe selected visibleisVisible() setVisible(bool)Ifthisisfalse,the action is notdisplayed checkable isCheckable() setCheckable(bool)Ifthisistrue, theaction can be switched on and off ( toggled)(for ex- ample, bold typeface in awordprocessing pro- gram)

12 In the What’s This? mode theapplicationdisplaysaninformation textfor eachselectedentry, which canbedefinedfor eachwidgeta✞ nd fo☎ re✞ ac☎hQAction withsetWhatsThis(). Users can navigatetothe What’s This?mode via ✝ Shift ✆+ ✝ F1 ✆ or via aQAction,which generatesthe call QWhatsThis().createAction(). It mustbeinsertedexplicitlyinto theHelpmenu.

129 4 Developing aGUI Application BasedonaMainWindow

continued Property Getmethod Setmethod Description checkedisChecked()setChecked(bool)Defineswhether atog- gled actionison(true)or off

4.7DockWindows

In some cases it is useful,apart from simple actions,toalsogroup together more complexwidgets so that theusercan place them either inside themainwindow or separatefromthis. Such so-called dockwindows arealsoprovided byQt 4: The classresponsible for this is calledQDockWidget. Users areespeciallyawareofthese in developmentenvironmentssuchasMicrosoft Visual C++orthe Qt Designer,13 which arrangeall theirtoolswithinthe main window.Ascan be seen in theexampleofthe Designer in Figure 4.11, theusercan dock them in thesamewayas toolbars to side areasofthe window,orposition them floatingover themainwindow.Iftheyaredocked, thelatter haveasplitter (see Chapter 5.3onpage 150), withwhich theusercan fine-tune thesizeratio of thedockwindowto themainwindow,ifnecessary.Dockwindowsalsohavea handle (similartotoolbars), withwhich theusercan movethemtoanother margin of thewindowor,ifhepulls them outofthe window,make them independent.

Figure 4.11: TheDesigner with a floatingwidgetbox. Allother dock windowsare stuckto theright margin.

To theright of thehandlethere aretwominiaturized buttons.The left onemakes thewindowindependent, while theright oneclosesit, whether or notitiscurrently dockedtothe main window.Closing it doesnot delete theQDockWindowinstance though,but merelyhidesit, usinghide()(inherited from QWidget).Twomethods

13 ForDesigner, onlywhile in thedockwindowmode,which canbeset via Edit→ UserInterface Mode→ DockedWindow.

130 4.7 Dock Windows

areavailable to theprogrammerifhenowwants to displaythem again: He can either make thewidgetitselfvisibleatanytime via show(), or he can usethe toggleShowAction() method, as alreadyused for toolbars, which then generates a corresponding entryin menus or toolbars. Thedeveloperhas theoptionofrestricting aseriesofprivileges that QDockWid- getprovides to theuser. Theseprivileges aredescribedinthe enumeratorQDock- Widget::DockWidgetFeatures. Theycan either be defined as apropertyin theQt Designer or bypassing acombinationofenumeratorelementstothe setFeatures() method:

DockWidgetClosable Determines whether adockwindowmaybe closed DockWidgetMovable Specifies whether adockwindowmaybe moved DockWidgetFloatable Defines whether adockwindowmayfloat

If you wanttorestrictprivileges,you mustinterpret theseDockWidgetFeaturesas bitflags that can be combined withalogical OR. This can be donebecause the enumeratorhas valuesthatare basedonthe power of 2, which arereferredtoas “bit flags.” 14 To preventthe user from closingand removingthe dockwindow,the followingcall is sufficient dockWindow->setFeatures(QDockWidget::DockWidgetClosable| QDockWidget::DockWidgetFloatable);

To removeorassign allfreedoms, theQtadditionallyprovides theenumerated val- uesQDockWidget::NoDockWidgetFeaturesand QDockWidget::AllDockWidgetFeat- ures. Enumerators that areusedasbit flags areanelementaryconceptinQt, andwewill come acrossthemfrequently.

4.7.1Positioning DockWindows

Anotherimportant differencebetween toolbars anddockwindowsistheir posi- tioning. As can be seen in Figure 4.12, QMainWindowdivides itsmargins into two

14 Unixusersknowthis from thefile permissionsinthe filesystem. Unix-based operating systems form thepermissionsfor reading, writing,and executing(rwx)fromthe powersoftwo, usinga single digit. Afile withaccess permissions5is readable (4)and executable (1), butnot writable (2).

131 4 Developing aGUI Application BasedonaMainWindow

rings; toolbars arealwayslocated directlyon thewindowmargin,but dockwin- dowsare alwaysinsidethe margin.

Figure 4.12: Themainwindow takes in toolbarsand dockwindowsintwo ringsasdecorations on themargin.

Dock windowshaveatitleand achild widget, so theircontents can be arranged in anywayyou like. With setWidget()weinsertthe widgetcreated specificallyfor thedockwindow.The heading setwithsetWindowTitle() appears as thetitle of the dockwindow. addDockWidget()thenintegrates thedockwindowinto themainwindow.Asthe first parameter,the method expectsthe positionofthe tool window,because,in contrast to toolbars, Qt doesnot specifyastandardpositionfor dockwidgets. Whichone you choosedepends heavilyon theintendedpurpose. In thecode exampleonpage 133we“stick” thewindowto theleftmainwindowframe, with Qt:LeftDockWidgetArea.The DockWidgetAreasenumeratorisusedtospecifythe location,and is describedinTable 4.3.

Table4.3: Value Position The DockWidgetAreas Qt::LeftDockWidgetArea Vertically,onthe left side of themainwin- enumerator dow,but to theright of anypossible menu bars Qt::RightDockWidgetArea Vertically,onthe rightsideofthe main win- dow,but to theleftofanypossible menu bars Qt::TopDockWidgetArea Horizontally,ashighupaspossible butbe- neaththe menu barand anypossible icon bars Qt::BottomDockWidgetArea Horizontally,aslowdownaspossible,but abovethe status barand anypossible icon bars Qt::AllDockWidgetAreasAll previous positions;not permissible for ad- dDockWidget()

132 4.7 Dock Windows

As the second argument,addDockWidget()expectsapointer to theQDockWidget to be inserted.Duringthe instantiationofthe dockwindowon page 133, wecould leaveout details of aparent, because addDockWidget()not onlyintegrates the windowgraphicallyinto themainwindow,but also transfersthe parenthood for thedockwindowto themainwindow. Since dockwindowsusuallycontainmorecomplexwidgets,horizontaldocking is generallyrecommended—thatis, positioning on theleftorright side of thewin- dow.The upperand lower sidesofthe main windowareonlyrarelysuited to docking.The areasinwhich thedockwindowmayreside areagainsummarized by theenumeratortype Qt::DockWidgetAreas; which of them areactuallypermitted is determinedbysetAllowedAreas(). Membersofthe enumeration areusedasan argument,and theyarelinkedwithalogical OR. To preventdocking on theupper andlower windowmargin,for example, weextendthe constructorfromthe above exampleasfollows: dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea| Qt::RightDockWidgetArea);

Normallytheusermayalso positionthe dockwindowas floatingover thewindow, insteadofdocking it.The callsetFloating(false) prevents this—the corresponding dockwindowthen sticks onlyto thewindowsidesallowed bysetAllowedAreas(). As hasalreadybeen indicated, dockwindowscan also be closed.Justlikemenu bars, theythereforehaveamethod calledtoggleViewAction(). Theexamplefrom page 126 works here in thesameway:Ifitisincludedinthe menu bar, theuser can make thedockwindowreappearatanytime throughthe entrygenerated with this.

4.7.2ADockWindow forOur Editor

Nowthat wehavehad agood look at thetheoryof dockwindows, wewillturnour attention to asmall example. Oureditorshouldbegiven alistoftemplates which can be inserted byclicking thecurrent cursor position. To do this,wegenerateanewdockwindowin theDesignerand giveit, together withthe texteditor, ahorizontallayout.15 In thePropertyEditorweallowdocking for it onlyon theright andleftsides of themainwindow.Finallywegivethe object aname: TemplateDocker. Alternatively,wecan generate thedockwindowin theconstructor of theMain- Windowas follows:

QDockWidget * templateDocker=newQDockWidget; templateDocker->setAllowedAreas(Qt::LeftDockWidgetArea| 15 TheDesignerinterpretsdockwindowsindesignmode as widgets in thecentral widget.

133 4 Developing aGUI Application BasedonaMainWindow

Qt::RightDockWidgetArea); templateDocker->setObjectName("TemplateDocker"); templateDocker->setWindowTitle(tr("Templates")); addDockWidget(Qt::LeftDockWidgetArea,templateDocker);

QListView * view=newQListView(); templateDocker->setWidget(view);

newTemplateHandler(view,textEdit,this);

Themainwindowrequires theobject name to savethe windowproperties. We willdiscuss this topicatthe endofSection 4.8. If thenameismissing,Qtcom- plains at runtimeonthe standardoutput. Unfortunately,itisnot possible to set thewindowTitle attribute of QDockWidgetinthe Designer,which is whyit is im- portant that this mustbedoneseparatelyin theconstructor.windowTitle labels thewindowandalsogives anametothe toggleactionthatisgenerated bytog- gleViewAction(). In thefinalstep webreathe lifeintothe widgetbyfillingitwithalistview.Wewill later findthe templates in this view.The TemplateHandler classnowinstantiated is responsible for fillingthe listand for insertingtemplates at thecurrent cursor positioninthe editor window:

// cuteedit2/templatehandler.cpp

TemplateHandler::TemplateHandler(QListView * view,QTextEdit * textEdit, QObject * parent) :QObject( parent),mTextEdit(textEdit) { mModel =newQStringListModel(this); QStringList templates; templates<< ""<< ""<< ""<< ""; mModel->setStringList( templates); view->setModel(mModel); connect(view,SIGNAL(clicked(const QModelIndex&)), SLOT(insertText(const QModelIndex&))); }

In Qt 4, listviewsworkonthe basisofthe model/view principleintroducedin Chapter 8: A model is responsible for obtainingdata, while the view displaysthe data. In thecaseofour templates,one modelisenough, which takesdatadirectly from aQStringList.Asbefore, theseare fedwithseveral templates,inthiscasefor HTML.16. We passthe listcreated in this wayto themodelvia setStringList()and turn this into thereference modelfor ourview,the listview.The listviewis nowfilled, and

16 In aproperapplicationthe templatesare notcompiledstatically,ofcourse, butare loaded from afile.

134 4.7 Dock Windows

wejustneed to includethe selected templateinthe editor window.Todothis, weconnect theclicked()signalofthe viewandimplement theinsertText()method (which wemustfirstdeclare in theclass definitionasaslot,ofcourse):

// cuteedit2/templatehandler.cpp (continued) void TemplateHandler::insertText( const QModelIndex&index) { QString text =mModel->data(index,Qt::DisplayRole).toString(); QTextCursorcursor=mTextEdit->textCursor(); cursor.insertText(text); mTextEdit->setTextCursor(cursor); }

Themodelindexpassedrepresentsthe selected lineinour model. Using thedata() method, wecan obtain thedataasQVariantfromthis, which wemuststill convert into aQString.QVariantworks in asimilarwayto aunion in C++. Theclass can also convertvarious types—both Qt-specificdatatypessuchasQString andQSize, as wellasC++ typessuchasint or double—fromone to another.

Figure 4.13: Thetemplate dock window in docked condition: Aclick insertsthe text into thecorresponding lineinthe editor window.

Themodel/viewconceptofQthas manydifferent roles for amodelindex(see table 8.1onpage 209; forinstance, manyviewscan displayan icon (Qt::DecorationRole), in additiontonormaltext(Qt::DisplayRole). At themoment, however,onlyQt:: DisplayRole is relevanttous.

135 4 Developing aGUI Application BasedonaMainWindow

ThetextCursor()method of thetextwindowrepresents thecurrent positionofthe writing cursor. 17 We passthe text, which it should insert at thecursorposition, to theinstance. Nowwemustinsertthe textcursortothe currentcursorposition again, usingsetTextCursor, to updatethe cursor. 18 Ourdockwindowis nowcompletelyimplemented.Thankstothe QObject base classand thefact that wepassthe main windowas theparentobject,wedonot need to delete theinstanceofTemplateHandler manually.The result is shownin Figure 4.13.

4.8SavingPreferences

Last butnot least, ourprogram should be able to keep thesettings made bythe user,even after theprogram is restarted.Tothisend,different conventionshave become establishedondifferent operating systems. Dependingonthe platform, applicationdatamaybe stored in theWindowsReg- istry(inthe user scope HKEY_LOCAL_MACHINE\Software) or in thesystem scope HKEY_CURRENT_USER\Software),inanXML-based .plist fileunder MacOSX,orin /etc/xdg,19 (system-widesettings)or~/.config(user-definedsettings)under Unix. Qt encapsulates accesstothese configuration storage systemswiththe help of theQSettingsclass.Everyfilingsystem in this is a backend .QSettingsobjects can be created either on theheaporonthe stack.Since littleworkisneeded to instantiatethem, werecommend that you create them on thestack, if this is necessary. In casetwoormoreQSettingsinstances areworking withthe same data, theclass ensuresthatdatabetween different instancesisalwayscorrectlysynchronized,in casetwoormoreQSettingsobjectsare working withthe same file. Thesame appliesfor twothreads,bothofwhich containaQSettingsobject withthe linkto thesamefile,and even for twodifferent processes, in casebothare usingQSettings linkedtoacommonfile.Qtusesinternallocking mechanisms for this purpose. TheQSettingsconstructor normallyrequires twoparametersfor theinstantiation in ordertogeneratethe appropriate entryin theconfiguration storage system:the name of theorganizationfor which theprogrammerworks,and thenameofthe program.InWindows,

17 TheQTextCursorclass in generaldoesnot havetodescribe thecurrentlyvisible cursor,but it canmanipulatetextatanypositionatall. 18 This is necessarybecauseQTextCursorworks notinapointer-based manner, butinavalue- basedone,and wetherefore workwithacopycreated withthe allocation to thecursorvariable. 19 Thedirectoryname standsfor an abbreviation of XDesktop Group thenow-obsolete generictermfor theFreedesktop.orgdevelopers.See alsohttp://www.redhat.com/archives/xdg- list/2003-March/msg00041.html.

136 4.8 Saving Preferences

QSettingssettings("OpenSourcePress","CuteEdit"); would referencethe registrypath

HKEY CURRENT USER\ Software \ OpenSourcePress\ CuteEdit

If aprogrammergenerates such QSettingsinstances at manylocationsinthe code, it would be agood idea nottohavetoconstantlypassthe parameters. This is possible if wefeed theapplicationitselfwithprogram andorganizationdetails, preferablystraight in themain() function:

QCoreApplication::setOrganizationName("OpenSourcePress"); QCoreApplication::setOrganizationDomain("OpenSourcePress.de"); QCoreApplication::setApplicationName("CuteEdit");

From nowon,QSettingsmakesuse of thesedetails,sothataninstancewithout parametersisall that is needed:

QSettingssettings;

It is surprising that setOrganizationDomain() method exists,since wehavejust managedwithout it.But it is justified throughthe waythat MacOSXstores its settings: it triestosortthe organizations according to an inverted domainname pattern. If thedomaindetails aremissing,QSettingscreates artificialdetails from theorganizationname. If setOrganizationDomain() is specified correctly,the file- namesinOSXareasfollows:

$HOME/Library/Preferences/de.OpenSourcePress.CuteEdit.plist $HOME/Library/Preferences/de.OpenSourcePress.plist /Library/Preferences/de.OpenSourcePress.CuteEdit.plist /Library/Preferences/de.OpenSourcePress.plist

It is notabsolutelyessentialtospecifythedomain, butitshouldnot be left out in casethe organizationhas arealdomainname. Thefirsttwoparts specifythe user scope,and thelasttwospecifythesystem scope,adistinctionthat—as hinted above—concerns allthree platforms. In theuserscope (QSettings::UserScope)anapplicationsaves allthe applications involvingjustthatuser, while in thesystem scope (QSettings::SystemScope)itsaves datathatare important for allusers. Because writing in thesystem scope generally requires root or administrator rights,the followingconstructor is normallyrelevant onlyfor installation programs:20

20 Never assume that theuserhas administrator rights,even if this is standardpracticeinmany Windowshomeinstallations.

137 4 Developing aGUI Application BasedonaMainWindow

QSettingssettings(QSettings::SystemScope);

QSettingsnowignoresthe user scope andreads andwrites exclusivelyin thesys- tem scope.Ifyou specifyQSettings::UserScope instead, theclass behaves as if it was calledvia thestandardconstructor.QSettingslooksinthisfor asetting, first in theuserscope.Ifthe object is notfound there, it then looksfor it in thesystem scope. To write theactualdata, QSettingsprovides thesetValue() call, which expectsa keyandthe actualvalue.The value itself is of theQVarianttype,withwhich we arealreadyfamiliar. Thefollowingcode first stores avalue in thesystem-specific configuration backend andthenreads it out:

// configtest/main.cpp

// manufacturer,product QSettingssettings("OpenSourcePress","ConfigTest"); QString hello ="Hello, world!"; // storeavalue settings.setValue("Greeting",hello); // resetvariable hello =""; // readvalueand assign tovariable hello =settings.value("Greeting").toString(); qDebug() << hello;//prints "Hello, world!"

Theexplicit conversion to aQString usingtoString()isnecessarybecause C++is notinapositiontocorrectlyconvertthe QVariant value returned byQt because QStringhas no knowledge of QVariant,and thus it doesnot provideanassignment operator. Afteritisrun, theprogram generates afile in Unixcalled~/.config/OpenSource Press/ConfigTest.conf withthe contents

[General] Greeting=Hello, world!

Since wehavenot specified anygroup, QSettingsstoresthe keyin the[General] standardgroup.There aregenerallytwomethods of naming aspecific group. On onehand, wecan specifythedesired groupbeforeone or more setValue() calls,but wemustremovethissettingafterwardifwewanttocontinue usingthe object for otherpurposes:

settings.beginGroup("MyGroup"); settings.setValue("Greeting",hello); settings.endGroup();

138 4.8 Saving Preferences

On theother hand,wecan simplyplacethe name of thegroup in frontofthe key, separated byaslash: settings.setValue("MyGroup/Greeting",hello);

In both cases theresultlookslikethis:

[MyGroup] Greeting=Hello, world!

UnderWindows, groups aresubpaths of thecurrent applicationpathinthe Reg- istry,whereas MacOSXstructures them throughXML tags.

4.8.1Extending CuteEdit

To useQSettingsinCuteEdit, wefirstset up twomethods for reading andwriting in MainWindow:readSettings() andwriteSettings(). We callwriteSettings()inthe destructor.Thisgenerates anewQSettingsobject and saves thesizeofthe currentwindowin theSizekeyof theMainWindowgroup. In thenextstep wesaveall internalsettings for theMainWindow;for instance,the positions of thetoolbars anddockwindows. To do this,QMainWindowprovides thesaveState()method, which converts thesepropertiesintoaQByteArray:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::writeSettings() { QSettingssettings; settings.setValue("MainWindow/Size",size()); settings.setValue("MainWindow/Properties",saveState()); }

We callits counterpart,readSettings(), as thefinalstep in theconstructor of the class. It reads thesettings andappliesthemtothe finishedmainwindow,using restoreState(). restoreState()restoresthe internalstatusofthe main window,us- ingthe read-outQByteArray.But first wemustconvertthe QVariant returned by value() into aQSizeorQByteArray:

// cuteedit2/mainwindow.cpp (continued) void MainWindow::readSettings() { QSettingssettings; resize(settings.value("MainWindow/Size",sizeHint()).toSize());

139 4 Developing aGUI Application BasedonaMainWindow

restoreState(settings.value("MainWindow/Properties").toByteArray()); }

Thesecondparameter that wepasstovalue()—sizeHint()—is also unusual. It is the defaultvalue if thebackendcannotfind thekey.Inspecific cases it ensuresthat theeditorwindowhasanappropriate initialsize.

140 r te ap 5 Ch

LayingOut Widgets

Even if you leaveituptoQttoarrange thewidgets in adialogormainwindow (aswehavedonesofar), thereisnothing preventing you from doing thelayout manuallyusingthe classlibraryin specialcases.Inpracticethisisseldom done, butacloser look at manuallayoutprovides an understanding of theQtlayout mechanism, which wewillexaminebelow.

5.1ManualLayout

In thetraditional version of GUI design,each widgetis“attached byhand”toa point in theoverlyingwindowor widget(that is,the widgetthathas been specified as aparentobject for thegiven GUI element) andfixed valuesfor itsheightand width aredefined. TheQWidgetclass provides thesetGeometry() method as abasis classfor nearlyallgraphical elements.Thisexpectsfourinteger parameters: first

141 5 LayingOut Widgets

thevaluesfor thexandypositions relativetothe parentwidget, followed bythe height andwidth.Atthispoint in time theparentwidgetdoesnot havetodisplay itsownfinalsize. As an example, wecan look at awindowderived from QWidget(Figure 5.1):

// manually/window.cpp

#include #include "window.h"

Window::Window(QWidget * parent) :QWidget(parent) { setFixedSize(640,480);

QTextEdit * txt =newQTextEdit(this); txt->setGeometry(20,20,600,400);

QPushButton * btn=newQPushButton(tr("&Close"),this); btn->setGeometry(520,440,100,20); }

Figure 5.1: Asimple, manually laid outwidget

ThesetFixedSize()method instructsthe windowto acceptafixed,unchanged size. Then wepositionaneditorwindow(a QTextEditwidget 1 )and abutton. From thesesetGeometry() calls it is alreadyevident that it is quitedifficulttoguess thecorrect values. Getting alayoutconstructed in this mannertoworkisacontin- uous cycleofchoosingcandidatevalues, compiling, andthenadjusting thevalues to improvethe appearance. It can also be quiteawkwardifthe widgetordialog

1 Forall thosewho havenot (yet)read,orsofar merelybrowsedthrough Chapter4:The QTextEdit classprovides amultiple-lineinput field fortext, which canbeformattedvia theAPI. In addition to pure text, it canalsoloadstructuredHTML.

142 5.2 AutomaticLayout

changes: If you wanttoadd anewbuttoninthe middleofanarrangement,for example, thepositionofall elements placed beneaththe newelementmustbe modified. Now,itcan be argued that none of this is aprobleminpractice, sincethe Qt Designerconsiderablysimplifies thepositioning workinvolved.But even aGUI designer cannotsolveall problems without usingautomatic layouts. Oneofthese problems concerns widgets that would look better if theycouldshrink or grow:Inaninflexible layoutand without additionalaids, such elements—like the editor windowin theexample—alwaysretain thesamesize, although it would be nice if theywould adjusttothe available screen sizeoratleast givethe user the optionofchangingtheir dimensions. To keep thesizeofthe dialog flexible,wecould replacethe setFixedSize()callwith theresize()method, which also expectstwointeger parametersoraQSizeparam- eter.Thisonlyadjusts thesize, anddoesnot fixit.The user can nowchange the dimensions of thedialogwiththe mouse, although thewidgets that it contains retain theirdimensions. Alternatively,you couldreimplement theQWidgetmethod resizeEvent(): Qt always invokes this method whenthe widgetsizechanges.You couldwrite code to com- pute thenewsizes andpositions of thewindowelements on each resizeevent. Butthisprocedure is muchtoo complexin most cases,and also requires manual calculation of thewidgetproportions.2 In addition,reimplementingresizeEvent()poses aparticularproblemincombina- tion withinternationalization: With localized software, thedimensionsofalabeled widgetmaydepend on thelanguage in which it is displayed.Abuttoncalled Close in Englishhas amuchlongerlabel in theGermantranslation ( Schließen), andthe textwill be cutoff unlessspecial precautionarymeasures aretaken. Ultimately,wecan onlypatch up thesymptoms in this way.Toactuallysolvethe underlyingproblem, wecannotavoidusing automaticlayout.

5.2Automatic Layout

TheQLayoutclass andspecialized layouts derived from it help thedeveloperto positionwidgets dynamically.For this to succeed,each graphic elementderived from QWidgethas asizeHint()method, which returnshowmuchspacethe widget would liketooccupyunder normal circumstances.Inthe same way,there is a minimumSizeHint()method—awidgetmayunder no circumstances be smallerthan thevalue returned byminimumSizeHint(). BothsizeHintand minimumSizeHintare properties, which can be changedwiththe corresponding setmethod.

2 In some casesthisprocedure is veryuseful,however.Anumber of KDEprogramsuse re- sizeEvent()todisplaystatuswindowsonthe currentlayoutatthe lower-right edge of the window.

143 5 LayingOut Widgets

Each widgetalsohas a size policy ,which thedevelopercan setfor thehorizontal andvertical valuesusing setSizePolicy(). Thepurposeofthiscan best be explained bymeansofanexample: TheQTextEditobject from Figure 5.1should, if possible, useall of thespaceinthe windownotrequiredbyotherwidgets—thatis, thefully available width andheight. Since this appliesnot onlyhere,but in generalfor editor windows, thestandardsettingfor this widgettype defines thesizepolicy QSizePolicy::Expanding for both directions (thatis, “windowsfor this widgettype should expandasmuchaspossible”). Abutton, on theother hand,shouldonlytake up as muchspaceverticallyas is specified in thesizeHint(). This is ensuredbyQSizePolicy::Preferred(that is,widgets of this type should occupytheideal size, if possible). QPushButtons expandin width as far as possible,because for this directionTrolltechspecifiesQSizePol- icy::Expanding.

Figure 5.2: QLayout Alllayouts inherit from the QLayout baseclass. QBoxLayout QGridLayout QStackedLayout

QHBoxLayout QVBoxLayout

5.2.1Horizontal andVertical Layout

QLayoutasanabstract base classonlycoversthe basicfunctionsfor layouts.Spe- cific strategies, such as thealreadyfamiliarhorizontalorvertical layout, arelooked after bythespecial Qt clustersshowninFigure5.2 inheriting from QLayout. Thus theQVBoxLayoutclass,usedinthe exampleonpage 29 andthe following pages, arranges widgets amongthemselves,vertically.Herethe orderinwhich the widgets areincludedinthe layoutusing addWidget()iscrucial. Theexamplefrompage 142nowappears as follows:

// vertically/window.cpp

#include #include "window.h"

Window::Window(QWidget * parent) :QWidget(parent) { resize(640,480);

QVBoxLayout * lay=newQVBoxLayout(this);

144 5.2 AutomaticLayout

QTextEdit * txt =newQTextEdit(this); lay->addWidget(txt);

QPushButton * btn=newQPushButton(tr("&Close"),this); lay->addWidget(btn); }

Theresize()instruction is notabsolutelynecessary.Without it,Qtadds themini- mum sizes of theeditorwindowandthe buttonsuggested byminimumSizeHint() to the spacing inserted bythelayout, that is,the distance between twowidgets in alayout. In addition it addsamargin for thelayoutand fixes thewindowsizeto thetotal.

Figure 5.3: Thewidgetwitha verticallayout

Figure 5.3clearlyshowsthe weaknessesofthe vertical layout: Thebuttontakes over thefullwidth,which is notwhatwehad in mind. Thereare twowaysofovercoming this problem. In thefirstcasewemake useof somethingweare alreadyfamiliarwith, andtake alook at theAPI documenta- tion of QBoxLayout,3 theclass from which QVBoxLayoutinherits:The addWidget() method actuallyhastwoother parameters, stretchand alignment.The latter looks after thehorizontalalignment of awidget. It is nowpossible to arrangethe button correctly,thankstoQt::AlignRight. To do this,wesimplyreplacethe last twolines of code abovewiththe following:

QPushButton * btn=newQPushButton(tr("&Close"),this); lay->addWidget(btn, 0,Qt::AlignRight);

Youshouldtrythis method, particularlyif you hadtrouble withthe grid layout describedinChapter 5.2.2. Gridlayouts remain thebetter choice,particularlyfor

3 Seehttp://doc.trolltech.com/4.1/qboxlayout.html.

145 5 LayingOut Widgets

more complexlayouts,inwhich you can easilylose track of whatislined up where whenusing boxlayouts. Boxlayouts haveanadditional propertywhich wehavesofar ignored, theso- called stretch factor. If this doesnot equal0,itdeterminesthe proportional space occupied bythewidgetinthe overalllayout, in thedirection of theboxlayout. This assumes, of course,thatthe widgetisinterested in spreading outinthispar- ticulardirection.Itdoesnot make anysensefor abutton, for example, to stretch outverticallyabovethe height or belowthedepth of thetextorthe icon that it displays. If this should still be necessary,however,the size policy can be adjusted usingset- SizePolicy(). Themethod expectstwoparametersherefromthe QSizePolicy::Policy enumerator(seeTable 5.1),which definethe sizeguidelines for thehorizontaland vertical stretches.

Table5.1: ValueMeaning TheEnumerator Policy QSizePolicy::Fixed Thewidgetmaynever haveasizeother than sizeHint(). QSizePolicy::Minimum sizeHint()isthe smallest acceptable size for thewidget, butthe widgetmaybe en- larged as muchasyou want. QSizePolicy::Maximum sizeHint()isthe largestacceptable sizefor thewidget, butthe widgetmaybe re- ducedinsizeasmuchasyou want. QSizePolicy::PreferredsizeHint()isthe optimal size, butthe widgetmaybe either larger or smaller than this value (default for QWidget). QSizePolicy::Expanding As Preferred, butthe widgetdemands any available space in thelayout. QSizePolicy::MinimumExpanding As Minimum, butthe widgetabsolutely demandsanyavailable space in thelay- out. QSizePolicy::Ignored IgnoresizeHint()—thewidgetisgiven as muchspaceaspossible in thelayout.

Butlet’sreturn to thestretchfactor:Thisisillustrated bythefollowingcode ex- ample, which places fivestretchable textedits nexttoeach other, butassignsa different stretchtoeach of them:

146 5.2 AutomaticLayout

// stretchfactors/main.cpp

#include intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QWidgetw; QHBoxLayout lay(&w); QTextEdit * txtEdit=0; for(intstretch =1;stretch <= 5;stretch++) { txtEdit=newQTextEdit(&w); lay.addWidget(txtEdit,stretch); }

w.show();

returna.exec(); }

We chooseahorizontallayoutinthisexampleand insert dynamicallygenerated textfields into it.These containastretchfactor of 1to5,accordingtothe status of theloop counter.Ascan be seen in Figure 5.4, thewidgets nowtake up more space,increasingfromlefttoright according to theirfactor.Thus thesecondtext field hastwiceasmuchspaceasthe first one, thethird,three timesasmuch, and so on. Thetexteditbehaves strangelyas soon as thehorizontalwindowsizeisreduced: Althoughall widgets become proportionatelysmaller, theyalwaystryto attain theirsmallest possible size(minimumSize()), which is alwaysthe same despitethe stretchfactor.Therefore, allthe textfields in ourexampleare thesamesizeassoon as thewindowhasreached itsminimum size.

Figure 5.4: Stretchfactors provideindividual widgets with more space.

While horizontalstretchesseldom cause problems,hardlyanywidgetwants to be stretchedvertically.However,ifthe user expands adialoglengthways, thelayouts insert uglyspacesbetween allthe GUI elements containedinthe dialog window. This can also be avoidedusing manuallydefined spaces. Oneapproachistodefine astretchfactor in oneofthe addWiget()calls for asingleposition, to definea

147 5 LayingOut Widgets

preteterminedbreaking point,sotospeak.Analternativeistouse addStretch() to add astretchofanysizetothe endofthe layout(that is,atthe lower or rightedge, dependingonthe layouttype).

5.2.2GridLayout

Thebestwayto describe thegridlayoutinQtisprobablyas akindoftable,such as frequentlyencountered,for example, in HTML or spreadsheet calculations. In contrast to QBoxLayoutderivatives,the grid layoutclass QGridLayoutalsorequires information on the column and row in which thelayoutshouldinsertawidget. As can be seen in Figure 5.2onpage 144, QGridLayoutinherits directlyfrom QLay- out, so it haspropertiesdiffering from thoseofQBoxLayout-basedlayouts. In particular, theseinclude anotheraddWidget()method that requires,inaddition to thewidgettobeinserted,atleast twomoredetails,namelytherowandthe column of theinsertion point.For widgets that should take up more space than one cell, an overloadedversion of themethod exists that expectsfourextraparameters: thecoordinates of thefirstcelland thenumber of cells that thewidgetshould cover in each direction. In addition thesetColumnStretch() andsetRowStretch() methods allowstretchfac- tors to be setfor individualcolumns or rows. Here thefirstparameter specifies the rowor column, andthe second parameter specifies therelevantstretchfactor. Thefollowingexampleimplementsour inputdialogusing agridlayout. Through addWidget(), it positions thetextfieldatthe coordinates (0, 0) andspecifiesfor it awidth of twocolumns andaheight of onerow. Thebuttonisplacedonthe second rowandinthe second column, withcoordinates (1, 1),because westart from zerowhencountingpositions,asisusual in informa- tion technology.Stretching thefirstcolumn withaddColumnStretch() then ensures that thesecondcolumn, in which thebuttonislocated,issquashedup. Using this trick, thelayoutisrestricted to theoptimal width:

// grid/window.cpp

#include #include "window.h"

Window::Window(QWidget * parent) :QWidget(parent) { resize(640,480);

QGridLayout * lay=newQGridLayout(this);

QTextEdit * txt =newQTextEdit(this); lay->addWidget(txt,0,0,1,2);

148 5.2 AutomaticLayout

QPushButton * btn=newQPushButton(tr("&Close"),this); lay->addWidget(btn, 1, 1); lay->setColumnStretch(0,1); }

5.2.3NestedLayouts

Sometimesitisusefultonestlayouts inside oneanother,for instance if you need to includeanewlayout, withall itswidgets,inanexisting one. Forthisreason, QLayoutclassesprovideawayof includingother layouts,withaddLayout(). This method expectsthe same parametersasthe addWidget()method of thesamelay- outobject. Formorecomplexlayouts in particular,the clearhierarchycreated in this wayturns outtobeveryuseful,especiallyif you wanttoarrange several buttons,asinthe followingcode:

// nested/main.cpp

#include intmain(intargc, char * argv[]) { QApplication app(argc, argv); QWidget * w=newQWidget; QHBoxLayout * mainLayout =newQHBoxLayout(w);

QTextEdit * txtEdit=newQTextEdit(w); mainLayout->addWidget(txtEdit);

QVBoxLayout * buttonLayout =newQVBoxLayout; QPushButton * cancelBtn=newQPushButton(QObject::tr("&Cancel"),w); QPushButton * okBtn=newQPushButton(QObject::tr("&OK"),w); QPushButton * defaultBtn=newQPushButton(QObject::tr("&Default"),w); buttonLayout->addWidget(defaultBtn); buttonLayout->addWidget(cancelBtn); buttonLayout->addWidget(okBtn); buttonLayout->addStretch();

mainLayout->addLayout(buttonLayout); w->show(); returnapp.exec(); }

Byplacingthe individualbuttons in aseparatelayout(buttonLayout),theyare made to appearasone unittothe overlyinglayoutmainLayout. Youcan nowuse addLayout()toinsertthe buttonLayoutintomainLayout.

149 5 LayingOut Widgets

Within buttonLayout, useisagainmade of addStretch(): Thevariable emptyspace created bythis forcesthe buttons upwards andtakesupthe remainingspace.

5.3Splitter

Althoughhorizontaland vertical layouts aredynamic, theycannotbechanged directlybytheuser. Butsometimesheshouldbeable to adjustthe spacing between twoormorewidgets interactively. This need is fulfilledbytheQSplitter classthat, just likethe standardlayouts,have an addWidget()(butnoaddLayout()) method. Thepartial widgets inserted using this method areseparated byaso-called handle,which can be pickedupand moved byusingthe mouse(Figure 5.5).

Figure 5.5: Twotext fieldsmoved with thesplitter

In contrast to theQBoxLayoutclass,there arenospecialized classesfor QSplitter that determine whether vertical or horizontallayoutisused. Insteadthe orien- tation propertydeterminesthe alignment.Itcan be setinthe constructor, or set later on.Ifnoorientation is specified,Qtcreates horizontalsplitters.

5.3.1BehaviorDuringSizeChanges

Thefreedom of movementallowed theuserbythesplitter is restricted bythe widgets involved:The smallest sizeisspecifiedbytheminimumSizeHintor(if set) theminimumSizeproperty.Ifthe user triestoshrinkthe widgetmorethanthis, the splitter is completelyhiddenbythewidget. This is knownasacollapsible widget. If you wanttopreventthe user from so “gettingrid of thewidgets,” you can disable this behaviorwithsetCollapsible(0,false), where 0stands forthe first widgetfrom theleftfor ahorizontalsplitter,or, withvertical splitters, for thetop widgetinthe splitter.

150 5.3 Splitter

TheisCollapsible() method, which takesone integer argument,provides information on whether thewidgetwiththe specified number is collapsible or not. Another propertyof theadjacentwidget, maximumSize, ensuresthatthe corresponding area abovethe splitter cannotbemade anysmalleroncethe neighboringwidget hasachieved itsmaximum size. Splitterscan react in twowaysifthe user pulls thehandleinone directionwhile holdingdownthe mousebutton: Theyeither drawagraylineatthe point where thehandlewould come if themouse buttonisreleased, or else actuallymove thehandletothe corresponding location.Thislatter method is knownasopaque resizing(that is,asizechangethat“lets no light in”). Normallyopaqueresizingisabetter choice,since theusercan directlyseethe re- sultsofhis actions.Since this techniquecan often triggeraresizeEvent()under certaincircumstances,however,uglyartifacts can appear if oneofthe widgets controlledbythesplitter performs verycomplexdrawingoperations, or is notop- timallyprogrammed. In this caseitisoften better to disable opaqueresizing, with setOpaqueResize(false).

5.3.2SavingSplitterPositions andDeterminingthe Widget Size

To savethe positions of individualsplittersbeyondprogram sessions, theQSplitter APIprovides themethods saveState()and restoreState(). Since saveState()storesall valuesinaQByteArray,the method is ideallysuited to savingthe sizes of asplitter between oneprogram session andthe next. This is doneusing theclass presented on page 136, QSettings. If wehadn’t implemented thetemplates in CuteEditasdockwindowsinChapter 4, butseparated them from thetextfieldwithasplitter,wecould savethe valuesofasplitter as akey/value paircalledSplitterSizes in theconfiguration file, withthe followingcode:

QSettingssettings("OpenSourcePress","CuteEdit"); settings.setValue("SplitterSizes",splitter->saveState());

Conversely,the followingcode extract resets thesizeofthe splitterswhenthe program is started:

QSettingssettings("OpenSourcePress","CuteEdit"); splitter->restoreState(settings.value("SplitterSizes").toByteArray());

Forsituationsinwhich,depending on thealignment of thesplitter,the widthsor heightsofindividualwidgets arerequiredasindividualinteger values, theQSplitter APIhas themethods sizes() andsetSizes(), which workwiththe listtype QList. This meansthatyou can read outthe sizes,for examplebyusingthe foreach macro defined byQt:

151 5 LayingOut Widgets

foreach(intsize, splitter->sizes()) qDebug("Size: %i",size);

qDebug() is oneofthe debugging macros that works likethe Cfunction printf(), andreturnsthe errormessage specified in theargument.Weuse it here to quickly produce output.Details on debugging withQtare containedinAppendixA. Analogoustoreading outthe currentsplitter sizes,itisalsopossible to change them bypassing thenewvalueswithsetSizes() in listform:

QListsizes; sizes<< 20 << 60 << 20; splitter->setSizes(sizes);

In this example, which assumesasplitter withthree widgets,these arenow20, 60,and 20pixelswide(for ahorizontalsplitter)orhigh(for averticallyarranged splitter).

5.3.3DefiningRelativeSizes

Just likeanormal layout, QSplitter also provides awayof definingastretchfactor foreach widgetinserted.Incontrasttolayouts,these mustbespecifiedfor splitters afterwardusing thesetStretchFactor() method. Since this function also requires the positionofthe widget, apartfromthe stretch, you first havetodefine theposition of thewidgetusing theindexOf() method. This returnsthe correctpositionfor a given widgetorahandle. Theexamplebelow,documented in Figure 5.6, is derived from thestretchfactor exampleonpage 147, butnowuses asplitter insteadofalayout. Since splitters arealignedhorizontallyif no otherdetails aregiven,the result more or less matches that of aQHBoxLayout—withthe exceptionthatthe spacesbetween widgets now carryhandleswhich can be used to definethe sizeofthe textedit.

// stretchfactorsplitter/main.cpp

#include

intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QSplitters; QTextEdit * txtEdit=0; for(intstretch =1;stretch <= 5;stretch++) { txtEdit=newQTextEdit(&s); s.addWidget(txtEdit); s.setStretchFactor(s.indexOf(txtEdit),stretch);

152 5.3 Splitter

} s.show();

returna.exec(); }

Figure 5.6: Thestretch factor example from Figure 5.4also workswith splitters.

Splittersare often used,for example, to separateamain widgetfromapage bar. With different-sized monitors, therelativesizecreated here bytheuse of stretch factors can quicklybecome anightmare:Ifthe space on thethe screen is too small, thepage barwillappear toosmall, while on widescreen displays, currently verypopularonlaptops, theywill take up toomuchspace. In such cases it is better to specifyafixed initialsizewithsetSizes() andtomanage thesizes defined bythe user withsaveState()and restoreState().

5.3.4Customizing Handles

Thesplitter handle itself is implemented in theQSplitterHandle class. Butsome- timesthe standardimplementationisnot enough,for example, if you wanttohave thesplitter collapsewhenitisdouble-clicked, similartothe waythepage barofthe browserreacts. Then you havetouse yourownimplementation, derived from QSplitterHandle.WewillcallthisClickSplitterHandle. Since wewanttoreact to adouble-click, wemustalsoreimplement themouseDou- bleClickEvent()method, as wellasthe constructor. To be able to usethisdouble- clickable handle in asplitter,the design of QSplitter also forcesustocreatea subclass of theactualsplitter.Amethod that allowsaQSplitterHandle instance to be setisnot enough,since asplitter—asalreadyexplained—can haveanynumber of handles. Because thecopyoperators of QWidget-basedwidgets aredisabled, theQSplitter APIperforms atrick: Theclass hasaprotected method calledcreateHandle(), which is allowed to overwrite subclasses. Theonlypurposeofthis factorymethod con- sistsofcreatinganewinstance of QSplitterHandle or asubclass. QSplitter then uses this method whencreatingnewhandles. In thefollowingexamplewewillthereforecreate, besidesthe subclass of QSplitter- Handle calledClickSplitterHandle,aclasswiththe name of ClickSplitter,which in-

153 5 LayingOut Widgets

herits directlyfrom QSplitter andwhich overwrites onlythecreateHandle()method:

// clicksplitter/clicksplitter.h

#include #include

class ClickSplitterHandle :public QSplitterHandle { Q_OBJECT public: ClickSplitterHandle(Qt::Orientation o, QSplitter * parent=0); void mouseDoubleClickEvent(QMouseEvent * e); private: intlastUncollapsedSize; } ;

class ClickSplitter:public QSplitter { Q_OBJECT friend class ClickSplitterHandle; public: ClickSplitter(Qt::Orientation o, QSplitter * parent=0) :QSplitter(o, parent) {} ClickSplitter(QSplitter * parent=0) :QSplitter(parent) {}

protected: QSplitterHandle * createHandle() { returnnewClickSplitterHandle(orientation(),this); } } ;

Theimplementationiscentered on themouseDoubleClickEvent()method. In the constructorweinitializeonlytheclass variable lastUncollapsedSize, which welater requireinmouseDoubleClickEvent()sothatwecan remember howlargethe widget was beforeitwas collapsed:

// clicksplitter/clicksplitter.cpp

#include "clicksplitter.h" #include

ClickSplitterHandle::ClickSplitterHandle(Qt::Orientation o, QSplitter * parent) :QSplitterHandle(o, parent) { lastUncollapsedSize=0; }

void ClickSplitterHandle::mouseDoubleClickEvent(QMouseEvent * e)

154 5.3 Splitter

{ QSplitter * s=splitter(); Qt::Orientation o=s->orientation(); intpos=s->indexOf(this);

QWidget * w=s->widget(pos);

if (lastUncollapsedSize==0) if (o==Qt::Horizontal) lastUncollapsedSize=w->sizeHint().width(); else lastUncollapsedSize=w->sizeHint().height();

intcurrSize=s->sizes().value(pos-1);

if (currSize==0) moveSplitter(lastUncollapsedSize); else { lastUncollapsedSize=currSize; moveSplitter(0); }

}

In ClickSplitterHandle::mouseDoubleClickEvent()wefirstdetermine thealignment of thesplitter.Weobtainthe positionofthe splitter,using theQSplitter::indexOf() method. This is also thepositionofthe widgetlyingtothe rightof(or directly beneath) thesplitter. Forreasons of symmetry,azerothhandleexists in everysplitter,which QSplitter never displays. This guaranteesthatindexOf() alwaysdeliversasensible position. Thefunction makesadistinctionbetween generalwidgets andsplitterswhendoing this,and is able to determine thenumber of aspecific widgetorsplitter.Thus the splitter can be defined for awidgetasfollows,

... QSplitter * splitter=newQSplitter; splitter->addWidget(newQWidget); QLabel * lbl=newQLabel; splitter->addWidget(lbl); splitter->handle(splitter->indexOf(lbl)); ... while in themouseDoubleClickEvent()method on page 154, welook for thewidget to go withasplitter. TheClickSplitterHandle classvariable lastUncollapsedSizeremembers thelastsize of thewidgetinanuncollapsed state. If this is 0for anyreason,the implementation uses thevalue of therespectivesizeHint(). Thecurrent positionofour splitter

155 5 LayingOut Widgets

dependsonthe sizeofthe widget in frontof thesplitter,which is whythecode detects thesizeofthe widgetthatoccupies thespaceuptothe positionpos-1,by accessing sizes(). If theleftwidgetiscurrentlycollapsed,the last lines of code on page 154 open it againusing thelastUncollapsedSizevariable.Otherwisethe widget“disappears” to theleftofthe handle,but notwithout rememberingits currentsizeinlast- UncollapsedSize.

5.3.5Layoutfor LanguagesWrittenfromRight to Left

Thewayin which texts in some languages, such as Hebrew,are read differsfun- damentallyfrom European languagesinone respect: Theyarereadfromright to left,and this mustalsobeaccounted for bysoftwareapplications.For such texts, it is notthe topleft, butthe topright edge of thescreen that is thestartingpoint for theeyewhenreading.Accordingly,atoolkitmustbeable to invertthe layout horizontally.The KDE browserKonqueror in Figure 5.7mastersthistaskcorrectly, because Qt managestomirrorall layouts horizontally,largelywithout thehelpof theuser, if thelanguage configuredisoriented from righttoleft, or if thepro- gram is passedthe -reverse option. TheQt-internalequivalentfor this optionis theQApplication::setLayoutDirection(Qt::RightToLeft)call. This program optionis mainlyused for testpurposes. In thedevelopmentofyourownlayouts andwidgets,you mustalwaysmake your ownprovisions in caseinverted layoutisused. Thus ourexamplefromaboveno longer works correctlyin thecaseofaright-to-leftconfiguration:Itstill collapses thewidgettothe left of thesplitter,althoughthe widgetthatshouldcollapseis nowon theright side.

Figure 5.7: Becauselanguages likeHebrewrequire a horizontally reflected layout,developers of widgets andlayouts must take this case into account andtest it.

156 5.4 StackedLayouts

To examinesuchspecial cases,QApplicationhas thestaticmethods isLeftToRight() andisRightToLeft(), which developers can easilyusetocheck thecurrent layout.

5.4Stacked Layouts

In stacked layouts ,several widgets areplacedontop of each otheronthe same area—in contrast to otherlayouts,which arrangewidgets on asinglelevel.This techniqueisusuallyappliedwhenimplementingcomplexconfiguration dialogs (see Figure 5.8, page 160). Theclass that implements this functionalityin Qt is calledQStackedLayout. New widgets arealsoaddedinthisformoflayoutwiththe addWidget()method. You should remember theIDreturned whenthisisdone: Thewidgets can be identified later on withits help.Alternatively,you can savethe pointer to theinserted widget. So that you can accessone of theinserted widgets again, QStackedLayouthas twoslots:setCurrentIndex() expectsapositionofthe widgetasaninteger value, whereas setCurrentWidget()acceptsapointer to an instance of aclass derived from QWidget.

5.4.1The Alternative: StackedWidgets

AQStackedLayout, likeall layouts,requiresawidgetthatwillmanage it.Inmost cases this needstobeadditionallycreated,and you havetoequip this widget withalayout. To simplifythis,Qtprovides so-called stacked widgets withthe QStackedWidgetclass,which havethe same APIasQStackedLayout. Internally, theseare widgets equippedwithastacked layout.

5.4.2WhentoUse StackedLayouts andWidgets

Belowwewilldevelop asimplevariation of such aconfiguration widgetourselves, which of course can also be implemented as adialog.4 Configuration dialogs such as thoseinKDE provideaverygood exampleofthe useofaQStackedLayout: AstandardKDE configuration dialog consists of alistor icon view,aswellasastacked layoutorastacked widget. Depending on which entrytheuserselects from thelist, thestackedclass ensuresthatthe relevant widgetcomes to thefront.Listand icon viewsinQtare normallybasedonthe so-called model/view concept ,which is covered separatelyin Chapter 8. Forour purposes,asimplified listviewthat is provided byQt withthe QListWidgetclass willbesufficient.Each listentryis encapsulated in an instance of thelightweight

4 Exactlyhowdialogs function is explainedinChapter 6.

157 5 LayingOut Widgets

QListWidgetItem class. Each QListWidgetItem contains thetextbelonging to the entry,aswellasadefinitionfor apossible icon.Inour configuration dialog we associate apage in thestackedwidgettoeach widgetitem. Theheart of ournewclass, which wederivedirectlyfrom QWidget, is theaddPage() method, which adds newpagestothe stacked widget. In addition werequire this verystacked widgetand alistviewas member variables:

// configwidget/configwidget.h

#include

class QListWidget; class QStackedWidget;

class ConfigWidget:public QWidget { Q_OBJECT public: ConfigWidget(QWidget * parent=0);

void addPage(const QString&title, const QIcon&icon, QWidget * page);

private: QStackedWidget * widgetStack; QListWidget * contentsWidget; } ;

In theconstructor weinitiallyarrangethe listviewto theright of thestacked widgetand restrict itswidth to 180pixelssothatitdoesn’t take up toomuch space. Thelistviewregards each widgetitem as a row .Assoon as theuserselects another item,itreports via thecurrentRowChanged(int) signal.Ithelps that stacked layouts andwidgets savethe positions of theirwidgets as integers, in which thenumber value corresponds to thenumber of thewidget. Theseclassesthereforehavethe setCurrentIndex(int)slot, which causesthe widgetwiththe number specified as an argument to be displayed.Weconnect this slot in theconnect() instructioninthe final lines of theconstructor to thecurrentRowChanged(int) signal. Nowit is important to create theentryin thelistviewwhenthe stacked widget incorporates theaccompanyingwidget. Since both indicesstart at zero, andsince onlyaddPage() carries outchanges to both widgets,itisguaranteed that thelist viewentryis associatedwiththe correctwidget.

// configwidget/configwidget.cpp

#include #include "configwidget.h"

158 5.4 StackedLayouts

ConfigWidget::ConfigWidget(QWidget * parent) :QWidget(parent) { QHBoxLayout * lay=newQHBoxLayout(this);

contentsWidget=newQListWidget; widgetStack =newQStackedWidget;

lay->addWidget(contentsWidget); lay->addWidget(widgetStack);

contentsWidget->setMaximumWidth(180);

connect(contentsWidget,SIGNAL(currentRowChanged(int)), widgetStack, SLOT(setCurrentIndex(int)));

} void ConfigWidget::addPage(const QString&title, const QIcon&icon, QWidget * page) { QListWidgetItem * item =newQListWidgetItem; item->setText(title); item->setIcon(icon); contentsWidget->addItem(item); widgetStack->addWidget(page); }

Using this newAPI, weinsertonlyafewsimple QLabelsbelow,but widgets are possible,ofcourse, in anycombinationand size:

// configwidget/main.cpp

... ConfigWidget * w=newConfigWidget; w->addPage("First Page",icon, newQLabel("

first page
")); w->addPage("Second Page",icon, newQLabel("
second page
")); w->addPage("ThirdPage",icon, newQLabel("
thirdpage
")); ...

Figure 5.8showsthe result.Insteadofastackedwidget, wecould just as welluse alayoutinthisexample. To do this onlythemembervariable widgetStack needs to usethe QStackedLayouttype.Wechangethe constructorasfollows:

...

159 5 LayingOut Widgets

contentsWidget=newQListWidget; QWidget * widget=newQWidget; widgetStack =newQStackedLayout(widget);

lay->addWidget(contentsWidget); lay->addWidget(widget); ...

AllAPI calls in this exampleremainintact in thesamewaywhenchangingtoa stacked layout.

Figure 5.8: With theselection widgetonthe left,Qt brings therelevant widgetinthe stacked widgetorlayoutto thetop.

160 r te ap 6 Ch

Dialogs

Dialogsand theirbaseclass,QDialog,which wehavealreadybrieflyencountered in Chapter 2, areusedinvarious contexts.These contexts determine theproper behaviorofthe dialog. Forexample, normallyaconfiguration dialog should always remain in theforeground of theapplicationuntil theuserhas made thedesired changestothe settings. Theinteractionwithasearch dialog in awordprocessoris somewhatdifferent,however—a user mayhavesuchadialog windowopenduring theediting of adocument,but he willcertainlynotwanttobeprevented from making changestothe document at thesametime. This chapter looksatthe various dialog typesand howtheycanbeimplemented withQt.

6.1ModalDialogs

Some dialogs remain in theforeground until theusercompletes hisinteraction withthem, andthe rest of theapplicationisblockeduntil thedialogwindowis

161 6 Dialogs

closed.These dialogs normallyimplementoperationsthatshouldbecompleted beforethe user can continue working.Suchdialogs areknownas modal dialogs. This dialog type is specificallysuited to configuration interfaces:Ifthese do not block theapplication, this often meansincreased programming work, particularly if settingsaltered usingthisdialoginfluenceother partsofthe GUI.Insucha caseunexpected phenomenacould occurfor theuser, butthese can be elegantly avoidedwiththe help of themodality. Amodal dialog represents anew toplevel widget .InQtthismeans that thede- velopermustmake instancesofthistype of QDialog visibleexplicitly,since dialogs arealwaysinvisibleafter beinginstantiated. 1 Formodal dialogs theexec() method is normallyused for this,which generates aneweventloop anddisplaysthe dialog at thesametime. Thesimilarityof themethod name to QApplication::exec(), the function which starts themaineventloop for theoverallprogram,isthus inten- tional. Thefollowingcode fragmentdemonstrates howto proceed wheninstantiating a modal dialog, usingthe subclass QFileDialog of QDialog as an example, which we willget to knowmore closelyin Section6.5.3.First weinstantiate adialogand let it enter itsowneventloop, byinvokingits exec() method:

QFileDialog dialog; intstatus =dialog.exec(); // start dedicated Event-Loop //execution continueshereafterthe dialog hasbeen closed

Onlywhenthe QFileDialog’s exec() finishesand returnscontrol to theapplication, for examplebecause theuserclosesthe dialog window,doesthe main eventloop resume (i.e., theQApplication::exec() method).Thisbehaviorensures themodal- ityof thedialoginternally,together withthe Qt::WA_ShowModal flag(seepage 164).Italsomeans that modal dialogs started via exec() can return asuccess or failure code for themaineventloop to examine, in asimilarwayto howthemain() function of aC/C++program returnsastatus code to theoperating system. Thereturn value is determinedbyaslot: QDialog providestwopredefined slots, ac- cept() andreject(), that arenormallytriggeredbyclicking theOKorCancelbuttons, ✞ ☎ respectively,withthe latter also triggeredbypressing the ✝ Esc ✆ key.Atthe same time theseslots closethe dialog, butwithout deleting thedialogobject.Supplied withthe return value,the code that calledthe dialog can convenientlytake over theprocessing logic. Of course,ifasubclass of QDialog implements theprocessing of itsGUI elements internally,orifthe return value is notrelevant, as in thenumber converter example from Chapter 2, theprogrammermayignore thereturn value.

1 In generalwidgets areonlyinvisible at first whentheydo notpossess aparentwindow;for dialogs this rule doesnot apply.

162 6.2 Non-modal Dialogs

6.2Non-modalDialogs

It is notalwayspossible or sensible to usemodal dialogs.Aclassicexampleisthe dialogs provided bywordprocessors for searchinginanopendocument.Here, theusermustbeable to interact simultaneouslywithboththe dialog andthe document view,which is stored either in themainwindowor in anotherwidget. AQDialog implementing anon-modal dialog can be displayed usingthe show() method. As withthe function of thesamenamepossesedby“normalwidgets,” this callimmediatelyreturnsavalue.However,communication withthe dialog is accomplishednot throughthe return value,but throughsignals andslots. We havealreadycome acrossthistype of behaviorinthe ByteConverterDialog example, which wediscussedinChapter 2.

intmain(intargc, char * argv[]) { ByteConverterDialog bc; bc.setAttribute(Qt::WA_QuitOnClose); bc.show(); returna.exec() }

Theshow() callreturnsimmediately.Thisisnot aprobleminthe aboveexample because bc is notdeleted from thestack beforethe program quits, causing a.exec() to return.However,dialogs outsidethe main method mustbeallocatedonthe heap, sinceobjectsonthe stack getdeleted as soon as themethod theyarecreated in (e.g., theconstructor)goesout of scope.Usuallywecan also manage without thesetAttribute()call, which is used onlyto terminate themaineventloop as soon as thedialogreturns.

6.2.1UsabilityProblems

Awarning is appropriate at this point,particularlyin viewof theexampleofthe search dialog just mentioned: Non-modal dialogs that areusedtogether withthe main applicationwindowfrequentlypresentthe user withavisuallyimpenetrable barrier.Thisisbecause dialogs areplacedabovethe matching main window,so that theuserknowswhich program theybelong to.Unfortunately,itisalmost impossible to preventthe dialog from coveringover relevantinformation displayed in theapplication’smainwindow. In practice,twodifferent approaches existtoget round this problem: On onehand, theapplicationcan ensure that therelevantpartofthe document or application

163 6 Dialogs

windowandthe non-modal (search) dialog never overlapbymovingthe dialog if necessary. 2 KDE applications,atleast in KDE 3, usethisapproachtoalarge extent. On theother hand,itisoften feasible to do without aseparatedialogaltogether andtodisplayan additionalsearchwidgetinthe main window,asisthe standard practice,for example, in theFirefoxbrowser. Butherethere is adangerthatthe user,expectingadialog, willnot seethe inputwidget.

6.3Semi-modalDialogs

Aseparatecategoryof dialog is represented bythe semi-modal dialogs.The term 3 is basedonthe factthattheyaredisplayed,likenon-modal dialogs,via show(). Theapplicationprogram thereforecontinuesrunning.However,these dialogs are meanttobeusedinamodal fashion: Theusershouldnot continue working in theapplication, butinall cases turn hisattention to thedialog. To enforce such a modal interaction, you mustcallsetModal(true)4 beforecallingshow().

6.4AvoidingBloated Dialogs

Auser-friendlydialog doesnot overwhelm itsusers withoptions.Itismuchmore sensible to setreasonable defaultsettings andtopresent theuseronlywithchoices for which he reallymustmake adecision. He should be shownmoreoptions onlyif he explicitlydemandstosee them.Thisusabilityrequirementcan be setusing the QDialog APIextensions.

Figure 6.1: Exampleofthe useof extensions:The Run dialog of KDEusually looksneatand tidy.

Theseprovideagood service, for exampleinthe KDE Run dialog. This dialog allows theusertotype URLs or program names, andtries to either displaytheweb page or startthe correctprogram (Figure6.1), as appropriate.

2 QWidget::mapToGlobal() andQWidget::mapFromGlobal() areagreat help here in transferring coordinatesbetween widgets. 3 Althoughthe Qt documentationalsoreferstoinstances of this type of dialog as modal dialogs, theauthorconsiders thedistinction to be relevant. 4 This is awrapperaround thecallsetAttribute(Qt::WA_ShowModal, true), which canalsobe appliedtoeveryothertop-level widget.

164 6.4 AvoidingBloated Dialogs

Moreadvancedusers willsometimeshaveextrawishes, such as specifyingreal- time priority,adifferent priority,orexecutionofaprogram as auserwithrestricted permissions. Since theseoptions areonlyrarelyrequired,theyarenot stored in the basicdialogbut in anotherwidget, which is displayed onlyif requested bytheuser via theOptions buttonfromFigure6.1.Figure6.2 showsthe result.

Figure 6.2: Thanks to the extension, it still provides advanced userswithawide rangeofsetting options.

Thefollowingcode exampleillustrates thestate of affairs:

// extensions/main.cpp

#include intmain(intargc, char * argv[]) { QApplication app(argc, argv); QDialog dlg; QPushButton * btn=newQPushButton(QObject::tr("Expand/Collapse"), &dlg); btn->setCheckable(true); QVBoxLayout * lay=newQVBoxLayout(&dlg); lay->addWidget(btn); QLabel * ext =newQLabel(QObject::tr("Extension")); dlg.setExtension(ext); QObject::connect(btn, SIGNAL(toggled(bool)), &dlg, SLOT(showExtension(bool)));

dlg.exec(); returnapp.exec(); }

Abuttonwiththe inscriptionExpand/Collapseallowsthe user to folddownalabel withthe inscriptionExtension (Figure6.3). Theextension itself is aQWidgetinQt4,

165 6 Dialogs

which wepasstothe dialog via QDialog::setExtension(). If it should folddownward rather than sideways, which is thedefault,the callQDialog::setOrientation(Qt::Ver- tical)can be used;thus,inthe examplecode abovewewould includethe following line:

dlg.setOrientation(Qt::Vertical);

So that thebuttonwilldisplayandhidethe extension,weturnitintoatoggle switch usingsetCheckable(true) andconnect itstoggled() signal to theshowExten- sion() slot of QDialog.

Figure 6.3: Theexample program from page 165 with andwithout a horizontally folded outextension

6.5Ready-madeDialogs in Qt

Manydialogs areuniversalenoughtojustifyapredefined class. Oneofthese is the openfile dialog implemented in QFileDialog,withwhich webecameacquainted in Chapter 4. In addition to this,Qt4hasother such ready-to-usedialogs,which willbeintro- ducedbelow.

6.5.1Message Dialogs

Veryfrequently,aprogram mustforwardinformation to theuser. If it is essential that theusernotices this or needstomake adecisionbased on it,asimple message in thestatusbar is notsufficient. Apossible alternativewould be to deriveaseparateclass from QDialog that dis- playsthe message in alabel andadditionallyprovides oneorseveral buttons with actions.Luckilythis is notnecessary,since Qt provides theQMessageBoxclassfor this purpose. Apartfromthe texttobedisplayed andthe obligatorywindowtitle (alsocalledacaption ,which is setusing thewindowTitle property), aQMessageBox contains up to threebuttons.Optionally,aniconcan be defined that is displayed

166 6.5 Ready-made DialogsinQt

in themessage dialog nexttothe textmessage.The textinthe dialog can—asinall Qt dialogs—be formatted usingHTMLtags.Thisisdemonstrated bythefollowing code,visualized in Figure 6.4:

// messageboxmanually/main.cpp

... QString text =QObject::tr("Thisisaverycomplicated way" "of showing message boxes. Onlyusethisin exceptionalcases! " "Doyouwanttocontinue?"); QMessageBoxmsg(QObject::tr("Academic Example Warning"),text, QMessageBox::Warning, QMessageBox::Yes|QMessageBox::Default, QMessageBox::No|QMessageBox::Escape, QMessageBox::NoButton); if (msg.exec() == QMessageBox::Yes) { qDebug() << "Keepon going!"; } ...

Figure 6.4: Amanually instantiated QMessageBox

Thecalltothe message box’s constructorseemstobequite complicated,but this is onlyduetothe manyarguments. Afterspecifyingthe dialog heading anda message to be displayed in thefirsttwoparameters, wecan choose from oneof fourpredefined iconsbypassing oneofthe followingvaluesasthe thirdargument:

QMessageBox::Question This is intendedfor themessage dialogs that askquestions.

QMessageBox::Information This emphasizes generalinformation.

QMessageBox::Warning This should be used for potentiallydangerous actions.

QMessageBox::Critical This is thechoiceofpreferencetoemphasizeserious errors.

QMessageBox::NoIcon This displaysnoiconatall.

167 6 Dialogs

Howthedisplayed icon willultimatelyappearisdeterminedbythecurrentlyse- lected style.Ifyou do notlikeanyof thepredefined icons, you can insteaddefine anyQPixmapyou wantasaniconfor themessage dialog, usingthe setPixmap Icon() method. Thefourthtosixth argumentstothe constructorare used to specifyup to threedif- ferent buttons.Possible valuesfor theseparametersare listed in Table 6.1. Thevalue QMessageBox::NoButtonhas aspecial meaning: As in theexample, it specifies that thecorresponding buttonisnot desired, andwillthereforenot be displayed. Since thecalltothe message dialog’sexec() method causesthe applicationtoenter aseparateeventloop andremainthere for thedurationofthe displayof thedialog, it is quiteenoughtoinstantiate theobject on thestack. This waywedonot specify aparentwidgethere. To make thedialogmodal,you would,however,berequired to specifyaparent. Theparentparameter followsthe final buttonspecificationin theQMessageBoxconstructor. Whichbuttonthe user ultimatelychooses is specified bythereturn value of exec(), which can be compared,inaniforswitch statement, againstthe valuesfromTable 6.1, as in theexample. ThroughanORlinkwithQMessageBox::Default andQMessageBox::Escape, wecan ✞ ☎ ✞ ☎ also specifytheactions that aretriggeredwhenthe user pressesthe ✝ Enter ✆ or ✝ Esc ✆ key.

Table6.1: Value Buttontext Possible buttontexts QMessageBox::OkOk QMessageBox::CancelCancel QMessageBox::Yes Yes QMessageBox::NoNo QMessageBox::Abort Abort QMessageBox::RetryRetry QMessageBox::IgnoreIgnore QMessageBox::YesAllYes,all QMessageBox::NoAll No,all QMessageBox::NoButton—

Theapproachdescribedhereisunsatisfactoryin onerespect—namely,the program- merhas to be careful that allofthe sections of code implementing themessage boxes for agiven type of eventare thesame, or else theapplication’suserinterface willbeinconsistent. So that wedonot havetorelytoomuchonthe disciplineof developers, QMessageboxprovides arange of static methods that can be used to displayfinishedmessage dialogs for different purposes.Onlyif theseare notsuf-

168 6.5 Ready-made DialogsinQt

ficient for yourrequirementsshouldyou tryandassemble yourowncustomized info box.Wewillpresent thestaticmethods below,inorder of importanceofthe message theytransmit.

Asking Questions

ThequestiondialogQMessageBox::question() (Figure6.5)isone of themostfre- quentlyneeded message dialogs.Apart from aparentwidget, itsconstructor ex- pectsacaption,ashortdescriptivetext, andthe textfor itsbuttons.Accordingto Microsoft’s style guide, theheading should matchthe name of theapplication. This is returned byqApp->applicationName(). Other style guides suggest usingacom- binationofthe program name andashortdescription of theoperation currently beingprocessed, for example“Overwrite file? – applicationname ”.

Figure 6.5: QMessageBox::question() takes decisions off theuser’shands.

In theexamplebelowweassign thebuttons—asisnormalfor question dialogs— thetextlabelsYes andNo, making theYes buttonthe defaultand allowingthe No ✞ ☎ buttontoreact to the ✝ Esc ✆ key.Since wedonot need athird button, weleaveit outbyspecifyingQMessageBox::NoButton. Thestatuscode returned byQMessageBox::question() corresponds to thevalue of thebuttonchosenbytheuser. To checkwhether theuserhas answered theques- tion withYes,wecomparethe return value withQMessageBox::Yes: bool checkOverwrite(const QString &filename) { intstatus =QMessageBox::question(this, tr("OverwriteFile?"), tr("Afile called ’%1’alreadyexists. \ n" "Doyourealywanttooverwritethisfile?") .arg(filename), QMessageBox::Yes|QMessageBox::Default, QMessageBox::No|QMessageBox::Escape, QMessageBox::NoButton);

if (status != QMessageBox::Yes) returnfalse; returntrue; }

169 6 Dialogs

Yes-Noquestions haveone considerable disadvantage,however:The user needs to read throughthe entire textand understandit. It is possible,especiallywith complexuser prompts, that theuserwillmisunderstandthe question anddecide on thewrong answer. This drawback can be avoidedtoalargeextentbyfollowingtwobasic principles. First, aquestionshouldnever be formulated in thenegative. Wordsthatnegate themeaning of aphraseorclauseare generallypassedover in aquick reading: Thereisahigh probabilitythat questionssuchas“Areyou sure that you do not wanttooverwrite thefile?” will be misunderstood. Forthe sake of better usability, such negated phrasesshouldabsolutelybe avoidedinaGUI,even if theymatch thelogicofthe applicationcode itself. In addition,users can be more sure of theirresponsestoaquestion dialog if instead of thesimplebuttonlabelsYes andNo, theycanreadadescriptivetextthatagain emphasizes theoptions describedinthe descriptiontext. QMessageBoxprovides programmaticsupportfor this paradigm of GUI design: Trolltechhas included anotherversion of thestaticmethod question() for this pur- pose. Thesecondvariant takesthe same first threeparametersasthe onejust described, butafter this thereare differences. Insteadofthree enumeratorvalues, wenowpassthe stringsthatare to appearon thebuttons.Ifabuttonisnot required,thisissignaledwithanemptystring,as showninthe followingexample(in thesixth parameter).5 Finally,you again need ✞ ☎ to specifythebuttons in turn that are“clicked” if theuseroperates the or ✞ ☎ ✝ Enter ✆ ✝ Esc ✆ key;thisisdonewiththe final twoparameters. Thenumberingofthe buttons corresponds to theirposition(in thecode andinthe orderofreading,fromthe user’s point of view), whereby0refers to thefirstbuttonand 2tothe thirdone. Thebuttonwiththe Choose adifferent name inscription(Figure 6.6) thus becomes thedefault buttonwiththe code below—an additionalfeature that helpstoprevent theuserfromlosingdatabyaccident:

bool checkOverwrite(const QString&filename) { intstatus =QMessageBox::question(this, tr("OverwriteFile?"), tr("AFile called %1 alreadyexists. " "Doyouwanttooverwritethe file orcancel " "the proceduretochooseadifferentname?").arg(filename), tr("&Overwritefile"),tr("&Choosedifferentname"), QString(),1,1); if (status == 1) returnfalse; returntrue; }

5 TheQString constructorcalledwithnoargumentsalwaysmakes aQString that denotesthe emptystring.

170 6.5 Ready-made DialogsinQt

QMessageBoxprocessesthe textenclosedintags as HTML andline-wraps it automatically.Wecan take advantage of thefact that simple HTML formatting is possible withinthese tags andput thefilename displayed to theuserinitalics. Thereturn value of this QMessageBox::question() variant is basedonthe positionof theselected button—in contrast to thefirstvariant,which returnsthe enumerator value for theselected button. This is an essentialsemanticdifferencethatcan easilylead to faultycode:Since enumerators can also be used as integer values, thecompilerdoes not complain if thecode you write to checkthe status is based on thereturn semanticsofthe opposingvariant.

Figure 6.6: QMessageBox::ques- tion() with individual responsestothe buttons

ConveyingInformation

If you wantthe program to informthe user aboutmattersoccurring during a workprocess that is proceedingnormally,information dialogs areideal andare provided bytheQMessageBox::information() static method (Figure6.7). Just like QMessageBox::question(), this is also overloadedtwiceand works identically,except that it merelydisplaysadifferent icon:anexclamation mark insteadofaquestion mark.Somestyle guides even suggest usingnormalinformation dialogs instead of thequestiondialog. Butsince Qt allowsyou to easilydifferentiate between question dialogs andinformation dialogs,itisrecommended that you do so.

Figure 6.7: QMessageBox::infor- mation() is used quite rarely as a replacementfor the question dialog

In particular, you should notmake useofthe optionofequipping an information dialog withmorethanone button(i.e., an OK button). If you requireasecond one for canceling, QMessageBox::question() is usuallythebetter choice. Forexample, to informauser of thesuccess of asearchprocess,you can usean information box,asshowninFigure6.7:

QMessageBox::information(this, tr("Search Failed"),

171 6 Dialogs

tr("Nomatchesfound!"), QMessageBox::Ok|QMessageBox::Default, QMessageBox::NoButton, QMessageBox::NoButton);

With OK as theonlyresponse,queryingthe return value in this caseisnot neces- sary.

IssuingWarnings

Warnings areencapsulated in Qt inside warning dialogs,represented bytheQMes- sageBox::warning() static method. This works in thesamewayas QMessage- Box::question(), butitshouldonlybe used for unusualproblems that interrupt thenormalprogram sequence.

Figure 6.8: To reporterrorsthat interrupt thecourse of theprogram,use QMessage- Box::warning().

In theexamplefromFigure6.8,aprogram absolutelyneedsaserver connection, without which it cannotstart.The warning dialog allowsthe user to choosebe- tween tryingagaintoestablishaconnection, or terminating theprogram,thus ensuring theavailabilityof thenetworkifthe applicationcontinues:

intresult=QMessageBox::warning(this,tr("Applicationname"), tr("Could notconnecttoserver.\ nThisapplication requires" "aservertofunction correctly."), tr("&Retry"),tr("&Exitapplication"),QString(),0,1);

if (result== 1) qApp->quit(); else retryConnect();

Of thethree buttons available,welabel twoand leavethe thirdone empty.Asin theQMessageBox::question() example, thethird buttonisthus notdisplayed.The ✞ ☎ ✞ ☎ last twoparameterslink ✝ Enter ✆ to thefirstbutton(0)and ✝ Esc ✆ to thesecondone (1). If theuserselects thesecondbutton, theresultvariable receives thebutton code 1, andthe applicationterminates.Otherwise, theprogram triesagain, via retryConnect(), to establishaconnectiontothe server.

172 6.5 Ready-made DialogsinQt

Passing on Critical Messages

Aprogram should openaspecialdialogfor critical messagesreporting errors that theuserhimself cannotsolve, or can solveonlywithgreat difficulty.Thisdialogis also available in thesametwoversionsasdiscusseduntil now.Itisnormallyused as follows(Figure 6.9):

QMessageBox::critical(this,qApp->applicationName(), tr( "Acriticalerrorhasoccurred." "Ifthe problem persists, \ n" "pleasecontactour support center" "at+01555 123456."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton);

Here,aswiththe information dialog, wedonot to checkfor areturn value,since thereisonlyonebutton. In arealsituation thedialogshouldcontain more infor- mation on thetype of critical errorthatwas encountered.Itisrecommended that you usecritical messagesverysparingly—theyshould be theexceptionratherthan therule.

Figure 6.9: Only if nothingelse worksisitistimefor QMessage- Box::critical() .

ProvidingYourOwn Information on theApplication

Thehelpareaofanapplicationusuallycontains asmall dialog providinginforma- tion on theapplication. We havealreadyencountered oneinSection 4.3.3onpage 117, without explaining it in more detail. Like theother static methods listed here,QMessageBox::about()alsofirstexpects aparentwidgetand awindowtitle. Thethird parameter is afreetextthatcan also be HTML formatted.Ifaniconfor theapplicationwas defined withQAppli- cation::setWindowIcon(), it is displayed as an icon nexttothe free text. Thedialog onlyhasone buttonand hasnoreturn value. If you also wanttoshowthat you havewritten yourapplicationwiththe Qt toolkit, you can includeanadditional help menu entryin yourprogram that calls QMes- sageBox::aboutQt(). This notificationdialogprovides information on Qt.

173 6 Dialogs

6.5.2Error MessagesThatAre Only VisibleOnce

ApartfromQStatusBar::showMessage() (see page 114) andQMessageBox,there is athird possibilityfor supplyingthe user withinformation—namely,using the QErrorMessage class. In contrast to QMessageBox,QErrorMessage does notprovidestaticmethods.It displaysmessagesifits showMessage() slot is called, either directlyor via thecon- nectionwithasignal.

Figure 6.10: Queuing with QErrorMessage:Only if thefirsterror message was confirmed ...

Figure 6.11: ...doesthe second message appear.

Twoother features make this classveryuseful.First,iftwomessagesare waiting, as in thefollowingexample, then QErrorMessage onlyshowsthe second oneonce theuserhas clickedthe first oneaway:

QErrorMessage * msg=newQErrorMessage(this); msg->showMessage(tr("Thiserrormessage will onlyreoccur if you" "don’t uncheck the checkboxin thismessage box.")); msg->showMessage(tr("Ifyoucansee thismessage, youhaveclosed the " "previous message box."));

Theeffect of this code is showninFigures 6.10and6.11. Second,each of these dialog boxes contains a“Showthis message again”checkbox.Ifthe user unchecks this,Qtsuppressesthe errormessage if thesamesituation arisesinfuture.

174 6.5 Ready-made DialogsinQt

6.5.3File SelectionDialogs

Applications frequentlyusedialogs to prompt theusertoselectfilesordirectories. Forthisreasonnearlyallplatforms provideimplementations of such selectiondi- alogs. Each onehas itsownspecial features,towhich theend usersofapplications quicklybecome accustomed. As aplatform-independenttoolkit, Qt is in somewhatofaquandaryhere:Onthe onehandthe user should be able to usehis or herownsystem dialog, butonthe other, thesedialogs cannotbeextendedwiththe widgets you as aprogrammer havewritten. TheQFileDialog class, which implements thefile anddirectoryselector,istherefore split into twoparts:Ifyou instantiatethe classvia theconstructor,thenQtdisplays aseparatedialog. If you usethe predefinedstaticmethods,onthe otherhand, which cover most needsofapplicationdevelopers, then Qt triestouse thefile dialog for theappropriate operating system. 6 If you explicitlywanttouse no nativesystem dialogs,you notifystatic methods of this via theQFileDialog::DontUseNativeDialogflag. In this caseQtusesits own dialog at thecorresponding position. This andother flags aredocumented in Table 6.2.

Value Effect Table6.2: QFileDialog::ShowDirsOnlyShowonlydirectories. Optionsfor QFileDialog QFileDialog::DontResolveSymlinks Do not resolvesymbolic links,but in- terpret them as regularfilesordirec- tories. QFileDialog::DontConfirmOverwrite Do notconfirmwhether an existing fileshouldbeoverwritten. QFileDialog::DontUseSheet Do notdisplaytheOpenfile dialog in MacOSXas asheet. Onlyworks if DontUseNativeDialogis not set. QFileDialog::DontUseNativeDialogAlwaysuse Qt’sowndialog.

File Selection Dialogs

In an initialexample, illustrated in Figure 6.12, wewanttoallowtheusertoselect an image.Todothisweuse thestaticmethod getOpenFileName():

6 An exceptionisprovided bytheX11 platform.Currently,the Portland project (http://portland.freedesktop.org/)isworking on asolutioninwhich applications canuse the filedialogs of thecurrentlyrunning desktopenvironment (GNOME or KDE).

175 6 Dialogs

QString file =QFileDialog::getOpenFileName( this, tr("Pick aFile"), "/home", tr("Images(* .png * .xpm * .jpg)"));

If successful,the method callreturnsafilename; otherwise, it returnsanullQString (which can be checkedwithfile.isNull()). As thefirstargument,the method expects apointer to thedialog’sparentwidget. If anullpointer is given as thefirstargu- ment,thenthe dialog willnot be modal.The nexttwoargumentsare thedialog heading andthe startdirectory. TheQDirclass provides static methods for retrievingthe paths to themostim- portant directories, which can be used as thethird argument insteadofafixed string: 7

QDir::currentPath() This returnsthe currentdirectoryof theapplication. QDir::homePath() UnderWindows, this returnsthe contents of theHOMEenvironment vari- able.Ifthisdoesnot exist, it triestoevaluate theUSERPROFILEenviron- ment variable.Ifthisalsofails,Qtforms thedirectoryfrom HOMEDRIVE and HOMEPATH. If thesevariablesare also notset, themethod calls rootPath(). In Unixsystems, includingOSX,the method uses theHOMEenvironment variable.Ifthisisnot set, rootPath() is used. QDir::rootPath() In Windows, this returnsdriveC:\; in Unix,the root directory/. QDir::tempPath() This is /tmp in Unix,while in Windowsitisbased on theenvironment vari- ablesTEMPand TMP.

Finally,asthe fourthargument,getOpenFileName() expectsafilefilter.Itallows onlyfiles withspecific endingstoappear in thedialog. Here you mustadhereto thefollowingsyntax:

" filetypedesignator ( * . ex1 * . ex2 ... * . exn )"

Thefile type designator can be freelychosen.Itexplains to theuserwhatsortof fileisinvolved.The fileextensionslisted in theparentheses act as theactualfilter for thefilenamescontained in thedirectory.The followingfilter,for example, finds allfilesthatend in .png,.xpm,or.jpg:

7 Foranexamplesee page 178.

176 6.5 Ready-made DialogsinQt

"Images(* .png * .xpm * .jpg)"

Everyfilter should be made localizable,thatis, enclosed bytr(). In this way,readers of otherlanguagescan still enjoyafile type descriptionintheir ownlanguage. If theuserhimself should decide which filtersare to be used,alternatives can be appendedtothe first filter,separated from it bytwosemicolons. Forexample, if thefilter is

"Images(* .png * .xpm * .jpg);;Text files(* .txt)" then acorresponding drop-downdialogappearsinthe Open filedialog, as in Figure 6.12.

Figure 6.12: Thestaticmethods of QFileDialog allowthe useofstandard dialogs with their ownfilters.

We can nowchoosefromexactlythosefilesthathaveeitherone of thethree graphic fileextensionsorthe extension .txtinthisexample. In general, theamount of filtersisofcoursenot limited.

SelectingSeveral Files Simultaneously

If auserneedstobeable to select several files simultaneously,thispossibilityis covered bythegetOpenFileNames()staticmethod. In contrast to itslittlesister getOpenFileName(), it returnsaQStringList, butthe argumentsare identical.Each entryin thereturned listcorresponds to aselected file. This method of QFileDialog also makesuse,inWindowsand OS X, of thenative dialogs of thecorresponding operating system.The followingexampleprintsall theselected files,together withtheir paths,tothe debugstream:

QStringList fileList =QFileDialog::getOpenFileNames( this, tr("Pick aFile"), "/home",

177 6 Dialogs

tr("Images(* .png * .xpm * .jpg)"));

foreach(QString file, fileList) qDebug() << file;

SelectingExisting Directories

Some programsrequire abasedirectoryfor theiroperations. When prompting theuserfor this path, theapplicationmustpreventthe user from specifyinga filetarget. Aphoto-manipulatingprogram,for example, cannotstore itsgallery beneathafile. To ensure that theuserisshownonlydirectories, you can callthe filedialogclass’s getExistingDirectory() method. This doesnot expect afilter anddisplaysonlydi- rectories. Furthermore, it checks to seewhether aspecifieddirectoryreallydoes exist—theuseroranother applicationcould havedeleted it in themeantime. Thefollowingexamplequeries theuserfor adirectory,inwhich theprogram in- tends to storeall itsphotosinfuture. As the base directorywewilluse thehome directoryof thecurrent user:

QString directory =QFileDialog::getExistingDirectory( this, tr("Pleasepick the foldercontaining the photogallery."), QDir::homePath());

SelectingaFilename

If auserwants to saveafile, this fileusuallydoesnot yet exist. getOpenFileName() tests whether thespecifiedfile exists,however,soitisofnouse for savingfiles. We requireanewstatic method that meets therequirementsofstorage semantics. In Qt this function is calledgetSaveFileName(), anditisanalogousinits arguments andinits return value to getOpenFileName(). Thefollowingexamplerequiresthe user to select thenameofafile in which to save data. Here,ifthe user selectsthe name of an alreadyexisting file, theprogram does notquestionthis, thanks to theDontConfirmOverwrite flag. This can be useful,for example, if theprogram wants to investigatethe stateofaffairs outsidethe dialog.

QString file =QFileDialog::getSaveFileName( this, tr("SaveFile As..."), QDir::homePath(), tr("Images(* .png * .xpm * .jpg)"), QFileDialog::DontConfirmOverwrite);

178 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 theparent widgetand theheading of thedialog. Then getInteger() expectsanexplanatory text, which it displaysabovethe inputwidget. This is followed byadefault value andthenthe limitsofthe allowed inputrange.Thisexamplerestricts theupper 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 expression such as QDate::currentDate().year() -200).

179 6 Dialogs

In thenext-to-lastparameter,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 thesamewayas withgetInteger(), you can also prompt theuserfor real numbers withgetDouble(). Thedialoginthe nextexampleexpectsthe pricefor agiven product.getDouble() also expectsthe pointer to theparentwidget, thedialog 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 thelistelement 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 thepredefined 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- lowingexampleintroducesprobablythemostfrequentusage of this type of user input: enteringapassword. Thefirstthree parametersspecifythedetails of theparentwidget, dialog head- ing, anddialogtext, andare followed bythedisplayform, which is specified by

181 6 Dialogs

avalue from theEchoMode enumeratorofthe QLineEditinput widget: 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 andasetFont() 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 settotrue. 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 r te ap 7 Ch

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,Qtalsogenerates itsownevents.Anexampleofthisisthe QTimerEvent, which is triggeredafter aspecific time hasexpired, setbythepro-

185 7 Events, Drag and Drop,and theClipboard

grammer. Such events arealsobased on QEventand areprocessedbytheevent loop. Each QEventhas atype.SubclassesofQEventcan contain arbitraryamountsof information;for example, QMouseEventhandles messagesabout buttons clicked andthe positionofthe mousecursor. Qt passesevents via QCoreApplication::postEvents() specificallyto 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.Ifaneventcannotbe processedimmediatelybythereceiver,the eventisput into aqueue andscheduled for delivery.Ifanother part of theapplicationblocks theapplicationbyexecutinga syncronous long-windedoperation, 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

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 theconstructor,the specialized event handlertimerEvent(), which willupdatethe clocktime. In theupdateTimer and switchTimermembervariableswesavenumbersthatserveasidentifiersfor the timers. TheshowClockstatusflag determineswhether theclock time (showClock= true)orthe date(showClock=false)appearsonthe widget. 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 #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 switchTimer ID,thisonlytoggles theshowClockvariable.The actualworkawaits 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 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 deduce this from the string length anddisplaythestringwithdisplay().

188 7.2 Handling Events

Figure 7.1: Our ClockWidget alternatelydisplays thetime(above) and thedate(below).

AlthoughQLCDNumbercannotdisplayallalphanumerical characters, it doescope withall thecharactersrequiredfor 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 #include "clockwidget.h" intmain(intargc, char * argv[]) { QApplication app(argc, argv);

ClockWidgetw; w.show();

returnapp.exec(); }

7.2.2Using theGeneral 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(e); if (te->timerId() == switchTimer) { showClock =!showClock; returntrue; }

if (te->timerId() == updateTimer) { // handle eventtimerasbefore ... returntrue; } } returnQObject::event(e); }

Otherwise, weworkwiththe te variable in thesamemannerasinthe timerEvent() method (see page 188).One peculiarityis that event(), in contrast to thespecialized eventhandlers, returnsaBoolean value.Thisreveals whether an eventhas been processedornot. If weoverride thedefault event(), wemustnot forgettoforwardall events that wedo not handle to theevent()method of theparentclass.Otherwise, theevent() method of theparentclass would never be calledand theeventhandlingofour classwould be lastinglydisrupted.BycallingQObject::event()unconditionallyin theend,weavoidabroken eventhandling. 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 eventtype,and no need to forwardunhandledevents.Inthiswayit can also be seen from just aglanceatthe classdeclaration which eventhandlersare implemented bytheclass.

7.3Using Event 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 followingsignature: bool QObject::eventFilter(QObject * watched, QEvent * e);

This mustbereimplemented byA. Thewatched parameter allowsevents from sev- eral monitoredobjectstobedistinguished 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 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 ✝ ✆ re 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 thewidgetwithaQVBoxLayout. Theconversation viewcomes into thesplitter at thetop, followed bytheactualinput widget, chatEdit:

// chatwindow/chatwindow.cpp

#include #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 theeventfilter in theinput widgetusing installEventFilter(), 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 theuserpressesthe ✞ ☎ ✝ Tab ✆ key.The callinthiscasehas theeffect that chatEditobtains thefocus before conversationView,sothatthe user can begintyping immediatelyafter theprogram 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 thetab 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(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 genericeventeintoits 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 #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 according 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, andallowsittobeincludedinother 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

QMimeData * prepareImageDrag(const QString&path) { QImage pic(path); QMimeData * mimeData=newQMimeData; mimeData->setImageData(pic ); QList 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 image 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: Application A Application B Aspecific event handlerisresponsible Source Destination forevery Destination::dropEvent()

drag-and-drop step Destination::dragEnterEvent() Destination::dragLeaveEvent()

in Qt. Source::mousePressEvent() Destination::dragMoveEvent() Source::mouseMoveEvent()

7.4.2The Drag Side

We haveseen howto encode datainMIMEformat. Buthowdo theMIMEdata 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 widgetcannotbeclicked, 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 theimage 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(). Thegraphical interfacelinks 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 thepatternfromFigure7.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 #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 widgetthatshouldaccept drops mustfirstactivatethiscapabilityin itscon- structor,withsetAcceptDrops(true):

// droplabel/droplabel.cpp

#include #include "droplabel.h"

DropLabel::DropLabel(QWidget * parent) :QLabel(parent) { setAcceptDrops(true); }

Events to be Handled

Thefirstdropeventthatthe widgetneedstoprocess occurs as soon 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 thereisanimage,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 thedrag-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(); 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 theimage 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, butalsoprovides 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 #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 thegraphics, 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 thedrop 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 basedonaMIME type.The methods mimeData() andsetMimeData()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 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 thetextthe moment it is longer than thewidgetiswide. Byenclosingthe textinside

tags,wecause it to appearcentered:

// drag2clip/d2clabel.cpp

202 7.5 TheClipboard

#include #include "d2clabel.h"

D2cLabel::D2cLabel(QWidget * parent) :QLabel(parent) { setWordWrap(true); setText(tr("

Dragfrom heretoretrievethe text currently" "located in the clipboardorfill the clipboardby" "dragging text from abitrary placesand dropping ithere." "

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 7 Events, Drag and Drop,and theClipboard

Next, dropEvent()doesexactlytheoppositeofmousePressEvent(). It clones the MIME data from theMIMEdatafromthe QDropEvent, after it hasfirstcheckedthe validityof thepointer,sothatitcan forwardthemontothe QClipboardinstance:

// drag2clip/d2clabel.cpp (continued)

void D2cLabel::dropEvent(QDropEvent * event) { if(event&& event->mimeData()) { QApplication::clipboard()->setMimeData(cloneMimeData (event->mimeData())); } }

Figure 7.5: Drag2Clip mediates between the drag-and-drop system andthe clipboard.

Finally,cloneMimeData() uses theformats() method, which contains allMIMEtypes as alistofstrings.Weuse this listtoreadout thedataofanentry,together with itsMIMEtype,using thedata()method. This wecopyto anewQMimeDataobject withthe help of setData(). In this wayweobtainanexact copyof thecontents:

// drag2clip/d2clabel.cpp (continued)

QMimeData * D2cLabel::cloneMimeData(const QMimeData * data) { if (!data) return0;

QMimeData * newData=newQMimeData; foreach(QString format,data->formats()) newData->setData(format,data->data(format));

returnnewData; }

Thetestapplicationwithwhich wewanttoverifythefunctionalityof this in- stantiates theD2cLabeland sets it to asizeof400x150pixels, as beforewiththe DropLabel,sothatthere is enough space available as a“drop space”:

204 7.5 TheClipboard

// drag2clip/main.cpp

#include #include "d2clabel.h" intmain(intargc, char * argv[] ) { QApplication app( argc, argv);

D2cLabel w; w.resize(400,150); w.setWindowTitle(QObject::tr("Drag2Clip")); w.show();

returnapp.exec(); }

Thevisual result of ourworkisshowninFigure7.5.However,the applicationis onlyof interestifyou can pull dataontothe widgetand then retrieveitfromthe clipboardorcopydatatothe clipboard, removeitfromthe widgetvia drag and drop, andplace it somewhere else.

205

r te ap 8 Ch

DisplayingDataUsing “Interview”

In GUI-based applications,dataisoften presented in listand table views, which can be nested withinone another. When usinglistviews, programmers often take averysimple approach:Theysimplyadd values, such as strings, to lists.However, this raises thepossibilitythat whatshouldbedistinctitems in thelistviewcannot be distinguishedfromone another, particularlyif duplicatelabelsare allowed or if theusercan manipulate thestrings used to labelthe listitems.Qt4thereforedoes notsupportpurelystring-based listviews. Asecondapproachtodatavisualizationistorepresent each of thelistentries as aseparateobject.Onpage 157wealreadytackled such item-based lists,which encapsulate each entryas alightweight item.These itemscan be extendedtocon- tain references to datadenoted bythecorresponding listentries,ortoincorporate this datadirectlyin theitem object itself.

207 8 DisplayingDataUsing “Interview”

Althoughthisconcept stands outbecause of itssimplicity,itisnot alwaysthe best solution.Whyshould you packthe resultsofSQL queriesintoitems,thus storing thedataofthe original queryasecondtime? In addition,certain advantagesof SQLwould be lost,suchasincremental deliveryof querieddatatothe application: Theuserhas to waituntil alldataare available,orthe programmerisfacedwith muchmoreworktodisplaythedatafromthe source in an item-based view. ForthisreasonQt4hasamodel/viewconceptcalled Interview .Thisismuch more flexible than item-based programming,but it is also more complex,sothat even professionalsneed awhile to fullyunderstandit. We willthereforefirstlook at theunderlyingideas,and then take acloserlook at some simple examples of model/viewprogramming.Startingonpage 222wewilldevelop ourownmodels, andwewillbrieflydiscussitem-basedviewsonpage 251.

8.1UnderlyingConcepts

Themodel/viewarchitecture is basedonthe idea of separatingdatafromits display. Bydoing so,the same datacan easilybe represented in avarietyof forms,such as lists or tables. Since thedatasourceideallyknowsnothing aboutthe view that displaysit, andaviewknowsnothing aboutthe internalorganizationofthe source,anelement in between is required—the model .Figure8.1 illustrates this relationship.

Figure 8.1: In Interview, the views displaydata fetched viaamodel from anydatasource at all. Forwritable models, changesflow backtothe source.

So that models andviewscan understandone another, themodelhas knowledge of thebasic propertiesofthe views: Each entryoccupies onerowandone column in themodel, andifnecessaryhasalinktoaparentobject.The latter is of importance onlyfor nested lists,not for normal lists andtables. Interviewdescribesthese propertieswiththe QModelIndexclass. With thehelpofthe QModelIndexclass, amodelcan referencethe datatowhich it refers andtherebysupplyit to aviewin theformatthatthe viewdemands. In ordertodisplaydata on thescreen correctly,aviewqueriesthe datasource, via aQModelIndex,invarious roles .Inthe DisplayRole theviewexpectstoreceivetext

208 8.1 Underlying Concepts

to be displayed;inthe DecorationRole,anicon; in theFontRole,the fonttype for some displayed text; andinthe ToolTipRole, thetexttobeshowninatooltip. There aremorethanadozen predefinedroles in total, as Table 8.1shows.

Value Effect(typicalcontent) Table8.1: Qt::DisplayRole Main datatobedisplayed (usuallytext) Rolesinmodels Qt::DecorationRole Additional decoration (usuallyicons) Qt::EditRole Data in aformsuitable for an editor (e.g., the filepathifthe DisplayRole dataisanimage) Qt::ToolTipRoleDatathatshouldbedisplayed byatooltip (text, HTML) Qt::StatusTipRoleDatatobedisplayed in thestatusline(text) Qt::WhatsThisRole Data thatthe widgets should return in the “What’s This?” mode (text, HTML) Qt::SizeHintRole Sizehintfor theelement;isforwardedtothe views(QSizeHint) Qt::FontRole Font type for renderingthe DisplayRole Qt::TextAlignmentRole Alignment of thetextofthe DisplayRole Qt::BackgroundColorRoleBackground color(QColor) Qt::TextColorRole Color for theDisplayRole text(QColor) Qt::CheckStateRole Defines whether an elementiscompletely, partially,ornot at allselected;the val- uesfromthe Qt::CheckState enumeratorare valid:Qt::Unchecked, Qt::PartiallyChecked,1 or Qt::Checked Qt::AccessibleTextRole Screenreaderorother accessibilitytoolsout- putthistext; an examplefor theAccessible- Role is thetextdescription of an icon,which ablindpersoncould notunderstand Qt::AccessibleDescriptionRole Adescription of theitem for accessibilitypur- poses Qt::UserRoleOffset for yourownroles that maybe re- quired in thedevelopmentofyourowndele- gates

Thedata()method of amodelisresponsible for deliveringdataand thereforeneeds to react accordinglywhenitispassedeach type of role,together withamodel

1 In atreeview,Qt::PartiallyCheckedisusefulfor itemswithchildrenofwhich onlyafewcanbe selected,thatis, thosethathavethe Qt::CheckState status.

209 8 DisplayingDataUsing “Interview”

index.Itreturnsdatatothe viewin theformofaQVariant. This enablesthe view to interpret thedataitreceives in accordance withthe role it adopted to make the request. If, given aroleand amodelindex,there is no dataavailable to return, themodelsignals this byreturninganemptyQVariant object as theresultofthe calltodata(). This procedurealsoenablesthe viewto presentdifferent datatypes in accordance withthe various roles. Figure 8.2illustrates theuse of rolesinan exampleofalistview. If thepredefined rolesare notsufficient,you can defineyourownroles andmake them available in themodel. Thepredefined views, however,can’t usethemyet. Butthere is an easier wayto useyourself-defined rolesthaninheriting yourown viewclassfromthe pre-defined viewclass. This is because aso-called delegate is in charge of drawinglistortable entries(seeFigure8.1). Thedelegateprovided byQt that is implemented in theQItemDelegateclass will be sufficientinmostcases.Italsoprovides an editor function for modifyingdata elements displayed in theview.For this to be useful,however,the modelmust supportwriting back to thedatasource, which is notalwaysthe case: Manymodels onlymake information available for reading anddonot providewrite access.

Figure 8.2: Qt::DecorationRole Qt::DisplayRole Rolesdefine the spaceentries can take up in aview.

Qt::ToolTipRole

8.1.1The View Classes

Forthe datavisualizationitself, Interviewprovides threeready-to-useclassesthat wecan admireinuse in Figure 8.5onpage 213:

QListView This classdisplaysone-dimensional lists (Figure8.5 at thetop left)and also hasaniconmode in which it showsall of itsentries arranged as icons(Figure 8.5atthe topright). QTreeView This classdisplayslists in tree form(Figure 8.5atthe bottomleft) andisthus more elaboratethanQListView.Inaddition this classcan displayseveral columns,which QListViewis notcapable of doing.

210 8.1 Underlying Concepts

QTableView This classdisplaysdatainatable (Figure8.5,bottomright). At thetop and side arethe rowandcolumn headings, which can be individuallycustomized.

In addition,QHeaderViewprovides headerlines for QTreeViewas wellasheader columns andlines for QTableView.Thisclass is notusedasaseparateview,but it can be adjusted to yourownrequirementsand then used in instancesofQTreeView andQTableView. TheseviewsinheritfromQAbstractItemView,the base classofall viewsinInterview. If you look more closelyat theinheritancestructure in Figure 8.3, you willrealize that QAbstractItemViewdoesnot inheritfromQWidgetdirectly.Rather, theview classesare basedonQAbstractScrollArea, aclass that providesawidgetwithan embeddedwidget. Theembeddedwidget, calledthe viewport ,can be manytimeslargerthanthe widgetthatenclosesit. Theframe widgetdisplaysvertical or horizontalscrollbars where appropriate. In this waytheviewcan make available muchmorespacefor thedatathanwas actuallyallocatedfor it in thelayoutinquestion; of course,the user mustthennavigatethrough theviewportusing thescrollbars.

Figure 8.3: Allviewclassesare derived from QAbstractItemView .

8.1.2The ModelClasses

Allthe models that areincludedinQtalsoinheritfromacommonabstract base classcalledQAbstractItemModel. Abstract here meansthatthe classcannotbein- stantiated directly,because it contains unimplemented methods.The programmer can create objectsonlyfrom asubclassthatistailoredfor aspecific purpose and implements themissing methods.Ascan be seen in Figure 8.4, everymodelisalso aQObject,and thus benefitsfromautomatic memorymanagement. QAbstractItemModel is notthe onlyabstract classinthe inheritancemodel. If you look more closelyat theinheritancestructures, you willsee twoadditional classes derived from QAbstractItemModel,calledQAbstractListModel andQAbstractTable- Model,whose furtherspecializethatofQAbstractItemModel withlistortable views. Theseabstract subclassesalsocannothaveobjectsdirectlyinstantiated from them.

211 8 DisplayingDataUsing “Interview”

Figure 8.4: QAbstractItemModel Theinheritance hierarchyofmodels QAbstractListModel QAbstractTableModel QDirModel QProxyModel QStandardItemModel in Interview QStringListModel QSqlQueryModel

Forcompatibilityreasons, classesbased on QAbstractListModel andQAbstractTable- Model can be used in tables, nested lists,and one-dimensionallists.The practical benefitsofdoing so,however,are strictlylimited if you tryto usetheminQListView andQTableView,respectively.QAbstractListModel,for example, reducesthe number of usable columns to onesingleone—which makeslittlesense in atable view. With QStringListModel, Interviewalso provides aspecific implementation of QAb- stractListModel,aneditable model, thedatasourceofwhich is astringlist. If the user changesastring in theview,the modeladjusts thecorresponding entryin the string list. QStandardItemModelallowsdatatobestoreddirectlyin themodel. Althoughthis contradictsthe basicideathatamodelonlyprovides apuremediation service between thedatasourceand views, it turnsout to be verypractical in certain applicationcases withmodest data-handlingrequirements. Forlarge applications, however,QStandardItemModelisusuallytooinflexible. QAbstractProxyModel replaces theQProxyModel classinQt4.1,which was intended to extract datafromamodel, manipulate them, andreturn them to anewmodel, for example, to filter thedata. Butthe classturnedout to be tooinflexible,and was thereforereplaced. Trolltechadvisesagainstusing it in newprojects.QAb- stractProxyModel is powerful, butagainitisanabstract class, andsoitrelieson subclassestoimplement thefunctionalityit offers. In orderthatthe effortinusing thenewclassisnot larger than that required for the directlyusable QProxyModel,Trolltechprovides theQSortFilterProxyModel subclass. It allowsyou to performthe most frequentlyperformedtasks of aproxy model, mainlyfilteringand sorting, without theneed to first derivefurther subclasses. Finally,Interviewprovides amodelinQDirModelthatcan be used,ifrequired, to projectadirectoryhierarchyin QListView,QTreeView,and QtableView.

8.2DisplayingDirectory Hierarchies

We arenowreadyto acquireour first practical experience withInterviewandcreate asmall program,using QDirModeland thethree ready-to-useviews, which displays thehomedirectoryin fourdifferent views(Figure 8.5).Inthe source code wefirst instantiate, in additiontothe obligatoryQApplicationobject,aQDirModel. You

212 8.2 DisplayingDirectory Hierarchies

againhavetorememberthathere, as an exception, weallocate themodelnot on theheapbut on thestack, because ourentirecode is locatedinthe main() method.2 In thenextstep wecreateawidgetwithatable layout, into which weinserttwo listviewsatthe topand atreeviewandtable viewat thebottom. In doing so,we switch thesecondlistviewto icon mode.For each of theviewsweuse theinstance of QDirModelthatwas just created as themodel, which wepassontothe view’s setModel()method as apointer.Thiswaywebindthe viewstothe model. Beforewecan workwiththe views, wehaveachicken-and-eggproblemtosolve: Theviewshavetoknowthepathtothe directorythat theyshould initiallydisplay. Butstrictlyspeaking,theycannotlearn this path, as theythemselves onlyworkon thedirectorymodel. Forthisreason, QDirModelprovides an overloadedversion of theindex() method. This function usuallyexpectsatripletofindices (column, row,and parent) as an argument,arepresentation which is of no help to us here.The overloadedversion, on theother hand,acceptsapathdescription encodedasaQString.

Figure 8.5: Four views,one model as thesource: Here theQt QDirModel is used to displaythe contents of the current directory.

Then wespecifythedirectorybeneathwhich theviewsshouldoperate. Since all viewsaccess themodelindependentlyof each other, wemustalsoset theindex separatelyfor each view.After that,weonlyneed to displaythewidgetand start theeventloop. Thecode for theapplicationisshownbelow:

// diransichten/main.cpp

#include intmain(intargc, char * argv[]) {

2 SeeSection 1.2.2onpage31.

213 8 DisplayingDataUsing “Interview”

QApplication app(argc, argv);

QDirModel dirModel; QWidgetw; w.setWindowTitle(QObject::tr("Four " "directory views using one model")); QGridLayout * lay=newQGridLayout(&w);

QListView * lv=newQListView; lay->addWidget(lv,0,0); lv->setModel(&dirModel);

QListView * lvi=newQListView; lay->addWidget(lvi, 0,1); lvi->setViewMode(QListView::IconMode); lvi->setModel(&dirModel);

QTreeView * trv =newQTreeView; lay->addWidget(trv,1,0); trv->setModel(&dirModel);

QTableView * tav=newQTableView; tav->setModel(&dirModel); lay->addWidget(tav,1,1);

QModelIndexcwdIndex=dirModel.index(QDir::currentPath()); lv->setRootIndex(cwdIndex); lvi->setRootIndex(cwdIndex); trv->setRootIndex(cwdIndex); tav->setRootIndex(cwdIndex);

w.show();

returnapp.exec(); }

8.2.1Using View Classesinthe Designer

Unfortunately,QDirModelhas onesignificantrestriction:Because theviewsdonot react to mouseclicks,wehavetobuild this functionalityin ourselves.Inaddition, onlyoneelement can be selected in each of theviews. If you wanttoallowseveral itemstobeselected,thenyou willalsohavetogoitalone.Wecan correct these shortcomings bydesigningour ownfile dialog, such as can be seen in Figure 8.6. As aniceside-effect,wewillget acquainted withhowto useviewclassesinthe Designer. Ourfile selectiondialogisbased on theDesignertemplateDialogwithButtons andconsistsofacomboboxwiththe available drives,abutton(Tool Button) to

214 8.2 DisplayingDirectory Hierarchies

theright of this,and astackedwidget.3 Thetwosmall trianglesinthe upper- rightcornerofthe stacked widgetalsoallowus to movethrough thestack in the Designer.Asthe first widget, weplace alistviewon thestack.

Figure 8.6: Thefile dialog in the icon mode

TheInsertpage entrylocatedinthe contextmenu 4 (Figure8.7)makesitpossible to add newpagesinwhich thereisspacefor furtherwidgets,ifrequired. In this way wecan extendthe stack byaddinganother listviewandatree view.

Figure 8.7: Quicklyclicked together:combo box, button, andstacked widgetwithlist view

Later wewanttouse thebutton—theobject name of which weset to switchButton in thePropertyEditor, andwhose textpropertywechangetoV(for View ;ifyou wantsomethingelse, you can select asuitable icon in theiconproperty)—to switch back andforth between thedifferent views. We placethe comboboxwithatool buttonintoaframe. To do this wemustfirstpositionthe frameand then placethe comboboxandtool buttoninsideit. If theframe becomescolored beforethe drop, then it will acceptthe widgets as children. We arrangebothofthemhorizontally

3 Figure 8.8showsall therequiredwidgets at aglance. 4 Thecontextmenuofthe stackedwidgetonlyopens if you right-clickwhile hoveringdirectly abovethe twosmall triangles.

215 8 DisplayingDataUsing “Interview”

via theLayoutmenuinthe contextmenu. We also rename theOKbutton: After a right-clickand thesubsequentselection of Change text. ..,wecan type in thenew textlabel Open,replacing OK.

Figure 8.8: Thedialoginthe draftviewwiththe required toolsfrom thetoolbox

Finallyweuse alayouttointegrate thelabel,the stacked widget, andthe horizontal buttonlayout(usingthe Layoutentryin thecontextmenu). So that theviewscan take up as muchspaceaspossible in theirstackedwidget, weselectthe three page objectsbeneath thestackedwidgetinthe object inspectorand setthe value for margin in thePropertyEditorinthe Layoutsection of thestackedwidgetto 0.Finallyweassign theobject name iconViewto oneofthe twolistviewsand change theviewModepropertyof theiconViewlistviewto iconMode.Itshould then displaythedirectorycontents as showninFigure8.6,while theother listview displaysthe information for each fileinaseparaterowof theview. In this basicframeworkitisappropriate to enable multipleselections. This is done via theselectionMode property.Inall threeviewsweswitch this propertyto Ex- tendedSelection so that theusermayselect several entriesatthe same time. ThedialogitselfwecallFileDialog bysetting theobjectNamepropertyaccordingly. Then wesavethe fileasfiledialog.ui in aseparatedialogcalledfiledialog.

8.2.2Implementingthe Functionalityofthe File Selection Dialog

Aftergivingthe standardinclude guards in theheaderfile filedialog.h, which con- tainsthe declaration of theFileDialog class, we#includeeach classdefinition that uicgenerates from theUIfile created bytheDesigner. Theforwardclass declara- tionspreventthe need to read in thecorresponding headerfilesfor thoseclasses at this point:

// filedialog/filedialog.h

#ifndef FILEDIALOG_H #define FILEDIALOG_H

216 8.2 DisplayingDirectory Hierarchies

#include "ui_filedialog.h" class QModelIndex; class QDirModel; class QItemSelectionModel; class FileDialog: public QDialog, privateUi::FileDialog { Q_OBJECT public: FileDialog(QWidget * parent=0); ... private: QItemSelectionModel * selModel; QDirModel * dirModel; } ;

#endif // FILEDIALOG_H

As wellasQDirModel, wenowrequireaselection model ,which managesand com- pares theselectionsfor theviewsassigned to it.AccordinglywecreateaQItemSe- lectionModelinaddition to thedirectorymodelinthe FileDialog constructor,after wehavecalledsetupUi()toinitializethe Designer-generated widgets.Todothis weassign thedirectorymodeltothe QItemSelectionModelconstructor.Thisway, theselection modelknowsthe datasource, andassuchcan manage theentries. Nowweassign allviewsthe same model, via setModel()and thesameselection modelvia setSelectionModel(). Thelatter step ensuresthatthe same selections areswitched on for allthree views: If you select several files in oneview,these automaticallyappearhighlighted in theother twoviewsaswell:

// filedialog/filedialog.cpp

#include #include "filedialog.h"

FileDialog::FileDialog(QWidget * parent) :QDialog(parent) { setupUi(this);

dirModel =newQDirModel; selModel =newQItemSelectionModel(dirModel);

listView->setModel(dirModel); treeView->setModel(dirModel); iconView->setModel(dirModel);

listView->setSelectionModel(selModel); treeView->setSelectionModel(selModel);

217 8 DisplayingDataUsing “Interview”

iconView->setSelectionModel(selModel);

QModelIndexcwdIndex= dirModel->index(QDir::rootPath());

listView->setRootIndex(cwdIndex); treeView->setRootIndex(cwdIndex); iconView->setRootIndex(cwdIndex);

Theviewsstill requireanentrypoint for themodel, however,which weset using setRootIndex(). This function expectsaQModelIndexas an argument,but weare usingthe semanticsofthe filesystem here.Tomediate between thesetwo“worlds” weuse theoverloadedindex() method from QDirModelasa“translator”: This ac- ceptsapath, searches for thematchingindexin themodel, andreturnsit. Then wefill thecombo boxgenerated in theDesignerwiththe base directories (Windows) or theroot directory(Linux). Because QDir::rootPath() consists of drive C: in Windows, thequestionarises, howdo weobtainalistofall available drives? As an answer,werecommend abrief digression on thewaymodels function in generaland howQDirModelfunctionsinparticular.

Figure 8.9: Thestructure of a modelusingthe example of QDirModel

Amodelbasicallyhastwodimensions: In aQDirModeleach rowcorresponds to one fileentry,whereas each of thecolumns contains onefile property(name,size, date of creation). If afile entryalso pointstoavalid QModelIndex,thiscorresponds to a subdirectory.Ascan be seen in Figure 8.9, this forms athird dimension, as it were. Althoughthe listand table viewscannotdisplaythis additionallevel of structure, thetreeviewsvisuallyrepresentdataitems from thesource(i.e., thefilesystem) that arethemselves valid QModelIndexobjectsassubtrees. This explains thecode that appears nextinthe constructorofthe filedialog:

// filedialog/filedialog.cpp (continued)

for(intr=0; rrowCount(QModelIndex()); ++r) { QModelIndexindex=dirModel->index(r,0,QModelIndex()); if (index.isValid()) comboBox->addItem(dirModel->fileIcon(index), dirModel->filePath(index)); }

218 8.2 DisplayingDirectory Hierarchies

An invalid (i.e., empty)QModelIndexmeansthatthe modelshouldselectthe root level in thefilesystem as thestart index.Inour casethislevel contains allthe drives in Windows, andonlythedirectorytree root in Linux.Wedetermine thenumber of driveentries via rowCount(), so that withthisknowledge wecan iteratethrough allentries.For this purposeweuse thezerothcolumn, sinceQModelIndexprovides us withthe desiredinformation at thesepositions.Tobeonthe safe side,wecheck whether theindexreallyis valid.Inthiscaseweadd an entryfor thecorresponding drivetothe combobox. To be able to workwiththe models,werequire anumber of slots, which wedeclare in theheaderfollowingthe declaration of theconstructor:

// filedialog/filedialog.h(replenished) ... protected slots: void switchToDir(const QModelIndex&index); void syncActive(const QModelIndex&index); void switchView(); ... switchToDir()shouldreact to clicks andupdatethe otherlistviewssothatthey also showthedirectoryselected.syncActive()compares the active entry,thatis, theone highlighted in colorinall threeviews, andopens at thecorresponding branch in thetreeview,while switchView() reactstothe togglebuttonand runs throughthe viewsstackedontop of each otherinthe stacked widget. We nowneed to connect each of thesenewslotsinthe constructorwiththe acti- vated() signal of allthree views. switchToDir()and syncActive()require theQMod- elIndexas an argument,which references thenewdirectory.FinallyweinstructQt to callthe switchView() slot if thereisaclickonthe switchButtondefinedinthe Designer:

// filedialog/filedialog.cpp (continued)

connect(listView,SIGNAL(activated(const QModelIndex&)), SLOT(switchToDir(const QModelIndex&))); connect(treeView,SIGNAL(activated(const QModelIndex&)), SLOT(switchToDir(const QModelIndex&))); connect(iconView,SIGNAL(activated(const QModelIndex&)), SLOT(switchToDir(const QModelIndex&)));

connect(listView,SIGNAL(clicked(const QModelIndex&)), SLOT(syncActive(const QModelIndex&))); connect(treeView,SIGNAL(clicked(const QModelIndex&)), SLOT(syncActive(const QModelIndex&))); connect(iconView,SIGNAL(clicked(const QModelIndex&)), SLOT(syncActive(const QModelIndex&)));

219 8 DisplayingDataUsing “Interview”

connect(switchButton, SIGNAL(clicked()),SLOT(switchView())); }

Theconstructor is nowfinished, andwecan turn ourattention to theimplemen- tation of theslots:InswitchToDir()wefirstcheck to seewhether theindexpassed reallyis adirectory.The appropriate method is containedinQDirModelitself. If this is thecase, weset thestart indexin themodeltothe newdirectory.Note: We do notneed to switch over to thetreeview,since this type of viewshould always displaytheentiredrivecontents.Since thetreeviewuses thesameselection model as theother views, it automaticallyshowsthe selected entries:

// filedialog/filedialog.cpp (continued)

void FileDialog::switchToDir(const QModelIndex&index) { if (dirModel->isDir(index)) { listView->setRootIndex(index); iconView->setRootIndex(index); } }

syncActive()compares theactiveentryin allthree views. Thecorresponding API callinQAbstractItemViewis setCurrentIndex():

// filedialog/filedialog.cpp (continued)

void FileDialog::syncActive(const QModelIndex&index) { listView->setCurrentIndex(index); treeView->setCurrentIndex(index); iconView->setCurrentIndex(index); }

Theslottoswitch throughthe viewsisjustone lineinlength: It queriesthe index of thecurrent widgetand increasesthisbyone. To ensure that theindexdoesnot become larger than thenumber of views, weinsertamodulo operation (whenthe value becomesgreater than theindexof thelastview,weshouldlandback in the first view,which hasindexzero):

// filedialog/filedialog.cpp (continued)

void FileDialog::switchView() { stackedWidget->setCurrentIndex( (stackedWidget->currentIndex()+1)%stackedWidget->count()); }

220 8.3 TheStringLists Model

Finally,wemake theselected files available to theuser. To do this wedefine a method calledselectedFiles(). After thedialogends—whenthe user clicks Open (the Designer hasalreadylinkedthe corresponding signal to theaccept() slot)—you can read outthe selected filenames (together withtheir paths)asaQStringList usingthisFileDialog method:

// filedialog/filedialog.cpp (continued)

QStringList FileDialog::selectedFiles() { QStringList fileNames; QModelIndexList indexes=selModel->selectedIndexes(); foreach(QModelIndexindex,indexes) fileNames.append(dirModel->filePath(index) ); returnfileNames; }

Whichentries arereturned is revealedbytheselection model: selectedIndexes() returnsthe selected QModelIndexentriesfromthe QDirModelinstance. With the filePath() method provided bythemodel, wecan obtain thefile paths.

8.3The String ListsModel

Simple, text-basedlists arepresented in Interviewvia theQStringListModel class. This operates on astringlistthatitdisplaysinacolumn. Each entryin thelistthus corresponds to arowin themodel. Thefollowingexampledepositsashopping listintoastring listand passesittothe model. As soon as weassign thestringlistmodeltothe listview,itdisplaysthe list entries:

// stringlistenmodell/main.cpp

#include intmain(intargc, char * argv[]) { QApplication app(argc, argv); QStringListModel model; QStringList toBuy; toBuy << "butter" << "milk" << "cherries" << "bananas"; model.setStringList(toBuy); QListViewview; view.setModel(&model); view.show(); returnapp.exec(); }

221 8 DisplayingDataUsing “Interview”

Themodelalsohas write accesstothe datasource: If theuserchanges an entry in theview(perhaps byclicking an entryandtyping somethinginthe editor that then appears), themodelwrites thechanges back to thestringlist, which can be read outagainatanytime withthe stringList() method.

8.4ImplementingYourOwn Models

Thebestwayto understandhowmodels areconstructed is to create yourown model. Initially,itshouldonlyread datafromasource anddisplaythis in aview. Later wewillmake it writable,sothatchanges to theviewwill cause thedata source to be updated.

8.4.1AnAddressBook Model

Ourexamplemodelshouldreadanaddressbook from aCSV file. Such adatasource is formatted as follows:

’’title (column 1) ’’,’’title (column 2)’’,...,’’title (column n) ’’ ’’value ’’,’’value ’’,...,’’value ’’ ’’value ’’,’’value ’’,...,’’value ’’ ’’value ’’,’’value ’’,...,’’value ’’

We regardthisdataasspecifyingatable,where thefirstlinereveals thecolumn headings, andall subsequent lines containthe entriesineach of therows. Because atwo-dimensionaltable should be formedfromthisfile,weuse QAbstractTable- Model as thebaseclass,since,incontrasttoQAbstractItemModel,itimplements an index() method that is suitable for alistwhose itemshaveseveral columns. Theconstructor in ourmodel, defined in addressbookmodel.cpp, receives theentire address book as aQString.Inorder to workwiththis, wesplit it up into theindi- viduallines of data(usingQString::split(), specifyingthe newline character, n, as afieldseparator). Ourmodelwillrepresent theaddressbook internallyas a QList calledaddressBook,inwhich each string listinthe QListisa dataset corresponding to oneaddress. Given alineofaddressbook data, thehelp function splitCSVLine()separates thelineintothe components of theaddress, re- moves thequote characters, andreturnsthe resultingdataset. Theconstructor uses this helper function to turn each of thelines of addressbook dataintoadataset andpacksthemintoaQList,thus producing thedatastructure wedesire:

// addressbook/addressbookmodel.cpp

#include #include "addressbookmodel.h"

222 8.4 ImplementingYourOwn Models

QStringList splitCSVLine(const QString&line) { bool inItem =false; QStringList items; QString item;

for(intpos=0; pos

AddressbookModel::AddressbookModel(const QString&addresses, QObject * parent):QAbstractTableModel(parent) { QStringList records=addresses.split(’\ n’); QStringList line; foreach(QString record, records) addressBook.append(splitCSVLine(record)); }

We knowthat ourmodelmanages n − 1 datasets,where n is thenumber of rows of actual datainthe CSVfile,since thefirstlineofthe CSVfile contains thecolumn names. In addition,CSV files finishwithanunused emptyline. Thenumber of data lines ( rows)isthereforeexactlytwolessthanthe totalnumber of lines in theCSV file. Aviewcan usethe rowCount() method to findout thenumber of rowsofdata containedinthe model. Since thereference to theparentQModelIndexpassed via theparentparameter variable is notneeded—after all, this is just aflat two- dimensionalmodel—wecan suppressanyirritating compilerwarningsusing the Q_UNUSED macro, which in additionserves to explicitlydocument that wedonot wanttouse thevariable:

// addressbook/addressbookmodel.cpp (continued) intAddressbookModel::rowCount(const QModelIndex&parent)const {

223 8 DisplayingDataUsing “Interview”

Q_UNUSED(parent); returnaddressBook.count() -2; }

To determine thenumber of columns,welook at thedataset from thefirstlineof theCSV file. TheQStringList::count() method is used to determine thenumber of stringsinthe string listthatcontainsthe dataset corresponding to thefirstlineof thefile,obtainedfromaddressBook byinvokingat(0):

// addressbook/addressbookmodel.cpp (continued)

intAddressbookModel::columnCount(const QModelIndex&parent)const { Q_UNUSED(parent); returnaddressBook.at(0).count(); }

Viewsthatuse ouraddressbook modelcan discover thelabelingofthe rowsand columns via theheaderData()method. To do so,theymustspecifythenumeric positionofthe section of datafor which theheading is desired, where asection is either arowor acolumn—whether thedesired heading is arowheading or acolumn heading is decidedbythevalue given for theorientation.Thisisof theenumeration type Qt::Orientation andhas thepossible valuesQt::Vertical or Qt::Horizontal. When it comestoroles,inthisexampleweare interested onlyin supporting the DisplayRole,which is used whenthe viewneedsthe texttobedisplayed.Everything else wepassontothe implementation of theoverclass. QAbstractTableModel does more than just return emptyQVariants: If wehad notreimplemented headerData(), it would number allthe rowsand columns.Inorder to ensure that wecan use themodellater withaQTableViewthat also queriesrowdescriptions,wecallthe headerData()function of theoverclass, particularlyin casethe orientationis not horizontal. This meansthatbydefault, alabel to theleftofthe datasets willdenote thedataset number.For horizontalorientation weuse theentries from thefirst dataset in thelist, which as weknowcontains thecolumn names:

// addressbook/addressbookmodel.cpp (continued)

QVariantAddressbookModel::headerData(intsection, Qt::Orientation orientation, introle)const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { returnaddressBook.at(0).at(section); } } returnQAbstractTableModel::headerData(section, orientation, role); }

224 8.4 ImplementingYourOwn Models

Finally,the modeldeliversactualdatatoaviewvia thedata()method. As argu- mentswepassaQModelIndex,which contains thepositionrequested bytheview, andthe requested role:

// addressbook/addressbookmodel.cpp (continued)

QVariantAddressbookModel::data(const QModelIndex&index, introle)const { if (!index.isValid()) returnQVariant(); QStringList addressRecord=addressBook.at(index.row()+1); if (role == Qt::DisplayRole || role == Qt::EditRole) { returnaddressRecord.at(index.column()); } if (role == Qt::ToolTipRole) { QString tip,key,value; tip="

"; intmaxLines=addressRecord.count(); for(inti=0; i") .arg(key,value); } tip+="
%1: %2
"; returntip; } returnQVariant(); }

Firstwecheck that theindexpassedisvalid—a good practice that prevents nasty crashesinInterviewprogramming,since out-of-rangeindices can alwaysoccur. Then weretrievethe desireddataset from thelist. In doing this weaccess the dataset followingthe oneinthe rowspecified bytheindex—after all, thecolumn headingsare in thefirstrow. We deliver an address dataset ourselves if theviewasks fordatainthe DisplayRole. To extract this,weproceed in almost exactlythesamewayas beforewhenreading outthe headings, withone difference: We localizethe dataset via therow() detail of theindex. Since themodelissupposed to be editable,wemustalsohandlethe situation in which theviewasks fordatainthe EditRole:Because thesametextshould appearlater on in theeditorasinthe DisplayRole,wehandlethe EditRole-and Qt::DisplayRole queriesinone go. To make theexamplealittlemoreinteresting,wewillalsoimplement theToolTip- Role at this point.Atooltip is ayellowboxthat containsadescriptionofaview

225 8 DisplayingDataUsing “Interview”

elementand appears if you lingerover it withthe mouse. In thetooltipfor alisten- try,our address book viewwill showthecomponents of thedataset corresponding to that entry,asdepicted in Figure 8.10.Itisour aimtodisplayonlythenonempty components in thedataset.

Figure 8.10: Thanks to the treatment of the ToolTipRole ,the views nowshowan individual tooltip when themouse lingers over an entry.

HTML formatting can be used in tooltiptexts,and weconstruct thedescription of theaddressbook entryusingthe

tag. Each rowof thetable consists of twocells,one of which contains thenameofthe address book field (the key)and theother,the matching value.Bothofthemwillbeshowninthe tooltiponlyif the value is notthe emptystring.Weobtainthe keybycallingthe headerData()method just implemented,and weobtainthe value byreading it outofthe currentdataset. We formatthe cells usingthe and
tags andappend theresulting HTML phrase to thestringthatwestarted previouslywiththe table tag. Finally,when allofthe fieldsofthe dataset havebeen processed, wecomplete thetooltiptext string byappending theclosing
tag, andreturn thefinished string. We willnowwrite aprogram to testour model. This reads in theCSV file, allocates amodel, andpassesthe file’scontents to themodelasaQString. We then bind themodeltoalistview,atable view,and atreeview.The result is showninFigure 8.11.

// addressbook/main.cpp

#include #include "addressbookmodel.h"

intmain(intargc, char * argv[] ) { QApplication app( argc, argv);

// Open the addressbook file in the working directory QFile file("addressbook.csv"); if (!file.open(QIODevice::ReadOnly|QIODevice::Text) ) return1;

// Readits contentintoastring QString addresses=QString::fromUtf8(file.readAll());

226 8.4 ImplementingYourOwn Models

AddressbookModel model(addresses);

QListViewlistView; listView.setModel(&model); listView.setModelColumn(0); listView.show();

QTreeViewtreeView; treeView.setModel(&model); treeView.show();

QTableViewtableView; tableView.setModel(&model); tableView.show();

returnapp.exec(); }

Thecolumn to be displayed bythelistviewcan be selected from themodelwitha setModelColumn() instruction; for example, setModelColumn(2)would displayall first namesinsteadofthe formatted name.

Figure 8.11: Thesethree views use ouraddressbook model.

If amodelyou havewritten yourselfdoesnot turn outtoworkasyou wished, you should first checkwhether theoverriddenconst methods havebeen declared properly.Since theyshould merelyprovideinformation aboutthe model, Interview doesnot givethemanywrite accesstothe class. If theconst keywordismissing, thecorrect method willnot be available,because theinheritancemechanism of C++makesadistinctionbetween constand non-constversionsofamethod.

8.4.2Making Your OwnModels Writable

Just outputting datainthe EditRole is notenoughifyou wanttomodifythedata source via themodel. In ordertobeable do this,weneed to overwrite theflags() andsetData()methods. flags() returnsspecific properties of an indexentry,so-called flags (see Table 8.2). Viewsuse this method to checkwhether operationsare allowed for aspecific item.

227 8 DisplayingDataUsing “Interview”

Table8.2: Value Effect ItemFlags formodels Qt::ItemIsSelectable Elementcan be selected Qt::ItemIsEditable Elementcan be modified Qt::ItemIsDragEnabledElement can be used as astartingpoint for drag- and-drop operations Qt::ItemIsDropEnabledElement can be used as atargetfor drag-and- drop operations Qt::ItemIsUserCheckable Elementhas aselection status withtwostates (selected,deselected); requires theimplementa- tion of Qt::CheckStateRole in themodel Qt::ItemIsEnabledElement reactstouserrequests Qt::ItemIsTristate Elementhas aselection status withthree states (selected,not selected,partiallyselected); use- fulinhierarchical models where several child en- triesare selected andothersnot selected;requires theimplementationofQt::CheckStateRole in the modelinadvance

We willcomeback to theservices of this method on page 241, whenweequip our modelwithdrag-and-drop capabilityfor data. Here wemustfirstmake allthe cells editable:

// addressbook/addressbookmodel.cpp (continued)

Qt::ItemFlagsAddressbookModel::flags(const QModelIndex&index) const { if (!index.isValid()) return0;

returnQAbstractItemModel::flags(index) |Qt::ItemIsEditable; }

Nowtheusercan edit everyposition. To do this,the viewsuse aQItemDelegateby default, just as forthe display.Onceithas finishedits work, it calls thesetData() method to storethe newdatainthe model. As soon as this hasstoredthe data successfullyin themodel, it returnstrue. setData() is thecounterpart to data(): Bothfunctionsmustworktogether.Since thestandardimplementationofsetData()doesnothing more than return false,we need to reimplementitasfollows:

// addressbook/addressbookmodel.cpp (continued)

228 8.4 ImplementingYourOwn Models

bool AddressbookModel::setData(const QModelIndex&index, const QVariant&value, introle) { if (index.isValid() && (role == Qt::EditRole || role == Qt::DisplayRole)) { // add 1tothe rowindextoskipoverthe header addressBook[index.row()+1][index.column()]=value.toString(); emitdataChanged(index,index); returntrue; } returnfalse; }

Firstwecheck, as always, whether theindexis valid.Thistimewealsoensurethat weare locatedinthe editingorinthe displayrole.Wedonot havetomake a distinctionbetween thesetworoles for ouraddressbook model, because in both cases thesamestringisinvolved.Other models mayneed to make adistinction between thetworoles;for example, whendeliveringorupdatingimage data, a modelmayneed to workwithactualpixmaps for theDisplayRole,but withpaths to pixmaps wheninthe EditRole. If theseconditionsapply,weset thenewvalue,passedvia value,atthe appropriate point.Hereweuse theindexoperator[], insteadofat()asusual,inorder to avoid thenormallydesirable behaviorofat(): Themethod provides onlyaconst reference to thestring, whereas theindexoperatorprovides asimplereference. 5 Aftersuccessfullychanging thedata, it is important that thedataChanged() signal is emitted so that theviewslinkedtothe modelupdatethe data. This demands twoindices as parameters, of which therowandcolumn propertiesshouldform arectangle.Because usuallyonlyonevalue is beingchanged at atime, wepass thesameindextwiceinorder to indicate thepositionofthe corrected dataitem. Finallywesignalthe successful completion of theprocess withreturn true.Inall othercases,inwhich wehavenot saved anything, weconsequentlyreturn false.

Insertingand RemovingRows

So that themodelwillbecompletelyflexible,weimplement theinsertion andre- moval of rows. To do this weoverwrite theinsertRows()and removeRows()meth- ods.The equivalentremoveColumns() andaddColumns() methods also existtore- moveorinsertcolumns,but weare notconcerned withthese at this point.As parameterswepassanindexto therowbeneathwhich wewishtoinsertempty rows, as wellasthe number of rowstobeinserted.Wecan safelyignore theparent argument. To insert an emptyrow,wefirstneed to create an emptydataset. To do this wefill astringlistwithasmanyemptystringsasthere arecolumns in themodel. Then we

5 On this subject,see alsopage400 in AppendixB.

229 8 DisplayingDataUsing “Interview”

informthe model, withbeginInsertRows(), that wewanttoinsertrows. If wedo notdothis, existing selections in this modelcould getmixed up.Next, weinsertthe emptydataset—againincrementingthe rowby1because of theheader—andend theinsertmode.Finally,wesignalthatthe datahavebeen successfullyinserted,by returningtrue:

// addressbook/addressbookmodel.cpp (continued)

bool AddressbookModel::insertRows(introw,intcount, const QModelIndex&parent) { Q_UNUSED(parent); QStringList emptyRecord; for(inti=0; i

returntrue; }

We implementremoveRows()inthe same way,but withasafetycheckinthiscase: If thereare more lines to be removed than thereare datasets in theaddressbook, wereturn false—otherwise, wewould runthe risk of theapplicationcrashing. We also need to announcethe removal of lines andsignalthe endofthe action. If everythingwas successful,wereturn true to thecallerasaconfirmation:

// addressbook/addressbookmodel.cpp (continued)

bool AddressbookModel::removeRows(introw,intcount, const QModelIndex&parent) { Q_UNUSED(parent); if (row-count-1 >addressBook.count()-1)returnfalse; beginRemoveRows(QModelIndex(),row,row+count-1); for(inti=0; i

With thesechanges,aprogram that accessesthe modelcan delete datasets by callingremoveRows()oradd them byinsertingemptydatasets withinsertRows() andfillingthemvia setData()—themethod is notreserved just for delegates.

230 8.5 Sortingand FilteringDatawithProxyModels

Outputting theContents of theModel

In ordertoround off ourmodeland to complete ourtourthrough theworld of writable models,weconstruct amethod calledtoString(), which converts thecon- tents of themodelback into CSVformand outputsthisasastring. To do this wegothrough allthe datasets anduse theQStringList method join to combineeach string listintoasingle lineinwhich theindividualstrings aresep- arated from oneanother withcommas(,).Weterminate each linewithanewline characterbeforebeginning thenextline, which ensuresthatthe desiredemptyline at theend of theCSV fileiscreated:

// addressbook/addressbookmodel.cpp (continued)

QString AddressbookModel::toString() const { QString ab; foreach (QStringList record, addressBook) { ab +=" \ ""; record.join("\ ", \ ""); ab +=" \ " \ n"; } returnab; }

To savethe currentstatusofthe model, you nowonlyneed to savethe return value from toString().

8.5Sortingand Filtering Datawith ProxyModels

Ourmodelupuntil nowhaslacked thecapabilityto return itsentries to aviewin asorted form. This is because thereisnosorting criterioninthismodelfor anyof thecolumns.Itisalsopracticallyimpossible to filter outspecific entriesfromthe model. To address this shortcoming, Interviewhasprovided,startingfromQtversion 4.1, theQSortFilterProxyModel class, after itspredecessorQProxyModel proved to be toounwieldy.Itisbased on theQAbstractProxyModel base class, which represents so-called proxymodels.These lie somewhere between amodeland view,obtain- ingtheir datafromthe modeland returningittothe viewin modifiedform(see Figure 8.14 on page 237).The proxy modelthereforebecomes thesourcemodelfor theview.Onpage 237wewilllook in more detail at howproxy models function andimplement ourownproxy model. Forthe moment,wewilljustlook at what QSortFilterProxyModel can do: sort andfilter.

231 8 DisplayingDataUsing “Interview”

Duringfiltering, themodelreturnsthe modelindices for thoserowsinwhich the textinacolumn matchesthe search filter.Duringsorting,the roworderisarranged according to thevaluesinaspecified column, wherebyyou can sort in ascending or descending order. We willdemonstrate both capabilitiesofQSortFilterProxyModel withasmallex- ample, theFilteringView.Thisconsistsofatree view,abovewhich is alineeditthat accepts afilter term. Nexttothisweplace acombo boxcontaining allthe column names. Figure 8.12 showshowyou can usethistoselectthe column that is to act as thesearchcolumn.

Figure 8.12: QSortFilterProxyModel helps in sortingand filteringmodels.

8.5.1Adjustments to theUserInterface

SimplifyingSorting

Since ourviewwill useaQSortFilterProxyModel instance,which can alreadysort, weneed onlyto adjustthe viewer accordingly.The worknecessaryfor this is done bytheconstructor of theclass:

// addressbook/filteringview.cpp

#include #include "filteringview.h"

FilteringView::FilteringView(QAbstractItemModel * model, QWidget * parent) :QWidget(parent) { setWindowTitle(tr("FilterView")); proxyModel =newQSortFilterProxyModel(this); proxyModel->setSourceModel(model);

QVBoxLayout * lay=newQVBoxLayout(this); QHBoxLayout * hlay=newQHBoxLayout; QLineEdit * edit=newQLineEdit; QComboBox * comboBox=newQComboBox;

intmodelIndex=model->columnCount(QModelIndex()); for(inti=0; i

232 8.5 Sortingand FilteringDatawithProxyModels

comboBox->addItem(model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString());

hlay->addWidget(edit); hlay->addWidget(comboBox);

QTreeView * view=newQTreeView; view->setModel(proxyModel); view->setAlternatingRowColors(true);

// Make the header"clickable" view->header()->setClickable(true); // Sort Indicatorfestlegen view->header()->setSortIndicator(0,Qt::AscendingOrder); // Sort Indicatoranzeigen view->header()->setSortIndicatorShown(true); // Initialsortieren view->sortByColumn(0);

lay->addLayout(hlay); lay->addWidget(view);

connect(edit,SIGNAL(textChanged(const QString&)), proxyModel, SLOT(setFilterWildcard(const QString&)));

connect(comboBox,SIGNAL(activated(int)), SLOT(setFilterKeyColumn(int))); }

Firstwecreateaproxy modeland saveitinamember variable calledproxyModel. Then wecreatebothavertical andahorizontallayoutusedlater to enclosethe widgets:Wegroup thelineeditand thecombo boxtogether in onelinewiththe horizontallayout. With thehelpofthe vertical layout, wepositionthisabovethe viewto which wepassthe proxy modelasthe source. To make reading easier,everyotherlineinthe tree viewis displayed withasecond background color. We activatethisfeature withsetAlternatingRowColors(true). To obtain thewidgetcontainingthe column headers in tree views, weuse header(). So that it can react to clicks,weset setClickable(true).Inaddition weprovide it withasorting indicator,usuallythis is atrianglethatchartswhether datais shownsorted in ascendingordescendingorder.Inthiscasewesortcolumn 0in ascendingorder (Qt::AscendingOrder) anddisplaytheindicator via setSortIndica- torShown(true).Tomake sure that thelistisalreadysorted beforethe user clicks theheaderfor thefirsttime, weprearrangethe datasets sorted bythefirstcolumn, withsortByColumn(0).

233 8 DisplayingDataUsing “Interview”

Restricting theView to SpecificDatasets

In ordertorestrictthe datashowninthe viewto datasets matching afilter string specified in thelineeditwidget, twoconnect() instructions areneeded:The first oneinforms theproxy modelassoon as thetextchanges in thelineedit. Theproxy modelthenusesthistextasthe newfilter. Thereare threeslots in theproxy modeltowhich thetextChanged() signal can be linked. setFilterFixedString() deliversall rowswhere thesearchcolumn contains the specified filter string as asubstring,whereas setFilterWildcard()—which weuse in theexample—also accepts *asawildcardinthe filter string.For example, when setFilterWildcard()isused, thesearchtermHel*ldwould matchadataset in the modelwith“Hello world” in thesearchcolumn. Asearchfor Hel*ld usingsetFilter- FixedString() willreturn onlydatasets that contain theexact six-character string Hel*ld. setFilterRegExp()acceptsfilter stringsthatare regularexpressions. Using Hel*ld as asearchstring, it will return rowsofwhich thesearchcolumn contains oneofthe followingsubstrings: Held,Helld,Hellld,Helllld andsoon. Thesecondsignal/slot connectionisusedtoselectthe field which theproxy model should compareagainstthe filter string.For this purposethe proxy modelhas the setFilterKeyColumn() method, which expectsthe search column as theargument. Since this method is unfortunatelynotimplemented as aslot, wemustimplement ourownslotinthe view,which willcallthe function:

// addressbook/filteringview.cpp (continued)

void FilteringView::setFilterKeyColumn(intcol) { proxyModel->setFilterKeyColumn(col); }

Theslotisalsothe reason wecreated theproxyModel member variable:Werequire accesstothe proxy modeloutside theconstructor.

8.6Making EntriesSelectablewith Checkboxes

If it is intendedthatthe user should make aselection from alist, Interviewplaces aboxin frontofthe corresponding entriesthatcan be checkedvia theQItemDele- gateusedbydefault, a checkbox.Wemake useofthispropertyin anewsubclass of ouraddressbook model, theCheckableAddressbookModelsubclass. To implementselectable entries, weneed to reimplementonlythreemethods,apart from theconstructor:Inflags() weinformthe viewthat specificentries can be selected.Inorder for thedelegatetodrawthecheckbox,weneed to useanewrole in data()and setData()—theCheckStateRole.

234 8.6 Making EntriesSelectablewithCheckboxes

// addressbook/checkableaddressbookmodel.h class CheckableAddressbookModel :public AddressbookModel { Q_OBJECT public: CheckableAddressbookModel(const QString&addresses, QObject * parent=0); virtualQVariantdata(const QModelIndex&index, introle =Qt::DisplayRole )const; virtualbool setData(const QModelIndex&index, const QVariant&value, introle =Qt::EditRole); virtualQt::ItemFlagsflags(const QModelIndex&index) const; private: QList checkedStates; } ;

In theconstructor wefirstcallthe constructorofthe overclass, passing it thecom- plete dataset as astring(addresses),aswellasthe parentwidget. We then haveto findout howmanydatasets thepassedstringcontains. Equippedwiththisvalue, wecan keep track of theselection status of therespectivelineinthe checkedStates list. We can findout thenumber of datasets throughthe number of newline characters. Onlythosedatasets should be selectable that reallycontainaddressdata—so not thefirstlinewiththe headers:

// addressbook/checkableaddressbookmodel.cpp

CheckableAddressbookModel::CheckableAddressbookModel( const QString&addresses,QObject * parent) :AddressbookModel(addresses,parent) { // Contrary towhatwe’vedone in the AddressbookModel, // wedon’t add 1tothe indexhere // since the headers can’t bechecked bythe user introws =addresses.count(’\ n’); for(inti=0; i

In thereimplementationofflags() wefirstcatch invalid indicesagain. So that thereisnot acheckboxin frontofeverysingle column entry,onlytheentries in thefirstcolumn areselectable,standingfor thewhole line. Forthisreasonwe checkthe indexandallowtheadditional status onlyin column 0.Itisimportant here to consultthe base implementation,AddressbookModel::flags(index), because this,among otherthings, ensuresthatitiseditable.Provided that weare in the first column, weapplyabitwiselogical OR operation withQt::ItemIsUserCheckable to theexisting flags.Thisoperation combines theflags witheach other:

235 8 DisplayingDataUsing “Interview”

// addressbook/checkableaddressbookmodel.cpp (continued)

Qt::ItemFlagsCheckableAddressbookModel::flags (const QModelIndex&index) const { if (!index.isValid()) return0;

if (index.column() == 0) returnAddressbookModel::flags(index)|Qt::ItemIsUserCheckable; else returnAddressbookModel::flags(index); }

Then weimplement data(). If thecallerislocated in thefirstcolumn andqueries themodelwhile in theCheckStateRole,welook up thestatusofthe currentrow (index.row()) in thecheckedStates list. If thecheckboxis selected,the correspond- ingelement is true,which wesignalbyreturningQt::Checked; otherwise, if it is false,wereturn Qt::Unchecked. In allother cases weretrievethe return value from theoverclass:

// addressbook/checkableaddressbookmodel.cpp (continued)

QVariantCheckableAddressbookModel::data( const QModelIndex&index,introle)const { if (!index.isValid()) returnQVariant();

if (role == Qt::CheckStateRole && index.column() == 0) { if (checkedStates[index.row()]== true) returnQt::Checked; else returnQt::Unchecked; } returnAddressbookModel::data(index,role); }

AlthoughQItemDelegatenowdrawsacheckboxfor everyrow,the user can still not change itsstatus. This onlyworks if setData() hasbeen implemented accordingly:

// addressbook/checkableaddressbookmodel.cpp (continued)

bool CheckableAddressbookModel::setData(const QModelIndex&index, const QVariant&value, introle) { if (!index.isValid()) returnfalse;

if (role == Qt::CheckStateRole && index.column() == 0) { checkedStates[index.row()]=!checkedStates[index.row()]; emitdataChanged(index,index);

236 8.7 DesigningYourOwn ProxyModels

returntrue; }

returnAddressbookModel::setData(index,value, role); }

Here wealsocheck whether theroleiscorrect andwhether weare locatedinthe first column. If everythingisall right, wenegatethe status of thelistelement at therelevantposition. In orderfor theviewstodisplaythechanged value,we triggerthe dataChanged()signalfor theindexwhenweare finished, just as wedid in theoverclass. We forwardall othercalls to theoverclass, as wedid for theother twomethods. To tryoutthe modelwehavejustcompleted,wechangethe main() program so that it instantiates ournewmodelinsteadofthe AddressbookModeloverclass, and adjustthe #include compilerdirectiveaccordingly.The resultscan be seen in Figure 8.13.

Figure 8.13: CheckableAddress- bookModel insertsa checkbox foreach row.

8.7DesigningYourOwn ProxyModels

With QSortFilterProxyModel wehavealreadygotten to knowoneclass that inherits from QAbstractProxyModel.But proxy models can also displaytheoriginalmodels in verydifferent ways. To demonstratethiswewillwrite ourownproxy modelthat swaps thecolumns androwsofthe original modelinawaysimilartothe matrix transposeoperation in mathematics.

Figure 8.14: Views Proxymodels lie between theoriginal Proxy modeland theview. Data source Model (Directory, , XML, ...) model

Delegate

237 8 DisplayingDataUsing “Interview”

Firstwewillstart withthe constructor: Since weare notusing additionaldata structures of ourown, it remainsemptyandmerelyinitializes theoverclass:

// addressbook/transposeproxymodel.cpp

TransposeProxyModel::TransposeProxyModel(QObject * parent) :QAbstractProxyModel(parent) { }

Thetwomethods that followdefinehowthedatafromthe source modelare ar- ranged in theproxy model: mapFromSource()converts an indexfrom thesource modeltoanindexfor theproxy model, while mapToSource()converts an index from theproxy modeltoanindexfor thesourcemodel. In themapFromSource() implementation wefetch theindexusingthe method of thesamename, butpass column() as therownumber androw() as thecolumn number.mapToSource() works in exactlythesameway,but calls theindex() method of thesourcemodel, in casethe source modelindexhasbeen manipulated:

// addressbook/transposeproxymodel.cpp (continued)

QModelIndexTransposeProxyModel::mapFromSource( const QModelIndex&sourceIndex) const { returnindex(sourceIndex.column(),sourceIndex.row()); }

QModelIndexTransposeProxyModel::mapToSource( const QModelIndex&proxyIndex) const { returnsourceModel()->index(proxyIndex.column(),proxyIndex.row()); }

Butweare still notfinished,because QAbstractProxyModel inherits directlyfrom QAbstractItemModel.Thismeans that wemustalsoimplement allofits methods. Take index(), for example. Since weare planning anormal, two-dimensionalmodel, weuse thecreateIndex() function to generate theindex.Columns androwsmust notbeswapped here,since thethiswould undothe mappingstoand from the source:

// addressbook/transposeproxymodel.cpp (continued)

QModelIndexTransposeProxyModel::index(introw,intcolumn, const QModelIndex&parent) const { Q_UNUSED(parent); returncreateIndex(row,column); }

238 8.7 DesigningYourOwn ProxyModels

We also havetoimplement theparent()method. Butsince ourproxy modelonly supports two-dimensionalmodels,and thereforedoesnot supportparentrelations, wereturn an invalid indexhere:

// addressbook/transposeproxymodel.cpp (continued)

QModelIndexTransposeProxyModel::parent( const QModelIndex&index) const { Q_UNUSED(index); returnQModelIndex(); }

Nextwereimplement rowCount() andcolumnCount(). Theviewuses thesefunc- tionstodetermine which indicesitshouldquery.For ourpurposes,rowCount() should callthe source model’scolumnCount(), andviceversa.

// addressbook/transposeproxymodel.cpp (continued) intTransposeProxyModel::rowCount(const QModelIndex&parent) const { returnsourceModel()->columnCount(parent); } intTransposeProxyModel::columnCount(const QModelIndex&parent) const { returnsourceModel()->rowCount(parent); }

In addition,data()mustdeliver thecorrect data. We also fetchthisdirectlyfrom thesourcemodel. It is important here that you convertthe indexcorrectly,using thepreviouslycreated mapping methods.Since thepassedindexoriginates from theproxy model, weuse mapToSource():

// addressbook/transposeproxymodel.cpp (continued)

QVariantTransposeProxyModel::data(const QModelIndex&index, introle)const { if (!index.isValid()) returnQVariant(); returnsourceModel()->data(mapToSource(index),role); }

Even if notrequired(theheaderData()method is notcompletelyvirtual), it is rec- ommended that you swap thecolumn androwheaders. To do this wesimplypass theother value of theOrientation enumeratorineach case:

239 8 DisplayingDataUsing “Interview”

// addressbook/transposeproxymodel.cpp (continued)

QVariantTransposeProxyModel::headerData(intsection, Qt::Orientation orientation, introle)const { if (orientation == Qt::Horizontal) returnsourceModel()->headerData(section, Qt::Vertical, role); else returnsourceModel()->headerData(section, Qt::Horizontal, role); }

We can nowplacethismodelasaproxy modelbetween atable viewandour address book model, for example. To do this wefirstmodifythemain() program from theoriginaladdressbook exampleonpage 226byincludingthe headerfile transposeproxymodel.h,displayed below:

// addressbook/transposeproxymodel.h

#ifndef TRANSPOSEPROXYMODEL_H #define TRANSPOSEPROXYMODEL_H

#include

class TransposeProxyModel :public QAbstractProxyModel { Q_OBJECT public: TransposeProxyModel(QObject * parent=0);

virtualQModelIndexmapFromSource( const QModelIndex&sourceIndex) const; virtualQModelIndexmapToSource( const QModelIndex&proxyIndex) const;

virtualQModelIndexindex(int,int, const QModelIndex&parent=QModelIndex()) const; virtualQModelIndexparent(const QModelIndex&index) const; virtualintrowCount(const QModelIndex&parent) const; virtualintcolumnCount(const QModelIndex&parent) const; virtualQVariantdata(const QModelIndex&index, introle =Qt::DisplayRole)const;

virtualQVariantheaderData(intsection, Qt::Orientation orientation, introle =Qt::DisplayRole)const; } ; #endif // TRANSPOSEPROXYMODEL_H

240 8.8 ImplementingDragand Drop in Models

Figure 8.15: Ourproxy model transposes the original model.

Then weinstantiate theproxy,invokeits setSourceModel()method, passing this a pointer to theoriginalmodel, andmake theproxy modelbethe source for theview. Figure 8.15 showsthe result.

8.8ImplementingDragand Drop in Models

So far ourmodelisnot able to moveorcopyindividualrowsvia drag anddrop. In Section7.4 wegot to knowhowdrag anddropcan be implemented for any widgets you like, andInterviewalso offers thepossibilityof usingelementsfrom viewsasdragobjects. Butincontrasttothe previous examples,itis not necessary here to useinheritanceand adjustone of theviewclassesinthismanner. It is sufficienttomodifythemodel. To demonstratethiswewilluse asubclassofthe already-implemented Address- bookModelclass,calledDndAddressbookModel.6 To provideitwithdrag-and-drop capability,wemustnowoverwrite thefollowingmethods:

// addressbook/dndaddressbookmodel.h

#ifndef DNDADDRESSBOOKMODEL_H #define DNDADDRESSBOOKMODEL_H

#include "addressbookmodel.h" class DndAddressbookModel :public AddressbookModel { public: DndAddressbookModel(const QString&addresses,QObject * parent=0); virtualQt::ItemFlagsflags(const QModelIndex&index) const; QStringList mimeTypes() const; QMimeData * mimeData(const QModelIndexList &indexes)const; bool dropMimeData(const QMimeData * data,Qt::DropAction action, introw,intcolumn, const QModelIndex&parent);

6 Of course,wecould also inheritfromCheckableAddressbookModeland extend itsfunctionality, butthatwould be more complex,and is thereforelesssuitable fordidacticpurposes.

241 8 DisplayingDataUsing “Interview”

} ; #endif //ADDRESSBOOKMODEL_H

In ourexampleimplementation, if theusertouches anyelementfromarowwith themouse,the entire rowwill alwaysbecopied,sothatthe dataset remainsintact. Forthisreasonthe DndAddressbookModelisuseless for table views. (Although with abit more work, it is possible to copyonlyindividualelements, wewillnot go into this here,toavoidthingsbecomingtoo complicated.) In theconstructor wedonothing more than forwardthe argumentstothe over- class. Calls such as setDropEnabled()are notnecessaryhere:

// addressbook/dndaddressbookmodel.cpp

#include #include "dndaddressbookmodel.h"

DndAddressbookModel::DndAddressbookModel(const QString&addresses, QObject * parent) :AddressbookModel(addresses,parent) { }

Qt::ItemFlagsDndAddressbookModel::flags(const QModelIndex&index) const { Qt::ItemFlagsdefaultFlags=AddressbookModel::flags( index);

if (index.isValid()) returnQt::ItemIsDragEnabled |Qt::ItemIsDropEnabled |defaultFlags; else returnQt::ItemIsDropEnabled |defaultFlags; }

If wewanttoallowdrops,wemustsignalthisfor each modelindexin theflags() method. Whereasweonlyallowdrags from valid modelindices,dropping is also possible on invalid ones:Ifthe user releases adragafter thelastentryin alist, this positionisinvalid as amodelindex,althoughitcan be used to signifythat anew elementshouldbeappended to thelist. In thenextstep wedefine which MIME typescan be handledbyourmodel. Here we useour ownformatcalledapplication/x-osp.text.csv,which willsaveussomework on thenextpage whencopyingthe entriesbetween twomodel/viewinstances:

// addressbook/dndaddressbookmodel.cpp (continued)

QStringList DndAddressbookModel::mimeTypes() const { QStringList types; types<< "application/x-osp.text.csv";

242 8.8 ImplementingDragand Drop in Models

returntypes; }

ThemimeData()method comesintoplayif theuserpulls aselection awayfrom the view,thus initiating adrag. We aregiven alistwiththe modelindices involved. Themethod should packthemintoaQMimeDataobject,and theinstantiation of a QDrag object is takenover byInterview:

// addressbook/dndaddressbookmodel.cpp (continued)

QMimeData * DndAddressbookModel::mimeData( const QModelIndexList &indexes) const { QMimeData * mimeData=newQMimeData();

QListrows; foreach (QModelIndexindex,indexes) if (index.isValid()) if (!rows.contains(index.row())) rows +=index.row();

QByteArrayencodedData; QDataStreamstream(&encodedData,QIODevice::WriteOnly);

foreach(introw,rows) stream<

mimeData->setData("application/x-osp.text.csv",encodedData); returnmimeData; }

Since weare interested onlyin complete rows, weextract therespectiverownum- bers from themodelindices passedand savetheminalist. In thesecondstep wemustfind asuitable wayof storingour datasets in aQByteAr- ray.Herethe QDataStream classisofhelp, which wewillget to knowbetter in Chapter 11 on page 317. It can serializeall primitivedatatypesinQtvia the<< operator, includingQStringList objects. Thebyte arrayencodedDataisusedhereas an output medium, because although QDataStream is intendedfor output into files andfor real output devices,thankstoanoverloadedconstructor theclass can also write to QByteArrayobjectsorreadfromthem. Corresponding to thefile seman- tics,the second parameter QIODevice::WriteOnlyindicatesthatthe QDataStream instance stream mayonlywrite to thebyte array. Nowwegothrough thejust-created rowslistand accessthe corresponding entry of theaddressBook structure. To gettothe positionwereallywant, wemustagain accessone entrybeyondthatposition. Each entryfound in this wayis read via aQDataStream into thebyte arrayen- codedData. We passthe finishedbyte arrayto themimeDataobject.The factthat

243 8 DisplayingDataUsing “Interview”

thecontents no longer havetobepureASCII textafter transformation through QDataStream is anotherreasonwecannotuse text/plainasMIMEtypes, in addi- tion to theissueofdistinguishabilityduring thedropprocedure. Theother side of thedrag-and-drop procedureishandled bythedropMimeData() method. Apartfromthe MIME data,italsocontainsthe type of drop: Should the databecopied (CopyAction), moved (MoveAction), linked(LinkAction), or ignored (IgnoreAction)? Furthermore, weare given both therowandcolumn in which theuserreleased themouse,thus triggering thedrop. Viaparentwelearn whether thecurrent item is achild of anotheritem. Since this cannotbethe caseinour childless model, wecan ignore parentaswellascolumn, sinceweare onlydragging anddropping entire rowsatatime.The method returnstrueifthe drop procedureissuccessful, otherwisefalse:

// addressbook/dndaddressbookmodel.cpp (continued)

bool DndAddressbookModel::dropMimeData(const QMimeData * data, Qt::DropAction action, introw, intcolumn, const QModelIndex&parent) {

Q_UNUSED(column); Q_UNUSED(parent);

if (action == Qt::IgnoreAction) returntrue;

if (!data->hasFormat("application/x-osp.text.csv")) returnfalse;

// workaround forQt4.1.2 bug if (row== -1) row=rowCount();

QByteArrayencodedData=data->data("application/x-osp.text.csv"); QDataStreamstream(&encodedData,QIODevice::ReadOnly); QListlines; while (!stream.atEnd()) stream>>lines;

introws =lines.count(); insertRows(row,rows,QModelIndex()); foreach(QStringList line, lines) { addressBook.replace(row+1, line); row++; } returntrue; }

244 8.9 Your OwnDelegates

We react to allactions,but to be on thesafe side wecatch IgnoreAction.We shouldn’treallyacceptthisaction. If this happens,though, weannounceasuccess- fulcompletion of thedropoperation—after all, wesuccessfullyignoredthe drop. In addition wemustensurethatour drag contains theapplication/x-osp.text.csv MIME type,otherwiseweterminate withreturn false,since thedropactionwas notsuccessful. In several Qt versions, including4.1.2,the problemoccurs that rowreturnsthe value -1 if thedroptargetliesbeneath thelastentryin alistortreeview.For this reason wewillintercept this caseand return thenumber of columns in themodel so that thenewdataset(s) can be inserted after thelastrow,asintended. Nowwereadout theQByteArrayfor ourMIMEtypes. This is thedataweobtained bycombiningthe various string listentries.Wenowreverse that process byreading outthe lines listfromencodedData, string listbystring list, butthistimemarked as read-only.The useofthe atEnd()method demonstrates that wehavetreated thebyte array,through QDataStream,likeafile. Nowwecan calculate thenumber of rowstobeinserted withcount andadd them to themodelwithinsertRows(). rowprovides us withthe offset here.Finallywe replacethe emptystring lists created byinsertRows()withthe real contents.The drag operation is nowcompleted,which wewillannouncetothe callerofthe method withreturn true. To testour modifiedmodel, wereplace AddressbookModelwithDndAddressbook- Model in themain() function of theaddressprogram on page 226 andstart two instancesofthe application. Drag anddropisnowpossible between them,and also withinthe same view.

8.9YourOwn Delegates

Untilnowwehaveaccepted that viewsdisplaytheirentries themselves.Wewill nowrevealthe secret of the delegates,which areresponsible for thedisplayof individualelementsand for providing an editor for writable models.Each model hasexactlyonedelegate. Alldelegates inheritfromQAbstractItemDelegate, in themannerofFigure8.16. Bydefault, allviewsuse theQItemDelegateclass derived directlyfrom this,which provides astandardeditorand contains thecharacter logicfor theentries.Wewill gettoknowasimilarSQL-specificclass calledQSqlRelationalDelegateinChapter 9. Belowwewillwrite adelegatethatnot onlyprovides an editor likeQItemDelegate butalsohas tabcompletion andanoverviewof allexisting entries. Thecurrent column serves as thedatasource. In thecaseofour address book model, this can savethe user agreat deal of typing work, for example, for frequentlyoccurring first namesand familynames.

245 8 DisplayingDataUsing “Interview”

Figure 8.16: Theinheritance QAbstractItemDelegate pattern of delegates in Interview

QItemDelegate

QSqlRelationalDelegate

Firstweshall look at theconstructor:All this hastodoiscallthe constructorof theoverclass, because for this modelwedon’t need anymember variablesthatwe would havetoinitialize:

// addressbook/completiondelegate.cpp

#include #include "completiondelegate.h"

CompletionDelegate::CompletionDelegate(QObject * parent) :QItemDelegate(parent) { }

Theviewcalls thecreateEditor()method whenthe user launchesaneditorfor the ✞ ☎ first time from anyindexpositionatall, bydouble-clicking or pressing ✝ F2 ✆:

// addressbook/completiondelegate.cpp (continued)

QWidget * CompletionDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem&option, const QModelIndex&index)const { const QAbstractItemModel * model =index.model(); if (!model) returnQItemDelegate::createEditor(parent,option, index);

QComboBox * box=newQComboBox(parent); box->setEditable(true); box->setAutoCompletion(true); box->setModel(const_cast(model)); box->setModelColumn(index.column()); box->installEventFilter(const_cast(this)); returnbox; }

246 8.9 Your OwnDelegates

As theprocessing widgetwedisplayacombo box,which can be edited likealine edit,and in additionitshouldbeable to performtab completion.Weuse theparent pointer passedasthe fatherfor theconstructor;asaresult,the view,and notthe delegate, controls thewidget. To fillthe comboboxwithdata, it is sufficienttopassthe comboboxthecurrent model, because QComboBox,althoughnot an officialviewclass, can handle the QAbstractItemModel-based models as asource. Just as withQListView,herewe mustalsospecifythecolumn from themodelfor which theselection boxshould look,using setModelColumn(). Theconst_castisnecessaryhere because weare in aconst method andour model pointer is aconst pointer.Thismeans that wemustpreventanywrite operations ✞ ☎ ✞ ☎ to themodel. In addition wemustensurethatcertain keys, such as ✝ Enter ✆ or ✝ Esc ✆, closethe editor andsignaltothe delegatethatitshouldwrite thedataback to the model. This is donebytheeventfilter that weinstall on thecombo box.Itdiverts all thekeystrokestothe delegate. Nowwewould havetooverwrite theeventFilter() method to intercept thekeystrokes. In practice,however,QComboBoxhassuchan eventfilter in theprivate(that is,internallyhidden) QComboBoxPrivateContainer class,7 which is undocumented,however. This meansthatintheory,the filter maydisappear in each newQt release. If you wanttobecompletelysure,you should write yourowneventfilter,based on the implementation of this Qt class.

Figure 8.17: Our CompletionDelegate completesthe entry on thebasis of other entriesinthe same column of thesource model. Theeditorisnowavailable,but it is possible that theusermaywanttouse it again later on at thesameindexposition. To supplyit withcurrent dataeverytime it is used,the setEditorData()method exists.Our comboboxserves as an editor widget, which is whyweconvertthe object via qobject_cast:

// addressbook/completiondelegate.cpp (continued) void CompletionDelegate::setEditorData(QWidget * editor, const QModelIndex&index)const { QComboBox * box=qobject_cast(editor); 7 Seeqcombobox_p.h/qcombobox.cpp in theQtsourcecode.

247 8 DisplayingDataUsing “Interview”

const QAbstractItemModel * model =index.model();

if (!box|| !model) QItemDelegate::setEditorData(editor,index); box->setCurrentIndex(index.row()); }

qobject_castfunctionslikeadynamic_castbut doesnot requireanyRTTI support,8 which manypeopleliketodisable for reasonsofspace, particularlyon embedded platforms.Inaddition it works beyondthe bordersofdynamiclibraries—normally dynamic_castwillnot workhere. Theonlyconsolation: It can onlybe used for QObject-based classes. Exactlylikeadynamic_cast, qobject_castalsoreturnsazeropointer if theconver- sion fails.Althoughthe conversion should never fail, wewill intercept this scenario andbusyourselves insteadwiththe base implementation.Ifeverythinggoesac- cordingtoplan, wepassthe rowcoordinates of ourcurrent positioninthe model to thecombo box.Itusesthisinformation at this point as thestandardtext. Ourfi- naltasknowconsists of implementing setModelData(). After afewsecuritychecks, wesimplysetthe currentcontents of thecombo boxas thenewvalue for the currentmodelindex.Ifyou wanttobereallysure,you should do this both for the DisplayRole andfor theEditRole:

// addressbook/completiondelegate.cpp (continued)

void CompletionDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex&index) const { if (!index.isValid()) return;

QComboBox * box=qobject_cast(editor); if (!box) returnQItemDelegate::setModelData(editor,model, index);

model->setData(index,box->currentText(),Qt::DisplayRole); model->setData(index,box->currentText(),Qt::EditRole); }

We can nowinsert ourowndelegates into whatever viewswewant(into ourad- dressbook as well, or course)using setItemDelegate(). What this lookslikeisshown in Figure 8.17.

8 RTTI standsfor RuntimeTypeInformation.Itallowsthe type of amethodtobedeterminedin C++. Since RTTI supportheavilyinflatesthe sizeofthe object filecreated,and mocobtains the corresponding informationanywayat compile time,you canmanagewithout RTTI supportin Qt programs.

248 8.10 WithoutYourOwn Data Source:The StandardModel

8.10 WithoutYourOwn DataSource: TheStandard Model

Formanypurposes,creatingyourownmodelwould seem to be excessive, as well as quiteinconvenient.Ifyou just wanttodisplayafewnumbers, for example, that do notchangeorchangeverylittle, QStandardItemModelisthe rightchoice. This classhas theadvantage that it manageswithout inheritanceinmostcases,sowe can useitdirectly. Forasmallexamplewewillagainuse ouraddressbook model, which wenow implementwiththe help of QStandardItemModel. To demonstratethatthe model manageswithout inheritance, willcarryoutall tasksinthe main() function. Firstweagainrequire thesplitCSVLine()helpfunction from page 222, which con- verts arowfrom theCSV fileintoastring list:

// stdmodeladdressbook/main.cpp

#include

QStringList splitCSVLine(const QString&line) { ... }

Thefirstpartofthe main() function also appears to be as before: We openthe file, read thecomplete contents into astring, chop off thelineends, andstore the result in astringlist. We removethe first lineofthis, withtakeAt()—in contrast to removeAt(), takeAt() returnsthe removed string directly:

// stdmodeladdressbook/main.cpp (continued) intmain(intargc, char * argv[]) { QApplication app(argc, argv);

// Open file QFile file("addressbook.csv"); if (!file.open(QIODevice::ReadOnly|QIODevice::Text) ) return1; // Readaddressesline forline intoastringlist QString addresses=QString::fromUtf8(file.readAll()); QStringList records=addresses.split(’\ n’);

// Take the first rowwiththe headers and splitthem QString header=records.takeAt(0); QStringList headers =splitCSVLine(header);

249 8 DisplayingDataUsing “Interview”

// Createamodel using the numberof rows and columnsasarguments QStandardItemModel model(records.count(),headers.count());

// Add headers for(intcol=0;col

// Add contents for(intrecNo=0;recNo

// Create, setand showmodel QTreeViewtreeView; treeView.setWindowTitle(QObject::tr("Addressbook viaQStandardItemModel")); treeView.setModel(&model); treeView.show();

returnapp.exec(); }

Afterwehavesplit theheaders, withsplitCSVLine(), it is time to instantiateQS- tandardItemModelwiththe number of rowsand columns as arguments. At this point wehavecollected enough datatoknowthemaximum number of rowsand columns,but westill havetoinsertthe dataintothe model. This is thenextstep. Firstthe headers areincludedinthe model, via setHeaderData(), followed bythe actualdata. Foreach elementofthe modelwemustcreateaseparatemodelindex. Because weknowboth thecurrent rowandalsothe currentcolumn, this is nota problem. Theonlydisadvantage:Wehavetomanage without themoreconvenient foreach() loop. In thefinalsegment of code,weinstantiate atreeview,set themodeland display theview.Lastofall, westart theeventloop. This exampledemonstrates that Interviewcan also be used via QStandardItem- Model without theneed for time-intensivereimplementationofmodels.Indoing this you saveall thedatainthe model, however.For theuser, theresultisidentical. Butthe proceduredoeshavesomedisadvantages: With reimplemented models,we can usecomplexdatastructuresinthe background,whereas in this casewemust getbywithduplicated datainthe model, onlyaddressable via QModelIndex. Writable models thus become verycomplex,sothatinthiscaseyou should always choosereimplemented models.Ifthisstill seemstoo time consuming,you should take alook at theelement-based views.

250 8.11 Element-based Views WithoutModel Access

8.11 Element-based Views WithoutModelAccess

Qt 3programmers areusedtoeveryelementinaviewbeingrepresented bya separateobject.Althoughthisconcept is no longer up to dateinQt4,there is still ause for it in some areas, butusuallyit serves as aporting aidfor Qt 3–based applications that areconverted onlyat alater stage to model/viewprogramming. ForthisreasonTrolltechhas developedanelement-based classfor each of the Interviewviewsthatmanageswithout an externalmodel. To distinguishthese from normal views, wewillcallthem view widgets from nowon.Wewilldiscuss them onlybrieflyhere,because for most purposes astandardmodelisjustassuitable. Internally,viewwidgets arebased on therespectiveviewclasses, buttheypro- videanextendedAPI. Because this makesthemcompletelyautonomous,and they requirenofurther classes, theyarecalledwidgets. In this waytheQListViewlistviewbecomesthe QListWidget, thecounterpart to theQTreeViewlistviewis QTreeWidget, andthe table viewQTableViewis called QTableWidgetinthe independentversion.

8.11.1Items

Each entryin theseviewwidgets is an instance of an element or item.For each of thethree widgets thereare separateitem classesthatdonot haveacommon parentclass.Thisalsomeans that thedatainaviewwidgetineach casecannotbe used in theother twowidgettypeswithout additionalprocessing. In orderthatthe item classesremainlightweight, theydo notinheritfromQObject, and, if theyarenot controlledbyaviewwidget, theyarethereforenot part of the automaticQtmemorymanagement. Theitem classesare each namedaccordingtothe viewwidgettowhich theybe- long:QListWidgetItem is used as an entryin alistview,QTreeWidgetItem represents an entryin atreeview,and QTableWidgetItem is responsible for displayingentries in atable. Each item hascertain propertiesthatcan be selectivelymodifiedvia itsAPI. These can be compared withroles,which weusedtomake distinctions in thedata() method of QAbstractItemModel (see Table 8.1).The setFont()method allowsan- otherfonttobeused, so it corresponds to theQt::FontRole,whereas setToolTip() corresponds to theQt::ToolTipRole. Theitems arebasicallypointer-based so they mustalwaysbecreated on theheap.

8.11.2The List View

Belowwewillinsertafewnamesintoalistwidget. We create theviewwidgetas beforewhendoing this,but wecreatethe entriesvia QListWidgetItem. Bypassing

251 8 DisplayingDataUsing “Interview”

this to theviewwidgetasthe second argument,ittakesover controlofthe item andinserts it.For this reason wedonot need to intercept thepointer returned by new:

// listwidgetexample/main.cpp

#include

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QListWidgetlistWidget; newQListWidgetItem(QObject::tr("Antje"),&listWidget); newQListWidgetItem(QObject::tr("Barbara"),&listWidget); newQListWidgetItem(QObject::tr("Daniel"),&listWidget); listWidget.show(); returnapp.exec(); }

Thereare twowaysofinstantiating aQListWidgetItem. Youcan callthe item con- structor,which expectsastring or an icon,followed byastringasaparameter.A suitable viewwidgetcan be passedoptionallyas thethird parameter,intowhich theitem is inserted.The exampleusesanalternativeconstructor that gets bywith- outspecifyinganicon. Amoredirectwayis to usethe addItem() method, which everyviewwidgetpos- sesses. It expectsastring,generates theitem automatically,and insertsthe item into theviewwidget.

8.11.3The Tree View

If you wanttohaveatree structureasaviewwidget, theQTreeWidgetisusedasa base class, in which thenumber of columns is fixed from thebeginning.Wespecify this withsetColumnCount(). Then wedefine theheader, withsetHeaderLabels(). We nowinsert thefirstitem into thewidget, as before, butwesavethe pointer.In this waywecan insert threechild entrieswiththe addChild() method of theitem:

// treewidgetexample/main.cpp

#include

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QTreeWidgettreeWidget; treeWidget.setColumnCount(1); QStringList headerLabels; headerLabels<< "Namen";

252 8.11 Element-based Views WithoutModel Access

treeWidget.setHeaderLabels(headerLabels); QTreeWidgetItem * parent= newQTreeWidgetItem(&treeWidget, QStringList(QObject::tr("Otto+Margit"))); parent->addChild(newQTreeWidgetItem (QStringList(QObject::tr("Daniel")))); parent->addChild(newQTreeWidgetItem (QStringList(QObject::tr("Moritz")))); parent->addChild(newQTreeWidgetItem (QStringList(QObject::tr("Philipp")))); treeWidget.expandItem(parent); treeWidget.show(); returnapp.exec(); }

Beforewedisplaythewidget, wefirstexpandthe parentitem Otto+Margitby callingthe expandItem() slot.Otherwisethe user would havetodothiswiththe + icon in frontofthe item.Nowtheviewappears as showninFigure8.18.

Figure 8.18: Asimpletreeviewis quicklyimplemented with QTreeWidget .

Since tree viewsmayhavemorethanone column, most setmethods expect the column as thefirstargument from QTreeWidgetItem. Thefollowinginstruction fills thesecondcolumn of an item calleditem with(new)text:

item->setText(1, tr("Text"));

In thesameway,setIcon() insertsaniconand setFont()determinesthe fonttype of thetext. An exception is setFlags(), withwhich thepropertieslisted in Table 8.2 on page 228 can be set. Theyrefertothe entire row.Itisthereforenot possible to provideindividualcolumns withcheckboxes:setFlags(Qt::ItemIsUserCheckable) sets theboxin thefirstcolumn.

8.11.4The TableView

Thethird andlastready-to-useviewwidget, QTableWidget, is basedonQTableView andusesQTableWidgetItem as an item class. Thesizeofthe table can be con-

253 8 DisplayingDataUsing “Interview”

venientlyspecified in theconstructor.Items areinserted here withthe setItem() method, which expectsacolumn andrownumber andthe item itself as argu- ments. Thefollowingexamplecreates a3x3table,inwhich each cell contains theproduct of itscolumn androwcoordinates:

// tablewidgetexample/main.cpp

#include

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QTableWidgettableWidget(3,3); for(introw=0;row

8.11.5Cloning Items

Often,you wanttohaveitems that areidentical up to acertain point:the same fonttype,the same icons, andsoon, withonlythetextdifferent each time.In such cases theclone() method, containedinall item classes, is veryuseful.Itallows an item to be puttogether into aprototype,fromwhich newitemscan then be cloned.Thenall you need to do is givethe cloneseparatetextand insert it into the viewwidget:

// listwidgetexample2/main.cpp

#include

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QListWidgetlistWidget;

// Setup aprototype QListWidgetItem * proto=newQListWidgetItem; proto->setFont(QFont("Times")); proto->setTextColor(Qt::blue); proto->setBackgroundColor(Qt::yellow);

// Clone and modifyobject,insert it

254 8.11 Element-based Views WithoutModel Access

// beforeeverything else QListWidgetItem * name =proto->clone(); name->setText("Antje"); listWidget.insertItem(0,name);

// Same procedure... name =proto->clone(); name->setText("Daniel"); listWidget.insertItem(0,name);

// Useprotoitself name =proto; name->setText("Barbara"); listWidget.insertItem(0,name);

// Sort the list listWidget.sortItems();

listWidget.show(); returnapp.exec(); }

ThesortItems() method sortsthe alreadyinserted itemsindescendingorder,sothat thenames appearbeloweach other, sorted in alphabetical orderinthe listwidget.

255

r te ap 9 Ch

TheQtSql Module

Nowadays, it is difficulttoimagine manyapplications beingable to function with- outrelationaldatabases to back them up.For this reason Qt provides arange of classesinthe QtSql module that workwithvarious relational databasemanage- ment systems(DBMS). Relational tablesand queriescan also be used as thebasis of Interviewmodels.

9.1Structure of theQtSql Module

TheQtSql module is an independentlibrarythat can load additionalplugins if re- quired.IncontrasttoQtCoreand QtGui,its contents arenot integrated bydefault (withqmake -project)intothe generated projects.Inorder to usethe library,the followingentryis thereforenecessaryin the.profile:

QT +=sql

257 9 TheQtSql Module

To be able to workwiththe classesofthe module,Qtprovides ameta-include for this packageaswell, which contains allthe classdefinitionsfromthe module.The command to integrate it into asourcefile is as follows:

#include

Each of theclassesofthe module belong to oneofthree layers. The driver layer im- plements theinterfacebetween thedriversfor various andthe API layer (see Table 9.1).Thisprovides applicationdevelopers withaccess to thedatabases andenablestypical SQLoperations, such as browsing or modifyingtablesorquery- ingdata. In ordertoinclude theresults of queriesinInterviewviews, the user interface layer provides models that arebased on SQLtablesorqueries.Figure9.1 provides an overviewof thelayersand theclassesbelonging to them.

Figure 9.1: Thestructure of the User interface level QSqlQueryModel, QSqlTableModel, QtSqlmodule QSqlRelationalTableModel

SQL API level

QSqlDatabase, QSqlQuery, QSqlError, QSqlField, QSqlIndex, QSqlRecord

Driver level QSqlDriver, QSqlDriverCreator, QSqlDriverCreatorBase, QSqlDriverPlugin, QSqlResult

9.2Selectingthe Appropriate Driver

Since thelicense of theclient APIfor some databasesystemsisnot GPL-compatible, anumber of driversare missing (marked in Table 9.1with *))inthe opensource edition.

Table9.1: DrivernameDatabasesystem Driversfor QtSql QDB2IBM DB2(Version7.1 andnewer) *) QIBASE BorlandInterBase QMYSQL MySQL QOCIOracleCallInterfacedriver (versions8,9,and 10) *) QODBCOpenDatabaseConnectivity(ODBC),usedbyMicrosoft SQL server andother ODBC-capable databases

258 9.2 Selectingthe Appropriate Driver

continued DrivernameDatabasesystem QPSQLPostgreSQL(version 7.3and newer) QSQLITE2 SQLite (version 2) QSQLITESQLite(version 3) QTDS Sybase AdaptiveServer*)

If theQtversion originates from packages of aLinuxdistribution,you mayneed to installadditional packages.Ubuntustoresthe SQLlibraryin thepackage libqt4- sql, whereas OpenSUSE,inaddition to installingqt-sql, requires aDBMS-specific databasepackage,suchasqt-sql-mysqlfor MySQL. If you build Qt from thesources,you should take alook at theoutputof./configure --help:

... -Istring ...... Add anexplicitinclude path. ... -qt-sql-...... Enable aSQL inthe QtLibrary,by defaultnone areturned on. -plugin-sql-.. Enable SQL asaplugin tobelinked toatruntime. -no-sql-...... Disable SQL entirely.

Possible valuesfor: [db2ibasemysqloci odbcpsqlsqlite sqlite2tds]

Auto-Detected on thissystem: [] ...

BydefaultQtbuildsthe driver modulesas plugins for allsystemsfound automati- cally—in this casefor SQLite. If you do notwanttocompile oneofthese explicitly, the-no-sql-driver switch is used;for example, in thecaseofSQLitethe switch would be -no-sql-sqlite. Qt also includes itsownSQLiteversion.Ifyou wantto useaversion of SQLite installedonthe system instead, you mustalsospecifythe -system-sqliteswitch. If ./configure cannotfind an installeddatabasesystem,despite thedevelopment packages installed, then you can specifytheinclude directoryof thedatabasesys- tem withthe -I switch,for example-I/usr/include/mysql, in thecaseofMySQL. It is left to each user to decide whether adriver is built separatelyas aplugin(-plugin- sql-driver)orcompiledpermanentlyinto thelibrary(-qt-sql-driver). Pluginsare more flexible,whereas compiled-in driversare simplertohandleifthe Qt libraryis to be included in theprogram.

259 9 TheQtSql Module

9.3Making aConnection

TheQSqlDatabaseclass is used to manage contact withthe databaseserver,and its addDatabase() static method returnsaninstanceofQSqlDatabase:

QSqlDatabasedb=QSqlDatabase::addDatabase("QPSQL");

As an argument,addDatabase()expectsatleast thenameofthe databasedriver in string form, thus somethinglike”QPSQL” for thePostgresdriver.AQSqlDatabase instance generated in this mannerserves as thestandardconnection. If thepro- gram needstoestablishcontact withmorethanone database, theaddDatabase() method additionallyrequires aconnectionname:

QSqlDatabasewebdb= QSqlDatabase::addDatabase("QMYSQL","WebServerDB"); QSqlDatabasepersonaldb= QSqlDatabase::addDatabase("QOCI","PersonalDB"); QSqlDatabaseembeddeddb= QSqlDatabase::addDatabase("QSQLITE","EmbeddedDB");

If this argument hadbeen omitted in thevariable definitions above, allthree QSql- Database instanceswould enduppointingtothe SQLite database, sinceeach ad- dDatabase()callwithout additionalparametersmodifiesthe standardconnection. In thefollowingexampleweset up aconnectiontoasingleMySQLserver.We establishaconnectiontoadatabaseonthisserver usingaQSqlDatabaseobject initialized withthe relevantdriver.Todothiswedeclare theserver name,the name of thedatabase, theusername, andthe password:

// sqlexample/main.cpp

#include #include #include

intmain(intargc, char * argv[]) { QApplication app(argc, argv);

QSqlDatabasedb=QSqlDatabase::addDatabase("QMYSQL"); db.setHostName("datenbankserver.example.com"); db.setDatabaseName("firma"); db.setUserName("user"); db.setPassword("pass");

if (!db.open()) { qDebug() << db.lastError(); return1; }

260 9.4 Making Queries

Theopen() method establishesthe connectiontothe databasewiththisaccess data. Whether theattempttoconnect was successful or notisindicated byits Boolean return value.Incaseoferror,wecan determine thereasonfor thecon- nectionfailure byusinglastError(). Themethod returnsanobject of type QSqlError, which qDebug() can read out. If you wanttoreuse this errorobject elsewhere,the QSqlError classmethod text()can be used.

9.4Making Queries

In thefollowingexamples wewillworkwithtwotables: Theemployees table holds information on theemployees in acompany(Table 9.2),and thedepartments table (Table 9.3) describesthe various organizationalunits in thecompany.

id last name first name department Table9.2: 1WernerMax1 The employees table from theexample 2Lehmann Daniel 2 database 3Roetzel David 1 4ScherfgenDavid 2 5ScheidweilerNajda 2 6Jueppner Daniela4 7Hasse Peter 4 8SiebigterothJennifer3

id name Table9.3: 1Management The departments table from theexample 2Development database 3Marketing 4Accounting

Fordatabaseoperationsweuse theQSqlQueryclass. If aclass used in thecon- structor is given an SQLcommand as astring, theinstanced object immediately carries outthisstatement. Youcan re-run thecommand stored in thequeryobject later on usingexec() (for example, after modification to thedatabase).Ifthere are several open connections,the QSqlQueryclassacceptsaQSqlDatabase instance as asecondparameter. If theSQL operation was successful,the QSqlQueryobject is regardedasactive, which can be checkedwithisActive(). If it hascollected datasets,for example

261 9 TheQtSql Module

throughaSELECT query,you can navigatethrough them: first() jumpstothe first dataset, last() to thelastone,next()tothe nextone,and previous() to theprevious one. With seek() you can addressaspecificdataset byspecifyinganinteger index. Thenumber of datasets containedinthe queryobject is revealedwithsize(). TheQSqlQuery::record()method returnsaQSqlRecord object.Itcontainsinfor- mation on theresponse to aSELECTquery.Using it wecan learn, for example, the numerical indexof aspecifiedcolumn in thequeryresult via QSqlRecord::indexOf(). We can usethisindexto read thevalue in that column of adataset (row)inthe result withQSqlQuery::value(). Therowis determinedbythecurrent positionin thequeryobject,which wecan retrieveusing QSqlQuery::at() andchangeusing QSqlQuery::next().

// sqlexample/main.cpp (continued)

QSqlQuery query("SELECT firstname, lastname FROM employees"); QSqlRecordrecord=query.record(); while (query.next()) { QString firstname = query.value(record.indexOf("firstname")).toString(); QString lastname = query.value(record.indexOf("lastname")).toString(); qDebug() << query.at() << ":"<< lastname << ","<< firstname; }

Foroperationsthatchangethe contents of thedatabase(such as UPDATE or DELETE), numRowsAffected() returnsthe number of datasets involved:

// sqlexample/main.cpp (continued)

query.exec("DELETE FROM employeesWHERE lastname =’Hasse’"); qDebug() << query.numRowsAffected(); // "1"

Things arealittlemorecomplicated for INSERT instructions.Since theseare used to write valuesfromthe program’s owndatastructurestothe database, it can be quite complicated to constructastring containing thecorresponding SQLinstruction. Forthisreasonwetake adifferent path: Using prepare()wesaveatemplate for the desiredcommand,equippedwithplaceholders, in theQSqlQueryobject:

// sqlexample/main.cpp (continued)

query.prepare("INSERT INTO employees(lastname, firstname, department)" "VALUES(:lastname, :firstname, :department)"); query.bindValue(":lastname","Hasse"); query.bindValue(":firstname","Peter"); query.bindValue(":department",3); query.exec();

262 9.4 Making Queries

The namedwildcards in theVALUESpartofthe SQLcommand,originating from theOracleworld,each beginwithacolon. Using thebindValue() command wecan replacethemwiththe specificvalues. QSqlQuerycan also handle the unknown parameters familiarfromthe ODBCusing addBindValue().Each calltothismethod replaces oneofthe question marksinthe VALUESclause, in theorder in which theyappear:

// sqlexample/main.cpp (continued)

query.prepare("INSERT INTO employees(lastname, firstname, department)" "VALUES(?,?,?)"); query.addBindValue("Schwan"); query.addBindValue("Waldemar"); query.addBindValue(3); query.exec();

If you don’t wanttospecifytheunknownvaluesaccordingtothe sequence of occurrence,you can usethe followingoverloadedvariant:

query.bindValue(2,3); query.bindValue(0,"Schwan"); query.bindValue(1, "Waldemar");

Here thefirstparameter specifies thepositionofthe question mark to be replaced in theprepare()string. bindValue() also playsacentralroleinthe useof stored procedures,because the parametersofthese procedures can be declared both as IN andasOUT. Parameters declared as cmdOUT function as return values. In ordertoaccess areturn value,wemustadjustthe bindValue() method: The value passeddoesnot matter here,asitwillbeoverwritten bytheOUT value later. Butthe QSql::Out specification,which tells QSqlQueryto overwrite thevalue,is important here.After wehaveexecuted exec(), thevalue liesatthe corresponding position. We can checkthiswithboundValue():

// sqlexample/main.cpp (continued)

query.prepare("CALL countEmployees(?)"); query.bindValue(0,0,QSql::Out); query.exec(); qDebug() << query.boundValue(0).toInt()

Unfortunately,thisapproachdoesnot workcorrectlyin MySQL5,due to APIlim- itations.Inorder to accessthe OUTvaluesunder MySQL5,wemustmake two queriesmanually:First werun thestoredprocedure withCALL, andthenweread in thevalue produced, usingSELECT. In ordertorefer to thevalue,ineach case

263 9 TheQtSql Module

weuse awildcardwith@as aMySQL-specificprefix,sothatwecan read outthe return value of thestoredprocedure as adataset:

// sqlexample/main.cpp(continued)

query.exec("CALL countEmployees(@outwert)"); query.exec("SELECT @outwert"); query.next(); qDebug() << query.value(0); return0; }

9.5Transactions

Notall databasesystemssupporttransactions,which combineseveral SQLopera- tionsintoanatomicoperation.Tohelpthe Qt programmerkeep thecode portable, QSqlDriver can thereforebeasked aboutits transactioncapabilitieswithhasFea- ture():

if (db.driver()->hasFeature(QSqlDriver::Transactions)) ... ;

If thedriver supports transactions,you can introduce them withthe QSqlDatabase method transaction(). If alloperationsare completed,the transactionisclosedwith commit(). If an erroroccurs, rollback()undoesall theoperationsofthe current transaction.

9.6EmbeddedDatabases

Qt’sSQLitedriver enablesdatatobestoredinarelational databaseand queried, without an externaldatabaseserver.There arerestrictions, of course,but thede- mandsmade of embeddeddatabases areusuallyless severethanthose for databases residing on dedicatedservers, andSQLiteisintendedfor just such situations.This meansthatSQLitecannothandlestoredproceduresand doesnot scaleaswellas itsbig brothers. It is wellsuited,however,toapplications that need abasic rela- tional datastore.Aperfectexampleisthe KDE music player , which stores metadataabout pieces of music in aSQLitedatabase. To openaconnectiontoaSQLite database, you onlyneed to specifyadatabase name.The SQLite driver expectsafilenameinthiscase:

QSqlDatabasedb=QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName("firma.db");

264 9.7 UsingSQL ModelClasseswithInterview

If thedatabaseshouldonlyremain in memorywhile theprogram is running,atem- porarydatabasecan be generated byenclosingthe databasenamewithincolons, as shownbelow.The :results:databasewillnot notbesaved as afile whenthe program terminates:

QSqlDatabasedb=QSqlDatabase::addDatabase("QSQLITE"); db.setDatabaseName(":results:");

Youcan workwiththisdatabaseasnormal, withthe understanding that any changesmade to it will later be lost.Atemporarydatabasedoesnot need its owndatastructuresifthe dataisalreadyof arelationalnature.

9.7Using SQLModelClasses withInterview

In ordertodisplaythecontents of databases,table viewsare usuallyappropriate, andinsomecases,listviewsare as well. This is whytheQtSql module hasarange of models for Interview(see Chapter 8onpage 207).

9.7.1DisplayingSQL Tables WithoutForeign Keys in Table andTreeViews

QSqlTableModel enablescomplete tablestobedisplayed directlyin atable or tree view.The column headers correspondtothe field names(attributes,columns)of theSQL table.Inour personneldatabasefromTable 9.2onpage 261, theseare id,firstname, last name,and department.Each linecorresponds to adataset. To illustrate this better,wewilllook at thefollowingexample, which assumesanopen standardconnection:

// sqlmvd/main.cpp

... QTableViewtableView; QSqlTableModel tableModel; tableModel.setTable("employees"); tableModel.select(); tableModel.removeColumn(0); tableView.setModel(&tableModel); tableView.setWindowTitle("’employees’ table"); tableView.show();

Firstwecreateatable viewandthenthe model. We allocate atable to it from the currentdatabaseand orderittofetch datawithselect(). Then weremovethe id column from theviewusingremoveColumn() (Figure9.2). This method originates

265 9 TheQtSql Module

from QAbstractItemModel,the ultimate base classofall models.Finally,webind themodeltothe table view,givethe table aname, anddisplayit.

Figure 9.2: QTableModel is responsible forSQL tables in Interview.

9.7.2Resolving ForeignKey Relations

QSqlRelationalTableModel extends thefunctionalityof thetable modelfor usewith relational databases.Inaddition,objectsinthisclass setoff foreign keyrelations. We can usethese to replacethe uninformativenumber showninthe department field withthe name of thedepartment, bymaking useofthe departmentstable (see Table 9.3onpage 261).

Figure 9.3: QSqlRelationalTa- bleModel recordsthe foreignkey field id with thehelpofa second table.

To describe this relation,the setRelation() method is used:Itexpectsthe number of thecolumn containing theforeign keyas thefirstargument.Inour exam- ple, thevalue in thenamefieldfromthe departmentstable should appearinthe thirdcolumn insteadofthe value in theforeign keyfield (thatis, theidfield) of thedepartments table.Thisinformation is encapsulated in theinstancerel of the QSqlRelation help class, which wepasstosetRelation() as thesecondargument.

266 9.7 UsingSQL ModelClasseswithInterview

Nowwecan startthe queryvia select(), bindthe modeltothe view,and displaythe results, as in theprevious example:

// sqlmvd/main.cpp(continued)

QTableViewtableRelationalView; QSqlRelationalTableModel tableRelationalModel; tableRelationalModel.setTable("employees"); QSqlRelation rel("departments","id","name"); tableRelationalModel.setRelation(3,rel); tableRelationalModel.select(); tableRelationalView.setModel(&tableRelationalModel); tableRelationalView.setItemDelegate( newQSqlRelationalDelegate(&tableRelationalView)); tableRelationalView.setWindowTitle( "Tableswithresolved relations"); tableRelationalView.show();

This is nowfollowed byapeculiaritythat onlyfunctionsincombinationwithQRe- lationalTableModel:AspecialdelegatecalledQSqlRelationalDelegateallowsthe user to select thevalue from alistwhenediting columns for which arelationis defined (Figure9.3). It compilesthese independentlyfrom theQSqlRelationused. In theexampleittakessuggestions from thenamecolumn; thevalue written back to thetable,onthe otherhand, comes from theidcolumn.

9.7.3DisplayingQuery Results

To displaytheresults of particularlycomplexSELECT queriesthatcannotsimply be modeledonaQSqlTableModel withafilter,make useofthe QSqlQueryModel. Thefollowingexampleevaluates howmanyemployees thecompanyhasineach department. In addition thecolumns should bear descriptivenames,ascan be seen in Figure 9.4.

Figure 9.4: QSqlQueryModel is used as asourcefor queriesofall typesin Interview.

267 9 TheQtSql Module

Afterinstantiating themodel, wepassthe queryas astringtosetQuery(). Alterna- tively,wecould useaQSqlQueryobject. Since errors can occurinmorecomplexqueries, weshouldintroduce an errorcheck immediatelyafter thequeryexecutes.lastError() returnsthe last errorannounced bytheSQL server in an QSqlError object.Ifthisisvalid,anerror hasoccurred,which wecan displaywithqDebug():

// sqlmvd/main.cpp (continued)

QTableViewqueryView; QSqlQueryModel queryModel; queryModel.setQuery("SELECT departments.name, " "COALESCE(COUNT(employees.lastname),0) " "FROM departments LEFT JOIN employees" "ON employees.department=departments.id " "GROUP BY employees.department");

if (queryModel.lastError().isValid()) qDebug() << queryModel.lastError();

queryModel.setHeaderData(0,Qt::Horizontal, QObject::tr("department")); queryModel.setHeaderData(1, Qt::Horizontal, QObject::tr("employee count")); queryView.setModel(&queryModel); queryView.setWindowTitle("employee countperdepartment"); queryView.show();

We can achieveuser-friendlycolumn headers byreplacingthe first twocolumn headers withsetHeaderData().1 Then webindthe modeltothe viewanddisplaythe view,asbefore, withacustomized heading.

9.7.4Editing Strategies

Allofthese table models arewritable.However,wehavenot yet looked closelyat thepoint in time whenthe modelwrites thedataback to thedatabase. QSqlTableModel andQSqlRelationalTableModel knowthree editing strategies,which areallocatedtomodels usingsetEditStrategy(). Theyareasfollows:

SqlTableModel::OnRowChange This is thedefault in allmodels.Ifthisstrategyis active, themodelsends an

1 This canalsobedonewiththe SQLinstruction AS, of course,but then you would haveto ensure,via tr(), that thequerycanbeinternationalized;otherwise, thecolumn headerscannot be transferredtoother languages.

268 9.7 UsingSQL ModelClasseswithInterview

UPDATE for thedataset as soon as theuserselects anotherdataset—that is, anotherrowin theview. SqlTableModel::OnFieldChange This transferseverychange to thedatabasedirectlyafter theuserhas changed avalue in afield. SqlTableModel::OnManualSubmit This temporarilysaves allchanges in themodeluntil either thesubmitAll() slot,which transfersall changestothe database, or therevertAll()slotis triggered. Thelatter rejectsall cached dataand restores thestatusfromthe database(seeChapter 9.7.5onpage 270for more on therevertAll()slot).

We willillustrate this last scenario bymodifyingthe examplefrompage 265so that it additionallycontains twobuttons that arearrangedinalayoutbeneath the table view.All othercommandsare left as theyare.

// sqlmvd/main.cpp(continued)

QWidgetw; QPushButton * submitPb =newQPushButton( QObject::tr("SaveChanges")); QPushButton * revertPb =newQPushButton( QObject::tr("Roll back changes")); QGridLayout * lay=newQGridLayout(&w); QTableView * manualTableView=newQTableView; lay->addWidget(manualTableView,0,0,1,2); lay->addWidget(submitPb,1,0); lay->addWidget(revertPb,1,1); QSqlTableModel manualTableModel; manualTableModel.setTable("employees"); manualTableModel.select(); manualTableModel.setEditStrategy( QSqlTableModel::OnManualSubmit); manualTableView->setModel(&manualTableModel); QObject::connect(submitPb,SIGNAL(clicked(bool)), &manualTableModel, SLOT(submitAll()) ); QObject::connect(revertPb,SIGNAL(clicked(bool)), &manualTableModel, SLOT(revertAll()) ); w.setWindowTitle("manuallyrevertable table"); w.show();

returnapp.exec(); }

Afterconverting theediting strategyto OnManualSubmit,weinserttwosignal/slot connections:Aclickonthe submitPb buttoncalls thesubmitAll()slot, whereas revertPbtriggers revertAll().

269 9 TheQtSql Module

Figure 9.5: With the OnManualSubmit editingstrategy, local changescan be transferredatany time youwanttothe database.

Nowwemustnot forgettodisplaythemainwidgetwas thenewtop-level widget. Theresultisillustrated in Figure 9.5.

9.7.5Errorsinthe TableModel

Several problems that occurinconnectionwiththe table models in Qt 4.1should notbeleftunaddressedatthispoint.One is that editor operationsdonot always function reliablyafter columns havebeen removed.The QSqlRelationalTableModel even ignoresthe removeColumn() instructionentirely.Asaworkaround,aproxy modelthatfiltersout theunwanted datasets is recommended here.Ifthe data should onlybe displayed,you can insteadsimplyplaceanSQL queryabovethe QSqlQueryModel. Anotherprobleminvolves therevertAll()slot, which is intendedtoundoall changes in relational tableswiththe OnManualSubmit editingstrategy.However,inthe columns in which aforeign keyrelation was previouslydefined withsetRelation(), revertAll()doesnot revertback to theold values. Theonlysolution until now was to connect theslotofthe buttonwithacustom-developedslotthatreplaces thecurrent modelwithanewonethathas thesameproperties. Since themodel temporarilysaves thedata, it will be lost in this way,and thenewmodelwilldisplay theoriginaldatafromthe database.

270 r te ap 10 Ch

TheGraphics Library“Arthur”

This chapter looksatthe drawingmethods of theclass librarythat Trolltechhas baptized “Arthur,” presumablyas areference to Microsoft’s “Avalon”technology.In this chapter wewillworkwithexamples that letusobservehowQt “paints” on buffers in thegraphicsand main memoryas wellasonwidgets andother devices, andwewillintroduce in detail theclassesbelonging to Arthur,together withtheir classicfields of application. Butfirstwemustexplainmorepreciselyhowdrawing reallyworks in Qt. Firstwewilllook at thecolor specificationsusedbyQt.

10.1Colors

Color specificationsare of centralimportanceingraphic interfaces,including the issues of howcolors aregenerated andhowknowncolorsare namedsothatyou can workwiththemefficiently.The followingsection is devoted to thequestionof howdevelopers can manage colors.

271 10 TheGraphics Library“Arthur”

10.1.1The RGBColor Space

Qt encapsulates colors in theQColor class. This is basedonthe RGBmodel, in which 8bits, representing arange of valuesfrom0to 255, areallocatedtoacolor. In addition,QColor specifies anothervalue,the so-called alphavalue,alsoreferred to as the alphachannel .Thisdefinesthe transparencyof apixel. QColor can also workwithvaluesother than integers. Foreach color command that accepts an integer,there exists afloating-point variation that allowscol- orstobespecifiedmoreprecisely.Whenever aQColor method that expectsin- teger colorinformation is discussedbelow,you can alwayssubstituteanassoci- ated floating-point variant instead, which accepts qrealvalues. Forexample, the setRgb()method, which expectsthree integer valuesfor thered,green,and blue colorcomponents andtakesanoptionalalpha value,has acorresponding floating- point equivalentcalledsetRgbF(). Thereare several waystogenerateanewQColor object.The basicQColor()con- structor creates an object withaninvalid color. Furthermore, thereisaconstructor that accepts colors describedusing integers. Thesemantics here correspondto thoseofsetRgb(). Thereisnoseparateconstructor that takesfloating-point num- bers as arguments, sincethiswould be ambiguous,asC++ automaticallyconverts integer valuestofloating-point values. To initializeacolorwithfloating-point val- ues, you first create an empty(and initiallyinvalid)QColor object,and then set colorvia setRgbF(). Earlierexamples often used yet anotherconstructor that accepts acolor chosen from 20predefinedcolorsthatare defined in theGlobalColor enumerator. This enumeration also includes valuesdescribinganumber of specialcases:The “color” Qt::transparent, for example, corresponds to QColor(0,0,0,0)and allowsaback- ground colortoshowthrough. In addition,QColor can deduce thedesired colorfromaname,asdefinedinthe SVG-1.0specification. 1 Forthispurposethe classhas aconstructor that accepts aQString or astring. ThesetNamedColor() method works in thesameway.This optionpermits anamed colortobeset later on,asillustrated in thefollowing example:

QColorcolor("navy"); // sets adarkblue color.setNamedColor("royalblue"); // sets alightblue

Thenames of allavailable namedcolorsare returned byQColor withthe color- Names()method. Finally,QColor hasaconstructorthatacceptsaQRgbvalue.QRgbinthiscaseis notaclassname, butaname,given byatype definition, for a32-bit integer.Given such avalue as an argument,thisconstructor sets theRGB valuesand thealpha

1 Seehttp://www.w3.org/TR/SVG/types.html#ColorKeywords.

272 10.1 Colors

value of thenewQColor instance to thevaluesencodedinthe QRgbvariable. Theadvantage of QRgbasalightweightalternativefor transporting RGBcolor information is particularlyevidentwhenever largeamountsofpixel datafroman image need to be read in. QRgbdivides theavailable bitsinthe 32-bit integer into fourinteger colorvalues, each consisting of eightbitsrepresentingvaluesfrom0to 255. This is doneas follows, in hexadecimalnotation(“A”=alphavalue,“R” =red,“G” =green,“B” = blue):

0xAARRGGBB

Youdonot havetoconstruct yourownQRgbvalues, however:The help functions qRgb() andqRgba() take on this task andexpect thecolor details to be provided in threeinteger arguments, as valuesbetween 0and255. qRgba()expectsthe al- phachannelasthe fourthargument.qRgb()omits thespecificationofthe alpha channel, andsets this componentofthe constructed QRgbvalue to 255 (corre- sponding to an opaque, that is,anontransparent, color).You can then accessthe individualQRgbcomponents via QRgb::qRed(), QRgb::qGreen(), QRgb::qBlue(), and QRgb::qAlpha(). Thesefunctionsreturn valuesfrom0to 255. Thefollowingexamplecreates ared,semitransparentQRgbvalue andpassesitto aQColor object,fromwhich wereadout thecolorsand thealpha channelwiththe rgba() function andwrite it back to theQRgbvariable:

QRgbrgba =qRgba(255, 0,0,127); // A=127,R=255, G=0,B=0 QColorcolor=QColor::fromRgba(rgba); rgba =0; // A=0,R=0,G=0,B=0 rgba =color.rgba(); // A=127,R=255, G=0,B=0

It is important here to usethe fromRgba() static method because thestandard constructor, which accepts aQRgbvalue,ignores thealpha channel.

10.1.2Other ColorSpaces

In addition to RGB, QColor can also usethe HSVmodel, which defines acolor through hue, saturation,and brightness (or value)parameters. (HSV is therefore sometimesalsoreferredtoasHSB,where theBstands for brightness ,which is actuallyamoreprecise termfor thethird parameter.) TheHSV modelcorresponds most closelyto thehumanperceptionofcolor composition. To make useofit, wemustaccordinglyconvertthe color, via toHsv(). Then wecan read outthe HSVparameters, either componentwisevia hue(), saturation(), value(), andalpha(), or allatthe same time,using getHsv():

273 10 TheGraphics Library“Arthur”

QColorred(Qt::red); QColorred =red.toHsv(); inth, s,v,a; red.getHsv(&h, &s,&v,&a); // HSV valuesnowin h, s,v,a

To specifyacolor in theHSV model, wehavethe setHsv() method, which expects thethree HSVcomponents as integersand,optionally,the alphachannel. If the alphavalue is missing,QColor assumesittobe255, that is, opaque . QColor is also able to acceptCMYK specificationsand to displaycolors specified in CMYK format. Since thereare differences between thecolor spacesofthe RGBand CMYK models,however,and CMYK is asubtractivecolor modelwhereas RGBisan additiveone,not allcolorscan be represented in both colormodels.Incases where thereisnoexact equivalentfor adesired conversion from onesystem to theother, Qt triestoapproximatethe colorascloselyas possible. To obtain theCMYK representation of acolor,itissufficient to read outthe four colorcomponents andthe alphachannel, withgetCmyk():

QColorred(Qt::red); intc, m, y,k,a; red.getCmyk(&c,&m,&y,&k,&a); // CMYK valuesnowin c, m, y,k,a

Here Qt calculates thematchingfour-colorvaluesingetCmyk()fromthe QColor’s internallystored RGBparameters. If aroutine of an applicationprogram requires thecolor to be specified in acolor modelother than RGBparticularlyoften,QColor can also representitinternallyin CMYK or HSV. In thesecases,noresources areused for conversion whenthe colorisaccessedvia getCmyk()orgetHsv(), respectively, buttheir interpretation as RGBcolorsisresourceintensive. To converttoanother colormodel, themethods toHsv(), toCymk(), andtoRgb() areused. Theconversion can be doneatanytime beforethe corresponding colorisaccessed:

QColorred(Qt::red); // convert toCMYK internally QColorred =red.toCmyk(); intc, m, y,k,a; // no conversion required, alreadyconverted red.getCmyk(&c,&m,&y,&k,&a); // CMYK colors nowin c, m, y,k,a

Youshouldonlyconvertpermanentlyto anothercolor model, however,ifyou have good reason to do so—internally,Qtusesthe RGBmodelnearlyeverywhere,which explains whyaclass is slower in operation after theinternalrepresentationofthe colors it uses is changedfromRGB to anothercolor model. ThesetCmyk()method defines acolor withCYMK details,and,inthe same wayas getCmyk(), reads in fourcolorsand,optionally,the alphachannel.

274 10.1 Colors

10.1.3Color SelectionDialog

Thecolor selectiondialogQColorDialog(Figure 10.1,left) is specialized for these- lectionofcolorsdescribedusing theRGB model.2 ItsAPI hasstaticmethods only. To read outanRGB value,getColor() is used:

QColorcolor=QColorDialog::getColor(Qt::red, this);

Thefirstparameter defines thecolor that thedialoginitiallyselects, andthe second describesthe obligatoryparentwidget, which can also be 0if themodalityof the dialog doesnot playanyrole.Ifthe user interruptsthe dialog, themethod returns an invalid color, which in this casecan be checkedwith!color.isValid().

Figure 10.1: QColorDialog:: getRgba() (right) differs from QColor- Dialog::getColor() (left) only in theinput field forthe alphachannel.

Anothermethod calledgetRgba()can be used to definethe alphachannelfor a color(Figure 10.1,onthe right): bool ok; QRgbrgb=QColorDialog::getRgba(qRgba(255,0,0,127),&ok, this); QColorcolor(rgb);

In contrast to getColor(), this method expectsaQRgbvalue.Since QRgbhas no dedicatedvalue for “invalid,” getRgba() againhas an ok parameter,which is setto false if Cancel is clicked. In this casegetRgba()returnsthe defaultvalue passed. The last lineshowshowyou can storeaQRgbvalue (together withits alphachannel) in aQColor object. bool ok; QColorcolor=Qt::red; color.setAlpha(127); color=QColorDialog::getRgba(color.toRgba(),&ok, this);

2 An HSVselection dialog is available as acommercialQtsolution.

275 10 TheGraphics Library“Arthur”

QColorDialog allowsanumber of yourowncolors, in additiontothe defaultcolor, to be stored in aseparatepalette. Thenumber of fieldsavailable is defined bythe QColorDialog::customCount() static method. To setacolorinthispalette, thesetCustomColor() is used.Thismethod expectsthe palettepositionand thecolor as aQRgbvalue.The followingcallloads thefirst positionofthe palettewithasemitransparentred tone:

QColorDialog::setCustomColor(0,qRgba(255, 0,0,127));

customColor() calls up thecolor in yourcustompaletteataparticular position. This method returnsthe QRgbvalue setfor theindexspecified:

QRgbQColorDialog::customColor(0);

Once theyareset, thecolorsapplyfor theentirelifetime of theapplicationfor all QColorDialog calls.

10.2Paintingwith Qt

We willnowtake alook at theclassesthatpaint colors in specificshapes. As in real life, paintingtoolsand adrawingboardare necessary.

Figure 10.2: QPaintDevice Thebaseclass QPaintDevice andits specializations

QPixmap QPicture QImage QPrinter QWidget

QBitmap

Painting toolsare bundledbyQt into theQPainter class. This can be used to both drawsimple brushstrokes andhandlemorecomplexgeometric forms,aswellas bitmaps.Awiderange of classesare eligible for useasthe drawingareas targeted byQPainter operations. Theyaredescendants of theQPaintDeviceclass.Each QPaintDevicecan thereforebearecipientfor QPainter operations. Theseinclude, amongothers, allwidgets andpixmaps,aswellasthe printinterface, QPrinter.An extensiveoverviewis showninFigure10.2. To start, here is asmall program that drawsafilled-in circle:

// pixmap/main.cpp

#include

276 10.2 Painting with Qt

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QPixmappm(100,100); pm.fill();

QPainterp(&pm); p.setRenderHint(QPainter::Antialiasing, true); QPen pen(Qt::blue, 2); p.setPen(pen); QBrushbrush(Qt::green); p.setBrush(brush); p.drawEllipse(10,10,80,80); QLabel l; l.setPixmap(pm); l.show(); returnapp.exec(); }

FirstwecreateanemptyQPixmapobject.The contents of this areinitiallyunde- fined,which is whywefill it withabasiccolor;whenitiscalledWithout afill color as an argument,fill()useswhite.

Figure 10.3: Anti-aliasing minimizesformation of staircaseartifacts.

Nowit is thepainter’s turn:Itperforms theactualdrawingoperations. So that thecirclelooksreallyround,and notsquare-edged, weswitch on anti-aliasing. This techniquesmooths theedgesthrough colorgradients,thus minimizingthe formation of steps(Figure 10.3). Beforesettingthe QPainter::Antialiasing flagto true,you should bear in mind that this can lead to significantlossofperformance, particularlyunder X11. Twosignificantpropertiesofapainter arecontained in additionalclasses. TheQPen defines howthepainter drawslines.Incontrasttothis, thepaintbrush, in theform of aQBrush,describeshowan area is filled, together withpatterns andtextures. In ourcasewewilluse abluepen,twopixelswide. We definethe paintbrushcolor as green,without apatternortexture.Wecan alreadydefinethese propertiesvia

277 10 TheGraphics Library“Arthur”

theconstructors. Then webindthese newdefinitions to thepainter withsetPen() andsetBrush(). Finallyweuse aQPainter drawingmethod to actuallydrawthecircle. drawEllipse() drawsacircle,startingfromthe coordinates (10 , 10),inasquare of 80× 80 pixels. Since wehaveset thesizeofthe whole image to 100× 100 pixels, thecircleisright at thecenter of thepicture.Qtadheres to thestandardprogramming convention in which thecoordinates (0, 0) specifythetop left corner of thecurrent reference system.Inour casethisisthe topleftcornerofthe pixmapdefined.

Figure 10.4: Ourpixmapina simplelabel

We displaytheresulting image in aQLabel, which can displaypixmaps as wellas textifyou usethe setPixmap()method insteadofsetText(). (Thismethod expectsa referencetoaQPixmap.)The result is showninFigure10.4.

10.3Geometrical Helper Classes

In theexamples just mentioned, wehaveplacedthe circle,using

p.drawEllipse(10,10,80,80);

in asquarewiththe side lengthsof 80 × 80 pixels, thelefttop corner of which is at thepoint (10 , 10).Ifwehad chosen twodifferent valuesfor height andwidth, this would haveresulted in aboundingrectangle insteadofaboundingsquare— anddrawEllipse() would havedrawnanellipse.Todescribe othergeometric objects, Qt provides theclassesQPoint, QSize, QRect, andQPolygon. TheQPointclass saves twocoordinates,without referencetoanexternalsystem. QSize, on theother hand,alsocombines twoparameterspassedinthe constructor, butinterpreted as aheightand awidth insteadofascoordinates,againwithout definingareference point.These object typesare united bytheQRect class, which generates arectangle:WhenpassedaQPoint andaQSizeasarguments, theQRect constructorinstantiates acorresponding rectangle. Alternatively,you mayusean

278 10.3 Geometrical Helper Classes

overloadedconstructor andspecifythe ( x, y ) positionfor thetop left corner of therectangle andthe height andthe width of therectangle,inthe formoffour integers. In casearectangleistobedefinednot via itsattachment point anddetails of itslengthand width,but byspecifyingatopleftand bottomright point,QRect provides anotherconstructor that expectsthispairofcoordinates in theformof twoQPoints. TheQPolygon constructortakesanumber of pointsand sketches outapolygon, edge byedge,asdeterminedbythepairs of consecutivepoints. This classisaspe- cial caseofQVectorand includes aseriesofusefulmethods.For example, QPolygon::boundingRect()definesthe smallest possible rectanglethatcontainsall thepointsofthe polygon. Thereare also floating-point variantsofall theseclasses: QPointF, QSizeF,QRectF, andQPolygonF, which providefor increasedprecision. InsteadofsupplyingdrawEllipse() withrectangle parameters, as above, you can use an alternativeversion of themethod that accepts aQRect as an argument:

QRectrect(10,10,80,80) p.drawEllipse(rect);

Theadvantage of this notation becomesclear as soon as several actions take place withinthe same coordinates,for example, if anothergeometric figure is to be added.Practical useofthe classesdiscussedsofar is illustrated bythefollowing, slightlymodifiedexample:

// pixmap2/main.cpp

#include intmain(intargc, char * argv[]) { QApplication app(argc, argv); QRectr(0,0,100,100); QPixmappm(r.size()); pm.fill();

QPainterp(&pm); p.setRenderHint(QPainter::Antialiasing, true); QPen pen(Qt::red, 2); p.setPen(pen); QBrushbrush(Qt::blue); p.setBrush(brush); QRectri=r.adjusted(10,10,-10,-10) p.drawEllipse(ri); QLabel l; l.setPixmap(pm);

279 10 TheGraphics Library“Arthur”

l.show(); returnapp.exec(); }

Here therectangle rforms theglobalreference system to which everythingelseis aligned. Theconstructor of thecorresponding pixmapstretchedout bythis expects merelyasizedetail. Accordingly,wepassthe sizeofthe rectangle, withr.size(), as QSize. To drawtheellipse,wegeneratearectangleshrunk by10pixelsoneach edge, which wecan usefor otherdrawingoperations.3 Theadjusted() function,with rasthe referencesystem,generates thecoordinates for anewrectangle: This is donebymovingfromthe topleftcorner10pixelstothe rightand 10pixelsdown, from thelower rightcorner10pixelstothe left (since this is anegativevalue)and 10pixelsupwards,and movingthe edgesinparallelwithruntil theylie on these points. Qt stores drawingpaths in instancesofthe QPainterPath class; theyareprovided for creating more complexgeometric objects, for which several of theseprimitives arenecessary.AQPainter object can usethese paths to filladescribedarea, to cut that shape outofsomeother area, 4 or to simplydrawacorresponding outline.

10.4How to PaintonWidgets

As can be seen in theinheritancediagram in Figure 10.2,QWidget, andtherefore allwidgets,are also QPaintDevices.Thisbringsustothe most important question in this chapter:Howcan you paint on widgets? To explainthis, werecommend abrief tour of eventhandlinginQt. If theuser starts aprogram,calls adialog, altersthe interface, or terminates aprogram in thecurrent window,the graphicssubsystem of theoperating system requests the applicationtoredrawthecorresponding windowor regions. Forthispurposeitsets off a paint event . Qt then calls thepaintEvent()method for thewidgets involved.Thismethod de- scribeshowthewidgetisdrawn. ThepaintEvent()expectsaQPaintEventobject as an argument.Thisobject is onlyrelevantfor more complexwidgets:Witha complexwidgetitcan often be worthwhile to redrawonlytheparts that need to be updated.Todothis, theclass hastwomethods:region() reveals which region of thewidgetneedstoberedrawn, andrect()returnsarectanglethatenclosesthis region.

3 Scalingwould also be possible via amatrixtransformation.Qtenablesthisvia theQMatrix class, which wewill introduce on page 290. 4 We will look at this process, also knownas clipping,onpage307.

280 10.4 HowtoPaint on Widgets

Forour simple example, wedonot need thesedetails.The declaration part of the code appearsasfollows:

// widgetpaint/paintwidget.h

#include class PaintWidget:public QWidget { Q_OBJECT public: PaintWidget(QWidget * parent=0); ˜PaintWidget() {};

virtualvoid paintEvent(QPaintEvent * ); virtualQSizesizeHint() const { returnQSize(200,200);} } ;

Here wefirstoverride paintEvent(). In addition weredefine thereturn value of the sizeHint()method. This ensuresthatwehaveasquare,atleast whenthe program starts,inwhich drawEllipse() then drawsacircle.Otherwise, sizeHint()would be oriented according to thelayoutintowhich thewidgetisinserted,orelsereturn an invalid sizeifnolayoutisresponsible for thewidget. In thesecases wewould thus nothaveensured that height andwidth wereidentical. Qt uses thesizehintsuppliedbysizeHint()whendisplayingthe widget, unless anotherlayoutforcesadifferent size. In thecurrent examplewewillforce neither apermanent fixed sizenor afixed page ratio, so that thecirclemayturn into an ellipse if thewidgetisenlargedorifitisfitted into alayout. If you can manage withjustawidgetofafixed size, you can simplyusethe QWid- getmethod setFixedSize(), which accepts either aQSizecontainer object or integers specifyingthe details of height andwidth.Ifyou callsetFixedSize()inawidget’s constructor, you don’t need to reimplementsizeHint(). In this caseeven layouts cannotchangethe sizeofthe widgetinthiscase. Theproblemcan also be solved more flexiblywithaseparatelayoutclass,which guaranteesafixed page ratio. From theaboveexampleitsoon becomesclear whatanadvantage it is to define thecirclerelativetoafixed referencesystem,inthiscasethe frameofour widget: Thecirclenowgrowsand shrinksautomatically,relativetothe widget’ssize. If overriding sizeHint()doesnot work, for whatever reason,itisessentialthatyou checkwhether you mayhaveforgotten thekeywordconst.The compilerwilloth- erwisegenerateanonconstantvariant of themethod, which is also valid in C++, butissomethingdifferent,which is whyit doesnot issueawarning. In theimplementationpartofour example’scode,weare onlyinterested in the definitionofthe paintEvent()method:

281 10 TheGraphics Library“Arthur”

// widgetpaint/paintwidget.cpp

void PaintWidget::paintEvent(QPaintEvent * ev) { QWidget::paintEvent(ev); QPainterp(this); p.setRenderHint(QPainter::Antialiasing, true); QPen pen(Qt::blue, 2); p.setPen(pen); QBrushbrush(Qt::green); brush.setStyle(Qt::Dense4Pattern); p.setBrush(brush); QRectri=rect().adjusted(10,10,-10,-10); p.drawEllipse(ri); }

We first forwardthe paint eventev,passedinthe calltoPaintWidget::paintEvent(), to thecorresponding method of theparentclass in theinheritanceline, which in this caseisQWidget::paintEvent(). This is thefirstthing to be drawn. Then weinstantiate aPainter on thestack andtreat this as discussedinSection 10.2.The onlydifferenceconsistsinthe selectionofour referencesystem.Now this is no longer chosen artificially,but dependsdynamicallyon theenvironment dimensions of thewidget, which QWidget::rect()returnstousasarectangle.

Figure 10.5: Drawingdirectly: Our example uses a QPainter to paint directly on awidget.

We nowredrawourcircle, butthistimeonthe PaintWidgetinsteadofonapixmap. Theresultcan be viewed in Figure 10.5.Asafurtherdetail, this time ourpaintbrush is notcompletelygreen,but displaysapattern(Qt::Dense4Pattern). Paintbrushes can generate patterns,tiles, andgradients;the examples to come willfurther illus- tratethis. In this contextweshouldmention that it is worthwhile to storethe actualcharac- ter code in helper methods:Fromour experience,paintEvent()can growrapidly,so you can quicklylose track of what’shappeningthere.

10.4.1How to PreventMonitor Flicker

To displaythegraphicsdrawnwithQPainter without flickeronthe monitor, Qt 4 uses atechniquecalled doublebuffering.Duringthisprocedure,all QPainter op-

282 10.5 Using QPainter in Practice

erations first land in a memory buffer that is not displayed. Only when all painting operations are finished does Qt copy the buffer’s contents to the screen. Double buffering thus elegantly prevents the user from seeing an unpleasant flicker on the screen caused by the multiple steps needed to update the screen as objects are redrawn. If, under X11, you want to implement double buffering yourself, you can switch off automatic double buffering by Qt with the following instructions: extern void qt_x11_set_global_double_buffer(bool); qt_x11_set_global_double_buffer(false);

This is only useful in specific cases, for example, if part of the program uses a different rendering library. Otherwise, double buffering is always switched on, on all platforms, and should be left that way.

10.5 Using QPainter in Practice

As we have now become familiar with the geometry classes and the underlying capabilities of QPainter, it is time to put them to the test in a practical example. We will write a PieWidget class, which paints a pie chart together with its legend on a widget and calculates the size required for both parts (Figure 10.6). It uses the sizeHint() and minimumSizeHint() methods to do this.

Figure 10.6: A pie chart with legend

We implement the actual drawing process in paintEvent(), and the widget obtains the data necessary for this from a QHash. This is an associative data structure that connects a key to a value. In the values associative hash, the name (a QString) serves as the key and the integer as the corresponding value. From a semantic point of view, the key is the legend entry, and the integer value is the associated number:

// piechart/piewidget.h

#ifndef PIEWIDGET_H

283 10 TheGraphics Library“Arthur”

#define PIEWIDGET_H

#include #include

class PieWidget:public QWidget { Q_OBJECT public: PieWidget(QWidget * parent=0);

QSizesizeHint() const; QSizeminimumSizeHint()const; void addEntry(const QString&key,intval);

protected: void paintEvent(QPaintEvent * ev);

private: QHashvalues; } ;

#endif // PIEWIDGET_H

10.5.1Drawing aPie Chart

In this specificcasewewillpopulate thevalueshashtable withdatafromafic- titioussurveyon themostimportant goalsinlife, as can be seen in Figure 10.6. Questionshereserveasthe key,and theassociatedvaluesare thenumber of peo- plewho made thecorresponding choice. In theconstructor weonlyperformthe initializations for theparentclass.The addEntry() method allowsnewvaluestobeentered into thehashtable:

// piechart/piewidget.cpp

#include #include "piewidget.h"

PieWidget::PieWidget(QWidget * parent) :QWidget(parent) { }

void PieWidget::addEntry(const QString&key,intval) { values.insert(key,val); }

Beforewetake alook at thedetails of paintEvent(), wemustthink abouthowto di- videupthe widget. Thepie charts mustalwaysberound,soherethe height should

284 10.5 Using QPainter in Practice

be the same as the width. The legend part should always be as wide as the longest text in the hash. The minimum height is calculated from the number of legend entries and their vertical spacing. The handler for the paint event and the reim- plemented methods sizeHint() and minimumSizeHint() must take these conditions into account. Before we can start painting, we first calculate the sum of all the values. We’ll need this later on to calculate (using a rule of three calculation) how much of the pie the current segment should take up:

// piechart/piewidget.cpp (continued) void PieWidget::paintEvent(QPaintEvent * /*ev*/) { // calculate total QHash::const_iterator it; int total = 0; for(it = values.begin(); it != values.end(); ++it) total += it.value();

// prepare painter QPainter p(this); p.setRenderHint(QPainter::Antialiasing, true);

We now instantiate the Painter and assign it the current widget (this) as the paint device. We also enable anti-aliasing.

Drawing Segments of the Pie

We also need to have a series of colors for the different pie segments in the dia- gram. To do this we access the colorNames() method, which gives us all the colors predefined in QColor. We also introduce the colorPos variable, which will later be used to select an element from the list. We initialize it with 13, because from this point onward there are several pleasant pastel colors in succession (in practice you would probably define a list with your own colors):

// piechart/piewidget.cpp (continued)

// prepare colors QStringList colorNames = QColor::colorNames(); int colorPos = 13; // pastel colors

int height = rect().height(); QRect pieRect(0, 0, height, height);

Then we define the dimensions of the chart. These should exactly match the height of the widget. We obtain this height value from the current size of the widget:

285 10 TheGraphics Library“Arthur”

rect() returnsthe sizeinthe formofaQRect(), andwecan extract theheightfrom this withheight(). pieRectisnowinitialized to containthe rectangleinwhich wewilllater drawour piechart.Wereservethe space remaininginthe width for thekey.Weobtain thecorresponding rectanglebyfirst copyingthe measurements of thewidget, with rect(), andthensubtractingthe width of pieRectfromthissquareonthe left side, withsetLeft():

// piechart/piewidget.cpp (continued)

// dedicaterighthalf tolegend QRectlegendRect=rect(); legendRect.setLeft(pieRect.width()); legendRect.adjust(10,10,-10,-10);

With theadjust()callwemoveten pixelsfurther inwardfromall sides, so the rectanglebecomes smaller. This hasthe effect that weobtainten pixelsofspace from theouter edgesand from theright side of thepie graphics. This causesthe geometriesfor both partsofthe widgettobedependent on the currentwidgetsize, andweproceed to drawthesegmentsofthe pieand the legend entriesbelonging to it,entryfor entry.Weneed twohelpvariablestodo this.lastAngleOffset specifies theangle in thecirclewhere wepreviouslystopped drawing. We also requirecurrentPoslater to drawthekeyentryat thecorrect position:

// piechart/piewidget.cpp (continued)

intlastAngleOffset=0; intcurrentPos=0;

// createanentry forevery piece of the pie for(it=values.begin(); it!= values.end(); ++it) { intvalue=it.value(); QString text =it.key();

intangle =(int)(16 * 360* (value/(double)total)); QColorcol(colorNames.at(colorPos%colorNames.count())); colorPos++;

// gradientforthe pie pieces QRadialGradientrg(pieRect.center(),pieRect.width()/2, pieRect.topLeft()); rg.setColorAt(0,Qt::white); rg.setColorAt(1, col); p.setBrush(rg); QPen pen =p.pen(); p.setPen(Qt::NoPen);

286 10.5 Using QPainter in Practice

p.drawPie(pieRect, lastAngleOffset, angle); lastAngleOffset += angle;

We again iterate through the hash and remember the keys and values. For each entry in the hash table we can determine how many degrees of the circle are to be apportioned to the current segment of pie. The value stored in the current key, divided by the total sum, results in the fraction that this value represents. Multiplied by 360, this reveals how many degrees the segment of pie to be drawn takes up. It only remains to be explained from where the additional factor of 16 comes. This is due to a peculiarity of the drawPie() method, which expects its details 1 in parts of 16 th of a degree, for reasons of precision. angle therefore contains the actual number of degrees, multiplied by 16. We then select the current color using the colorPos variable from the colorNames list. With a modulo calculation (%), we ensure that under no circumstances do we overwrite the end of the list, which means that if we were to run out of colors, we would just start assigning the current color from the beginning of the list again. The next step is to define the form and color of the paintbrush and pen. Whereas we always used a continuous color for the paintbrush, we will now change to a gradient. Qt has several predefined gradient types. Here we will use a radial one. This gradient has a center, a diameter, and a focus. We specify the center as the real center of pieRect, and we also determine the diameter via pieRect(). So that the gradient later “creases” the edge of the pie chart circle, thus creating the impression of spatial depth, we place the focus to the edge of the upper left region. We achieve this by specifying a region, with pieRect.topLeft(), which actually lies outside the pie. Between the center and the outer edge, we must also define at least two values for the course of the gradient. We do this using setColorAt(), which accepts colors for any floating-point numbers between 0 and 1. Instead of a color, we pass the gradients obtained in this way with setBrush(). Since we do not want any borders, we set the pen to NoPen, but not before saving the current pen—we still need it to draw the legend text, where it is used to define the font colors. Now we can illustrate the current hash entry. drawPie() stretches out a rectangle in pieRect and draws an angle/16-degree large segment of pie, starting at lastAn- gleOffset.

Drawing Key Icons

The matching legend entry is still missing in the legendRect. We make this associ- ation clear with a square in the color of the corresponding segment of pie, which we store in the legendEntryRect variable:

287 10 TheGraphics Library“Arthur”

// piechart/piewidget.cpp (continued)

// calculatethe squaresforthe legend intfh =fontMetrics().height(); QRectlegendEntryRect(0,(fh* 2)* currentPos,fh,fh); currentPos++; legendEntryRect.translate(legendRect.topLeft());

// define gradientforthe legend squares QLinearGradientlg(legendEntryRect.topLeft(), legendEntryRect.bottomRight()); lg.setColorAt(0,col); lg.setColorAt(1, Qt::white); p.setBrush(QBrush(lg)); p.drawRect(legendEntryRect);

Since this square should matchthe height andwidth of thetype size, itssizemust be basedonthe nature of thecurrent font. TheQFontMetricsclass is of help to us here,asitcalculates thesizeoflettersand stringsinaspecificfont. Thefont metricsfor thecurrent widgetare obtained via fontMetrics(). If you just want to change thefonttemporarilywithinthe Painter,you should read outthe font metricsvia themethod of thesamenamefromQPainter. Here wejustrequire information on themaximum height of aletter,which weread outwithheight(). If wenowplan as muchspacebetween theentries as is necessary for oneentry,thenwecan calculate thepositionofthe square:Onthe X-axis it lies directlyat thezeropoint,while on theY-axis it wanders twofontheights downfor each position((fh*2)*currentPos).Width andheightare also specified byfh in each case. Nowwestill havetomovelegendEntryRect to thelegendRect, because so far it is at thezeropoint of theX-axis.Thisisdoneusing thetranslate method, to which wepassthe upperleftpoint,thatis, ourdesired offset. We redrawthesquareitselfwithagradient, butthistimewithalineargradient running from thetop righttothe bottomleft, ending in thecolor white. Since thepen is still defined withNoPen,the drawRect() methodnowcalledwiththe rectanglealsodrawsthe square without theedge.

InsertingLegendText

It is nowtime to drawthelegendtextnexttothe square.Sothatthe distance from thesquaretothe textisadjusted to thesizeofthe fontused, weselectthe width of theletter xin therespectivefontasthe spacing.For this purposeweadd thecorresponding width to the x componentofthe textStart point.Thisvariable nowcontains thetop left point of ourtext. Thelower left point is determinedby combiningthe rightedge of legendRect andthe lower side of thecurrent entry

288 10.5 Using QPainter in Practice

rectangle legendEntryRect into a new point. Together with textStart, this now opens up the textEntryRect in which space should be made for our text:

// piechart/piewidget.cpp (continued)

// draw text behind squares QPoint textStart = legendEntryRect.topRight(); textStart = textStart + QPoint(fontMetrics().width(’x’), 0); QPoint textEnd(legendRect.right(), legendEntryRect.bottom()); QRect textEntryRect(textStart, textEnd); p.setPen(pen); p.drawText(textEntryRect, Qt::AlignVCenter, text); } }

After restoring our paintbrush, we insert the legend text precisely into the rectangle spcified, using the drawText() method. The AlignVCenter option ensures that the text is centered in its vertical alignment. We repeat this procedure for each entry in the list until the circle is completely full.

10.5.2 Defining the Widget Size

In the minimumSizeHint() and sizeHint() methods, we need to determine only sen- sible minimum and default sizes for the widget. We specify that the widget must not be smaller than the default size, thus bringing both methods into line. But the widget may inflate at any time. The height is the decisive factor for vertical expansion, which results from the total of all legend entries, together with the intervals between them. For the horizontal spread, we must first calculate the length of the longest entry in pixels. To do this we again iterate through the hash and look for the longest string in it:

// piechart/piewidget.cpp (continued)

QSize PieWidget::minimumSizeHint() const { int fh = fontMetrics().height(); int height = fh*2*values.count(); int longest = 0; QHash::const_iterator it; QFontMetrics fm = fontMetrics(); for(it = values.begin(); it != values.end(); ++it) longest = qMax(fm.width(it.key()), longest); int width = height+longest+fontMetrics().width(’x’)+fm+(2*10); QSize minSize(width, height); return minSize; }

289 10 TheGraphics Library“Arthur”

QSizePieWidget::sizeHint() const { returnminimumSizeHint(); }

Thetemplatefunction qMax() helpsusindoing this.Itisable to comparetwo objectsofthe same type,provided theyboth havethe smaller-than operator,and it returnsthe larger element. In thesameway,qMin() also exists. Thewidth is determinedbythefollowingparameters: thewidgetheightofthe square of thepie, 5 thelongest entryin thehashtable,the width of an x,the width of thelegendsquare(fm)and thewidth of the 2 × 10 pixel–widemarginonboth sidesofthe legend square.Wepack theheightand width into an instance of QSize andreturn this.

10.5.3The DiagramApplication

In ordertouse theclass,weinstantiate thewidget, add afewentrieswithvalues, anddisplayit.Wehavealreadyseen theresultinFigure10.6:

// piechart/main.cpp

#include #include "piewidget.h"

intmain(intargc, char * argv[]) { QApplication app(argc, argv); PieWidgetw; w.addEntry("Choice 1",50); w.addEntry("Choice 2",40); w.addEntry("Choice 3",60); w.addEntry("Choice 4",70); w.show(); returnapp.exec(); }

10.6Transformations of theCoordinate System

NormallyaQPainter drawsinaneutral, two-dimensionalcoordinate system.But sometimesitisnecessaryto manipulate this.Qtincludesthe QMatrixclassfor this purpose, which wewillfirstapproachtheoretically.AQMatrixmakesavailable a 3 × 3 matrixin theform

5 With squares, length andwidth areequal.

290 10.6 Transformationsofthe CoordinateSystem

  m 11 m 12 0    m 21 m 22 0  dx dy 1

To understandhowit functions, ashort mathematics lessonisrecommended:To transportapoint ( x, y ) in atwo-dimensionalspacetoanewpoint ( x % ,y% ) ,it appliesthat:

% x = m 11 ∗ x + m 21 ∗ y + dx % y = m 22 ∗ y + m 12 ∗ x + dy

Theconstructor of QMatrixhasthe form

QMatrix(qrealm11, qrealm12,qrealm21, qrealm22,qrealdx,qrealdy);

It thereforetakesthe individualmatrixcomponents as floating-point numbersand instantiates thecorresponding 3 × 3 matrix. 6 Formatricesthatare invertible,the corresponding inverse matrixundoesthe op- erations carried outbytheoriginalmatrix.QMatrixobjectshaveamethod bythe name of inverted() that calculates theappropriate inverse matrix.Thismethod ex- pectsasone of itsparametersthe address of aBooleanvariable,and it returnsa QMatrix.Ifthe value stored in thevariable after themethod hasbeen calledistrue, then theQMatrixobject that inverted() returnsisthe inverse matrixof thespec- ified matrix.Ifinverted() sets theBooleanvariable to false,however,the original matrixis singular ,meaning that no inverse matrixexists.Inthiscasethe method returnsaunit or identy matrix,whose applicationresults in no transformation taking place; that is,the unitmatrixmaps each point to itself.Whether amatrix can be inverted can be checked, whennecessary,withisInvertible().

In theunitmatrix,all theelementsonthe main diagonal ( m 11, m 22,and thelower right-hand 1 )havethe value 1 ,and theothershavethe value 0 .Whether amatrix is aunitmatrixor notisrevealedbytheBooleanreturn value of theisIdentity() method. To movepointsbyaspecific displacement usingatransformation matrix, dx and dy areallocatedvaluesfor the x and y offsets,inthe followingexample 10 each, andthe othervaluesare likethose of aunitmatrix:

x % =1∗ x +0∗ y +10 y % =1∗ y +0∗ x +10

6 qreal is adatatype defined byQt,corresponding to theC++ double type.Section B.6in AppendixBlists alltype definitions defined byQt andexplains theirbenefits.

291 10 TheGraphics Library“Arthur”

This matrixtransformation,calledatranslation, moves thepoint ( x, y ) so that the newx-coordinate x % is thevalue of x shifted by dx (here: 10)units.Inthe same way,you obtain y % bymoving y by dy (inthisexample, also 10)units. Insteadofdetermining yourselfwhatthe corresponding QMatrixwill look like, you can applythetranslate()function to aunitmatrix:

QMatrixmatrix; // yieldsaunitmatrix matrix.translate(10.0,10.0);

From nowon,matrixmoves allpointsby 10 units.7 Thedecisivevariableswhenscaling(that is,whenenlarging or decreasing in size) are m 11 and m 22,each setto 10 in thefollowingexample:

x % =10 ∗ x +0∗ y +0 y % =10 ∗ y +0∗ x +0

Thecorresponding matrixtransformation enlarges thepointsinthe figure byafac- torof10in each direction, thus scalingthe coordinate system.The corresponding QMatrixcan be obtained throughthe call

QMatrixmatrix; // yieldsaunitmatrix matrix.scale(10.0,10.0);

Figure 10.7: Allfour transformationsata glance:scalingand rotation (above), moving andshearing (below)

So far wehavenot seen theeffectsof m 21 and m 12.These valuesinatransforma- tion matrixcause theupperand lower sidesofarectangle to moveapart from each otherinahorizontaldirection,and thesides to come closer together accordingly.

7 Theunits themselves arespecifiedbytheclass used bythematrix.Atthe moment weare using thetermabstractly.

292 10.6 Transformationsofthe CoordinateSystem

This effect,which can be seen in Figure 10.7 at thebottomright,isreferredtoas shearing:

x % =1∗ x +10 ∗ y +0 y % =1∗ y +10 ∗ x +0

Rotation uses thefactorsfor shearing andscaling. Thetheorybehind this is rather complicated,however;amore detailedtreatment of this,together withderivations, would go beyondthe scope of this book.Atthispoint,wecan onlyascertainthat rotation around theoriginofthe coordinate system can be represented as follows:

x % =cos a ∗ x − sin a ∗ y +0 y % =sin a ∗ y +sin a ∗ x +0 a stands forthe degree of rotation in radian form; therotationaldirection is coun- terclockwise. Therotate()method implements this rotation.Asaparameter it demandsthe angleindegrees,which can be specified in floating-point precision.

10.6.1Transformations in Practice

To achieveabetter understanding of transformationswithQt, weshall look at the followingexample, which drawsacircle filledwithpatternontoawidget. Aclick on thewidgetstartsitturning;the second clickstops it again. In addition weallow theusertochangethe directionofrotationwiththe mousewheel. In addition to thesizeHint()method, which defines theinitial sizeofthe widget, wereimplement paintEvent()for drawing, mousePressEvent()for catchingmouse clicks,wheelEvent()for reactingtothe scroll wheel,and timerEvent(), which helps us to implementautomatic rotation.rotate()performs theactualrotationwork. We also need thevariable timerIdtobeable to handle atimer.Withdegreewe note byhowmanydegrees thecirclehas just rotated:

// rotationwidget/rotationwidget.h

#ifndef ROTATIONWIDGET_H #define ROTATIONWIDGET_H #include #include class RotationWidget:public QWidget { Q_OBJECT public: RotationWidget(QWidget * parent=0);

QSizesizeHint() const { returnQSize(200,200);}

293 10 TheGraphics Library“Arthur”

protected: void paintEvent(QPaintEvent * ev); void mousePressEvent(QMouseEvent * ev); void timerEvent(QTimerEvent * ev); void wheelEvent(QWheelEvent * ev);

void rotate(intdegree);

private: inttimerId; intdegree; } ; #endif // ROTATIONWIDGET_H

Theconstructor initializes theparentclass andensures that thetwomembervari- ablesare preset to zero:

// rotationwidget/rotationwidget.cpp

#include #include "rotationwidget.h"

RotationWidget::RotationWidget(QWidget * parent) :QWidget(parent) { degree =0; timerId=0; }

In paintEvent()wefirstdetermine thegeometryof thewidget. So that ourdraw- Ellipse() callimmediatelyresultsinacircle,wematch thewidth of therectangle to itsheight. Then weadjustthe rectangle. Specifically,QPainter from version 4.0 onwardnolongerincludesthe linethatwedrawwiththe penintodimensionsof thefigures. Accordingly,wemustreducethe rectangleinwhich weare aboutto drawthecirclebythewidth of thepen,which in this caseistwopixels:

// rotationwidget/rotationwidget.cpp (continued)

void RotationWidget::paintEvent(QPaintEvent * / * ev * /) { QRectpaintRect=rect(); paintRect.setWidth(paintRect.height()); paintRect.adjust(2,2,-2,-2); QPainterp(this); p.setRenderHint(QPainter::Antialiasing, true);

QMatrixm; m.translate(center.x(),center.y());

294 10.6 Transformationsofthe CoordinateSystem

m.rotate(degree); m.translate(-center.x(),-center.y()); p.setMatrix(m);

QPointcenter=paintRect.center(); p.setBrush(QPixmap("qt.png")); p.setPen(QPen(Qt::black, 2,Qt::DashLine)); p.drawEllipse(paintRect); }

Afterwehaveinstantiated theQPainter andhavetaughtittohandleanti-aliasing, it is nowtime to create atransformation matrixthat allowsthe coordinate system to rotate around themidpoint of thecircle. To do this wecreateanewmatrix. Since thecenter of thedesired rotation is notthe currentzeropoint (origin) of the coordinate system,but thepoint at thecenter of thesquare, wefirstmovethe center of oursquaretothe zeropoint of ourcoordinate system.Thenwerotate thematrixandmovethe point back to itsoriginallocation. We then passthe matrixgenerated in this mannertothe Painter.Everythingthat it nowpaintsisrotated bythenumber of degrees specified in degree. We select an ellipse as adrawingobject.Tofill it withatiledpattern, wepassan image to thepaintbrush, which then turnsitintotilesplacednexttoeach other, provided thereissufficient space. This time wealsoselectthe penslightlydifferently:Itisblack, hasathicknessof twopixels(as mentioned),and forms adashedline(Qt::DashLine).Finally,wedraw an ellipse into thesquare, thus forcing acircletobedrawn. As soon as theuserclicks thewidgetwithone of hismouse buttons,the widget obtainsamousePressEvent(). We first checkwhether theuserpressedthe left mousebutton, andwhether thetimer is 0.Ifitis, then thetimer is notrunning, andwecan nowstartit. If, however,itcontainsanumber notequal to 0,thenitis activeand wedelete it,but notbeforeresettingthe timerId:

// rotationwidget/rotationwidget.cpp (continued) void RotationWidget::mousePressEvent(QMouseEvent * ev) { if (ev->button() != Qt::LeftButton) return; if (timerId==0) timerId=startTimer(20); else { killTimer(timerId); timerId=0; } } void RotationWidget::timerEvent(QTimerEvent * ev) {

295 10 TheGraphics Library“Arthur”

if (ev->timerId() == timerId) rotate(1);

}

We setthe timerinterval to 20milliseconds,which corresponds to 50timerevents andtherefore, ideally,50frames persecond, sincefor each timerEvent()triggered, theprogram calls rotate(). Theparameter in this call(here:1)specifiesbyhow manydegrees thecircleshouldturn. With most mice,ascroll wheel movementcorresponds to 15 degrees.Qtmultiplies this byafactor of 8, so in this casewedivideitagainby8. Ascrollwheel move- ment thereforerotates thecircleby15 degrees clockwiseorcounterclockwise— dependingonwhich directionthe wheel is moved:

// rotationwidget/rotationwidget.cpp (continued)

void RotationWidget::wheelEvent(QWheelEvent * ev) { rotate(ev->delta()/8); }

void RotationWidget::rotate(intdeg) { degree =degree +deg %360; update(); }

rotate nowmanagesthe degree value that weuse in thepaint event. As soon as it exceeds359, themodulo operatorensures that thecounter is resettozero. In wheelEvent(), thedelta() method from QWheelEventholds themovementvalue. To force apaint event, wemustnowcallupdate(). update()sends arepaintEvent() via theeventsystem to thewidget. This in turn causesthe repaintEvent()handler to be called, andthe widgettoberedrawn. To demonstratethisbehavior, it is sufficienttoinstantiate thewidget, together withaQApplication, displayit,and then enter theeventloop, withapp.exec():

// rotationwidget/main.cpp

#include #include "rotationwidget.h"

intmain(intargc, char * argv[]) { QApplication app(argc, argv); RotationWidgetw; w.show(); returnapp.exec(); }

296 10.7 QImage

10.7 QImage

If image points aretobemanipulated directly,the QImage,which is optimized for this purpose, is idealasa“screen.” Qt carries outcorresponding operationsonthe processor, whereas thegraphic cardisnormallyresponsible for QPixmapoperations. UnderX11 in particular,another differencebetween thetwoclassesisofsomesig- nificance:The Xclient is responsible for renderingQImages, whereas pixmaps are drawnbytheserver.Everyconversion between classesisthereforeslow,and possi- blybandwidth intensive,8 butneverthelessgenerallypossible withQPixmap::con- vertToImage() andQPixmap::convertFromImage(). An advantage of QImage is its platformindependence, apropertythat QPixmapdoesnot have. If aprogram should onlybe basedonQCoreApplication, butshouldstill process graphics, QPixmapisnot available,but it is still possible to workwithQImage. 9

10.7.1Storage Formats,Transparency,and ColorPalettes

AQImage is capable of storingimagesinvarious waysinthe main memory.The de- velopercan specifytheimage formatwhencallingthe classconstructor bypassing it thematchingvalue of theFormatenumerator. Theformat()method accordingly returnsthe formatofthe currentQImage. NormallyQt stores imagesinaqualityrequiring32bitsfor each pixel.Indo- ingthis, QImage uses 8bitseach forthe colors red, green,and blue, so that only 24 bitsare used up.The remainingbyte either remainsunused, 10 or specifies the “transparency”(the alphachannel )ofeach pixel.The formeroptionisspecifiedus- ingQImage::Format_RGB32,whereas theformatwiththe predefinedtransparency value is referred to byTrolltechas ARGB32 (QImage::Format_ARGB32). It is relativelyresource intensivetouse an alphachannelwhendrawing, because aseriesofcalculationsmustprecede each drawingoperation:For each pixel,the processormultiplieseach color channelwiththe value of thecorresponding alpha channel, anddivides theresultby255. Theobvious consideration, of performing thesecalculationswhenthe pixel is setand savingthe resultsinthe colorvalues, hasbeen putintopracticebyTrolltechwiththe ARGB32-Premultiplied format (QImage::Format_ARGB32_Premultiplied).Herethe alphachannelisstill stored as an additionalvalue.The disadvantage of this formatisthatwhenthe alphachannel is recalculated,thisdoesnot result in exactlythesamecolor.Since thedeviation is verysmall, however,itcan often be ignored. However,ifyou constantlycon-

8 Remember that anyconversion is expensive, even in alocal X-session—thatis, even if you do notuse thenetworkcapabilityof theXserver. 9 Nevertheless, QImage remainsapart of theQtGuilibrary.Thismusttherefore be included in theproject,whatever thecase, even though you do notuse agraphical interface. 10 Theobvious idea of reducing thesizeto24bitsisabadone,because modern processors usually processatleast32-bit words anyway.Inthe worst case,speed would be reducedbycorrespond- ingspecial adjustments.

297 10 TheGraphics Library“Arthur”

vertcolorsthathavealreadybeen converted back from theRGB32-Premultiplied format, thecolor deviation willbecomeclearlyvisible at some stage.Because of itsadvantagesinspeed,the ARGB32-Premultipliedisneverthelessthe preferred formatofthe RGB32 familysupported byQImage. Alternatively,you can useacolorpalette. In asimilarwayto thepaletteofa Painter,onlyselected colors areavailable here.Withthe QImage- Indexed8 format (QImage::Format_Indexed8),an8-bit indexis available for colors, which means that thereisspacefor amaximum of 256 colors on thepalettesimultaneously.Be- foreyou can workwiththisformat, you mustspecifythenumber of colors in the palette, usingsetNumColors(), withanumber between 1and 256. Thecorrespond- ingquerymethod, numColors(), returnsavalid value onlyfor colorpalette-based formats;otherwise, it returns0. To setthe colorpalette, theAPI provides twopossibilities. setColorTable() allows thecomplete palettetobecovered withavectorconsistingofRGB colordetails (QVector). In addition to this,setColor() allowsindividualpalettecolorsto be defined.Thismethod expectsthe positionasaninteger andthe colorasQRgb as itsparameters. Monochromeimagesare saved byusingQImage::Format_Mono. When this hap- pens,each bit represents apixel,wherebythe most significant bit comesfirst. In contrast,QImage::Format_Mono_LSB saves monochrome imageswiththe least significant bit first. AQImage can be converted to anotherformatvia theconvertToFormat()method. This method expectsafirst parameter specifyingthe newformat, andfor thesec- ondparameter it requires more details on theconversion,which maysometimes result in losses. If aQImage becomesinvalid,its formisalsoinvalid,and format()returnsQ- Image::Format_Invalid.

10.7.2Reading outPixelsLinebyLine

If you wanttoperformcomplexoperationsonimages, you musthaveaccess to everysingle pixel.For this purpose, QImage provides thescanLine() method, which returnscolor information of thelinepixel bypixel,asanarrayof unsignedchars. Since an unsignedchariseight bitsinsize, each individualpixel is represented by fourarrayentries. When this is done, however,the byte order gets in thedeveloper’sway:Some systemsarrange bytes from left to rightsothatthe byte withthe lowestvalue lies at thelowestmemoryaddress.These architectures, which includethe x86 family,are referred to as littleendian .For othersystemsthe oppositeisthe case: Theystorethe byte withthe highestvalue at thelowestmemoryaddress.This

298 10.7 QImage

species, the bigendian system, 11 includes thePowerPC processor, which is used in awiderange of IBMLinuxserversand in manyMacintoshcomputersand laptops. Thecolor details of an image four pixelsinsizecan thereforebearrangedintwo variations, dependingonarchitecture on which theimage is stored as aQImage: BBGGRRAABBGGRRAABBGGRRAABBGGRRAA (littleendian) AARRGGBB AARRGGBB AARRGGBB AARRGGBB(big endian) In ordertoimplement colordetails without taking into account thebyte order, Qt uses asmall trick: It defines thetype QRgb. This is nothingmorethana32-bit integer that stores thealpha channeland thethree colorchannels in theform 0xAARRBBGG. When converting via reinterpret_cast, Qt takesupthe un- signed charsinthe byte orderofthe platform. Theindividualcomponents of the colorcan nowbe safelyaccessedvia QRgb::qRed(), QRgb::qGreen(), QRgb::qBlue() andQRgb::qAlpha(), which also return valuesfrom0to 255. Thefollowingexamplereads outanimage linebylineand runs throughthe RGB values. Theresultisalsosaved to aQImage. Thecoreofthe program is therotateRgb() method. This creates anewQImage object of identical sizeand identical colorformat. Then it opens thesamearrayin both files andreinterprets thecharactersasaQRgbarray.The number of rowsare defined bytheheightofthe image,the number of columns bythewidth.For each pixel wecan nowread outall colorvaluesseparately,and recombinethemaswe please.The image processedinthiswayis then returned:

// rotatergb/main.cpp

#include

QImage rotateRgb(const QImage &in) { QImage out(in.size(),in.format()); for(intline =0; line (in.scanLine(line)); QRgb * outPixels=reinterpret_cast(out.scanLine(line)); for(intpos=0; pos

11 Theterms bigendian and littleendian owetheir origin to an analogyin astoryfrom Gulliver’s Travels, in which twostatesare at war over which endofaboiledegg should be choppedoff. Thesimilarlyheated discussionsonwhatisthe best byte ordernowseem to be running in favor of big endian architectures. Bigendianisisalsoknownas NetworkByteOrder,asitisusedto transportdataacrossthe Internet.

299 10 TheGraphics Library“Arthur”

returnout; }

In themain() function weload thereference image,haveitrotated,and then dis- playthedistorted image in alabel.Thenwerepeatthe procedureand obtain an image that is still distorted.Onlyafter thethird swap arethe colors againinorder. Allthree labels areshownnexttoeach otherinFigure10.8.

// rotatergb/main.cpp (continued)

intmain(intargc, char * argv[]) { QApplication app(argc, argv); QImage img("qt.png"); QLabel rgb; img =rotateRgb(img); QLabel brg; brg.setPixmap(QPixmap::fromImage(img)); img =rotateRgb(img); QLabel grb; grb.setPixmap(QPixmap::fromImage(img)); img =rotateRgb(img); rgb.setPixmap(QPixmap::fromImage(img)); rgb.show(); brg.show(); grb.show(); returnapp.exec(); }

Figure 10.8: Oneimage,three colorarrangements: Ourtestapplication hasswappedthe RGB channelsaround.

10.8SVG Support

Since version 4.1, Qt 4has supported the scalablevectorgraphics format, in short, SVG. It is beingofficiallypublishedbytheW3consortium, which is also responsible

300 10.8 SVGSupport

for theHTMLand CSSstandards.SVG describestwo-dimensionalvectorgraphics andisbased on XML, in contrast to manyestablishedvectorgraphicsstandards. ThelastSVG version to be publishedbytheW3C was version 1.1, andthe SVG working committee of theW3C is preparingversion 1.2, which currentlyhasthe status of aworking draft. To guarantee itsuse on mobile platforms,the W3Cadditionallypublishedso-called profiles withareducedfunctionality, SVGBasic and SVGTiny. 12 Qt 4implementsSVG-TinyandSVG-Basic in versions1.1 and1.2,but currently supports neitherECMA script (often referred to as JavaScript) norother graphic manipulationsvia the Document Object Model (DOM). Qt combines theSVG classesinaseparatelibrarycalledQtSvg. To usethem, you mustfirstextendthe .pro fileasfollows:

QT +=svg

Theinclude instructioncorresponds to thelibraryname:

#include

So far,Qthas nothandled SVGfilesvia QPixmap. Insteaditmakesthe rendering APIdirectlyavailable,under thenameQSvgRenderer. In addition thereisawidget, QSvgWidget, which can directlydisplaySVGimages(belowwecreateaninstance that displaysthe filefile.svg):

QSvgWidget * svgw=newQSvgWidget("file.svg");

QSvgWidgetknowsthe load()slotintwovariations. Thefirstone expectsafilename as aQString,similartothe constructorinthe aboveexample. Thesecondone is passedthe contents of an SVGfile as aQByteArray.Nocorresponding constructor exists for this. Anothermethod that distinguishesQSvgWidgetfromanormal QWidgetisthe ac- cess function renderer(), which returnsapointer to theQSvgRendererobject used for thewidget. QSvgRendereristhe classresponsible for actual rendering. If QSvgWidgethas loadedanimage,itfindsout aboutits sizeHint(), which matches theimage sizeproposed in theSVG file, withQSvgRenderer::defaultSize(). Without aloadedimage,itinherits itsbehaviorfromQWidget::size(). TheQSvgRendereritselfprovides an extensiveAPI for checking imagesand anima- tions. In addition to theload()slot(which is available in QSvgWidgetand QSv- gRenderer),there is an additionalrender()slot, which expectsaQPainter object as aparameter.Thisenablesthe redrawingofthe widgetbythePainter passedon,

12 Seehttp://www.w3.org/TR/SVGMobile/.

301 10 TheGraphics Library“Arthur”

if you wanttouse therenderobject directly.Thisisalwaysnecessaryfor widgets, for example, if apaint eventoccurs. QSvgWidgetreactstothissomethinglikeas follows:

void QSvgWidget::paintEvent(QPaintEvent * ) { ... QPainterp(this); renderer->render(&p); }

renderer here is theQSvgRendererinstance. TheQSvgRenderer::animate()method checks whether theloadedSVG filecontainsanimated elements.Ifthisisthe case, animationDuration() returnsthe length of theanimation as an integer value in sec- onds.InQt4.1,however,thismethod is notfullyimplemented:Italwaysreturns 0.framesPerSecond() reveals thenumber of frames persecond. Thestandardplay- back speed is 30frames persecond. setFramesPerSecond() changesthisvalue,thus slowingdownorspeedingupthe animation. If load()loads an SVGfile withanimated elements,QSvgRendereremits thesig- nalrepaintNeeded() according to thedetails specified in framesPerSecond(). The currentframe of an animationisrevealedbycurrentFrame(). It is defined with setCurrentFrame().

10.9Printingwith QPrinter

TheQPrinter classisresponsible for printing in Qt. Like QPixmaporQImage,itisa QPaintDevice, buthas several interesting specialfeatures. So that theprintingprocess can begin, thecurrent Painter mustexplicitlyconfirm that it hasfinished itswork, withend(). In addition you mustcallthe QPrinter method newPage() for each newpage,including thefirstone.The Painter,which works on theQPrinter instance,willthenhaveafree page available again. Some parameters, such as thepage orientation(portraitorlandscape mode), can onlybe changed before thePainter registerswiththe QPrinter object,thatis, back in the QPainter constructor. Thereisalsothe QPrintDialog class, enablingavarietyof settingstobemade at theprinter.Under Windowsand MacOSXtheclass showsthe printdialogof thesystem;otherwise, it uses aseparatedialog. Theusercan manipulate allthe settingsinthe dialog. We wanttotake acloserlook at theseclasses, usingasanexampleasmallprogram to create screenshots(Figure 10.9 on page 307).Thishas aslotthatprepares the acquisitionofthe screenshot,another slot that retrieves thescreenshot,and athird onetoprint it out.

302 10.9 Printing with QPrinter

In addition it hasthe previewLabelmembervariable,pointingtoaQLabelwhich displaysthe last screenshot in previewsize, as wellasascreenshot member,speci- fyingaQPixmapwiththe screenshot in full resolution:

// screenshot/grabdialog.h

#ifndef GRABDIALOG_H #define GRABDIALOG_H #include #include class QLabel; class GrabDialog :public QDialog {

Q_OBJECT public: GrabDialog(QWidget * parent=0);

protected slots: void prepareGrabDesktop(); void grabDesktop(); void printScreenshot();

private: QLabel * previewLabel; QPixmapscreenshot; } ; #endif // GRABDIALOG_H

In theconstructor weset up atable layoutintowhich weplace thepreviewLabel so that it takesuptwocolumns.Twobuttons,one to startthe screenshot andone to printitout,are placed onerowbeneaththis, each in theirowncolumn. So that thepreviewlabelisalwaysvisible,wefixit to 300 × 200 pixels:

// screenshot/grabdialog.cpp

#include #include "grabdialog.h"

GrabDialog::GrabDialog(QWidget * parent) :QDialog(parent) { QGridLayout * lay=newQGridLayout(this); previewLabel =newQLabel; previewLabel->setFixedSize(300,200); lay->addWidget(previewLabel, 0,0,1,2); QPushButton * screenshotBtn=newQPushButton(tr("&Screenshot!")); QPushButton * printBtn= newQPushButton(tr("&Print"));

303 10 TheGraphics Library“Arthur”

lay->addWidget(screenshotBtn, 1, 0); lay->addWidget(printBtn, 1, 1); connect(screenshotBtn, SIGNAL(clicked()),SLOT(prepareGrabDesktop())); connect(printBtn, SIGNAL(clicked()),SLOT(printScreenshot())); grabDesktop(); setWindowTitle(tr("Screenshot")); }

We nowconnect theclicked()signalofthe Screenshot!buttontothe slot that prepares thescreenshot,and thesignalofthe Printbuttontothe slot that enables theconfiguration of theprintingparameters. Then wecallgrabDesktop(), which retrieves ascreenshot from thecurrent screen at thetimethe program is started. It is intendedmerelyas asubstituteimage for thepreviewLabel.

10.9.1Digression:Making Screenshots

Because screenshot programsare normallynotintendedtorecordthemselves,they should be hiddenasmuchaspossible during thescreenshot itself.Todothiswe callhide(), directlyfollowed bya singleshot timer , 13 which starts grabDesktop():

// screenshot/grabdialog.cpp (continued)

void GrabDialog::prepareGrabDesktop() { hide(); QTimer::singleShot(500,this,SLOT(grabDesktop())); }

The500-milliseconddelayis intendedtogivethe operating system achance, before theactualscreenshot is made,toadjustanypossible artifacts that mayhavebeen causedbythewindowsuddenlydisappearing. Buthowdo weaccess acopyof theentirescreen?AlthoughQPixmapprovides the static method grabWidget(), it can record onlyindividualwidgets in thecurrent application. Luckilythereisalsothe grabWindow() static method, which expects notapointer to awidget, butawindowID.The useofthese IDsisportable in principle; nevertheless, thedocumentationstronglywarns againstmaking certain assumptionsabout theIDs. Information on thecurrent desktopisprovided bytheQDesktopWidgetclass.The QApplicationinstancealreadyprovides an object of this class, which wecan im- mediatelyaccess. ThewindowID for theentirecurrent desktopcan be obtained via theQWidgetmethod winId(), appliedtothe currentscreen,which is returned

13 In contrast to normal timers,asingleshot timerannounces itself onlyonce,not at fixed inter- vals.

304 10.9 Printing with QPrinter

byQDesktopWidget::screen(). screen() returnsaQWidget, which you can unfortu- natelynotscan directlyusinggrabWidget(), butwhich insteadhas thesizes of the currentdesktop as wellasits windowID. ButwithgrabWindow() wecan obtain thescreenshot andstore it in screenshot.A pixmapscaled downtothe sizeofthe labelisalsoincludedinthe previewLabel. In addition wesavethe screenshot on theharddrive, to be more precise, in the currentworking directoryof theapplication, andthendisplaythewindowagain:

// screenshot/grabdialog.cpp (continued) void GrabDialog::grabDesktop() { QDesktopWidget * w=qApp->desktop(); screenshot=QPixmap::grabWindow(w->screen()->winId()); previewLabel->setPixmap(screenshot.scaled(previewLabel->size())); screenshot.save("screenshot.png","PNG"); show(); }

10.9.2PrintinganImage File

Nowweare readyto print: To do this wefirstinstantiate aQPrinter object.Since wewanttoprint an image,werequire ahighresolution. Forthispurposeitwould be good to passthe QPrinter::HighResolution parameter to theconstructor.But duetothe immenseamount of memoryneeded,thisprecautionarymeasurewill be of no help to us under Linux.Thisislessthe fault of Qt than that of theLinux memorymanagement system.For such images, even on systemswith512 MB of main memory,itcontinuesswappinguntil thesystem simplyhangs. Forthis reason wewillstick to thestandardresolution, even if it producessomewhatpoorer results:

// screenshot/grabdialog.cpp (continued) void GrabDialog::printScreenshot() { QPrinterprinter; printer.setOrientation(QPrinter::Landscape); QPrintDialog dlg(&printer,this); if (dlg.exec() == QDialog::Accepted) { printer.newPage(); QPainterp(&printer); QPixmapresized =screenshot.scaledToWidth( printer.pageRect().width()); p.drawPixmap(0,0,resized); p.end(); } }

305 10 TheGraphics Library“Arthur”

In thenextstep weinstantiate aprint dialog andstart it withexec(). In this way theuserhas theopportunityto change nearlyallpreviouslysetprintingoptions as he pleases. Thedialogisbased on thecurrent QPrinter settings: theorientation is alreadysettoLandscape,for example. If theuserhas endedthe dialog byclicking Ok,the actualprintingprocess be- gins:Weinstantiate anewQPainter,which operates theprinter.Thenwescale thescreenshot so that it fitsexactlyonto thepage—normallyit is slightlylarger— dependingonthe resolution.Asahelp in orientation, weuse pageRect() to do this: This value alreadytakesintoaccount anypossible page marginsalreadyset. We nowdrawtheresized pixmapmatchinginsize, withdrawPixmap(), to drawon the printer.Withp.end(), wesignalthatour workiscomplete, andQPrinter sendsour jobtoaprinter.

10.9.3GeneratingPDFs

Since Qt 4.1, QPrinter hasalsobeen able to generate PDFfiles. To write ourimage to aPDF file, just thefollowingcode is needed:

QPrinterprinter; printer.setOutputFormat(QPrinter::PdfFormat); printer.setOutputFileName("out.pdf"); printer.newPage(); QPainterp(&printer); QPixmapresized =screenshot.scaledToWidth( printer.pageRect().width()); p.drawPixmap(0,0,resized); p.end();

10.9.4The Test Application

Thetestroutine for thedialoginstantiates aQApplicationobject andadialog. Insteadofsending it into aseparateeventloop withexec(), wedisplayit normally withshow() (Figure10.9), andafterwardenter theglobaleventloop. This wayis idealifyou need neitherareturn value noradialog that behaves modally:

// screenshot/main.cpp

#include #include "grabdialog.h"

intmain(intargc, char * argv[]) { QApplication app(argc,argv);

306 10.10 ComplexGraphics

GrabDialogdialog; dialog.show(); returnapp.exec(); }

Figure 10.9: Thescreenshot program afterit starts

10.10 Complex Graphics

Thepossibilitiesthatare offeredbyQPainter can be skillfullycombined,essentially usingthree techniques:clipping,Painter paths,and thecompositionmodes.

10.10.1Clipping

With clipping,aQPaintDeviceiscut off, withthe help of afigure,sothatitcan be seen onlyinside theboundaryof thefigure.Thistechniqueisdemonstrated in Figure 10.10,inwhich thetiledpatternisrestricted byatriangle. In thecorresponding code wecreateaQWidgetsubclasscalledPaintWidget. Apart from theemptyconstructor, this just hasastatic sizeHint():

// clipping/paintwidget.h

#include class PaintWidget:public QWidget { Q_OBJECT public: PaintWidget(QWidget * parent=0);

virtualvoid paintEvent(QPaintEvent * ); virtualQSizesizeHint() const { returnQSize(200,200);} } ;

307 10 TheGraphics Library“Arthur”

Figure 10.10: Atriangularpolygon restrictsthe Painter.

In theconstructor weinitializethe parentclass,whereas in paintEvent()weinstan- tiateaQPainter on thewidget. Therewethenconstruct aQPolygon withthree points:

// clipping/paintwidget.cpp

#include #include "paintwidget.h"

PaintWidget::PaintWidget(QWidget * parent) :QWidget(parent) { }

void PaintWidget::paintEvent(QPaintEvent * / * ev * /) { QPainterp(this); QPolygon poly; poly<< rect().topLeft(); poly<< QRect(rect().center().x(),rect().bottom()); poly<< rect().topRight(); p.setClipRegion(poly); painter.drawTiledPixmap(rect(),QPixmap("qt.png")); }

TheQPolygon classisbased on QVectorsothatwecan add newpoints withthe streamingoperators. We select them so that theyopenupatriangle between thetwotop corner pointsand thelower midpoint,and setthe polygon created in this wayas a clip region. We nowpaint atiledpatternover theentirewidgetbackground.But onlythepart inside thetriangleisvisible, as showninFigure10.10.

308 10.10 ComplexGraphics

10.10.2Painter Paths

Thesecondtechniquethatplaysarole in connectionwithcomplexgraphicsis Painter paths.The QPainterPath classcan combineinstances of allprimitivege- ometryclassesintoafigure that can be as complexas you want, anditcan also includeBeziercurves.

Figure 10.11: Painterpaths allow flexibleforms with gradients.

With this technique, figurescan be implemented as showninFigure10.11. These arecreated withthe followingsourcecode:

// painterpath/paintwidget.cpp

#include #include "paintwidget.h"

PaintWidget::PaintWidget(QWidget * parent) :QWidget(parent) { } void PaintWidget::paintEvent(QPaintEvent * / * ev * /) { QLinearGradientgradient(rect().topLeft(),rect().bottomRight()); gradient.setColorAt(0,Qt::yellow); gradient.setColorAt(1, Qt::white);

QPainterPathpath; path.cubicTo(rect().topLeft(),rect().bottomLeft(), rect().bottomRight()); path.cubicTo(rect().topRight(),rect().bottomRight(), rect().bottomLeft());

309 10 TheGraphics Library“Arthur”

QPainterp(this); p.setRenderHint(QPainter::Antialiasing, true); p.drawTiledPixmap(rect(),QPixmap("qt.png")); p.setBrush(gradient); p.drawPath(path); }

Theclass declaration andconstructor matchthose from theprevious example. The differences can be found in thepaintEvent()method. Therewefirstinstantiate aQLinearGradientobject,the colorgradationofwhich should runfromyellowto white acrossthe widgetalong themaindiagonal.With this gradientwewanttofill aPainter pathconsistingoftwocubic Beziercurves. We fixtheshapesofthese to thecornerpointsofthe widgetgeometry. Afterwehavecompleted thesepreparations,weinstantiate aQPainter here as well. In thenextstep weactivateanti-aliasing,drawthebackground,and passthe gradienttothe paintbrush. This nowfills thepixelspainted over byit according to thespecifications of thegradient. If wedrawthepath, it contains thecolor gradient. Thus elegantfigurescan also be implemented via Painter paths.

10.10.3CompositionModes

Finally,QPainter supports theso-called compositionmodes for pixels, according to Porter andDuff.14 In Porter-Duffcompositing, apixel is combined from twosources that each have colordetails withanalpha channel. So-called compositionoperators combine thesesources into anewpixel. In Qt theseoperators alwaysrefer to thecurrent Painter operation (the source)as wellasthe currenttargetpixel on thedrawingdevice(target, or destination). The compositionmode can be changedbeforeeverydrawingoperation.AnewARGB32 pixel is created according to theformula

result color pixels = source color pixels ∗ pdo destinationcolorpixels

Here theoperator ∗ pdo is notanormal multiplication, butstandsfor oneofthe Porter-Duffoperators that combinebothvalueswitheach other. If such an - torisset, this changesthe compositionmode in theQPainter. Forthistofunction,the QImage mustuse theFormat_ARGB32_Premultipliedor theFormat_ARGB32.Ifthisisnot thecase, it is converted accordinglyusingcon- vertToFormat().

14 Namedafter Thomas Porter andTom Duff, who developedthistechniqueatLucasfilmand wentpublic at theSIGGRAPHconference(Thomas Porter andTom Duff, “CompositingDigital Images,” SIGGRAPHVol.88, 1984, pages253–59).The term Porter-Duffalgebra is also used.

310 10.10 ComplexGraphics

Compositing Operators

Anumber of compositionmodes, such as SourceOver and DestinationOver,only revealtheir full efects for imageswithAlpha channels.The SourceOver mode (Q- Painter::CompositionMode_SourceOver)isalsoknownas alphablending:Here thePainter paintsthe source pixel over thedestination pixel.Iftransparencyis switched on,parts of thedestination pixel still shinethrough (Figure10.12links). Theoppositeisthe casewithDestinationOver (QPainter::CompositionMode_Desti- nationOver): Here thesourcepixel becomesthe background,and thedestination pixel accordinglyremainsinthe foreground (Figure10.12onthe right).

Figure 10.12: Acomparisonofthe SourceOver and DestinationOver operators

The source and destination operators also behaveinacomplementarymanner: In thesourcemode (QPainter::CompositionMode_Source), thenewpixel results directlyfrom thesourcepixel.Its contents overwrite theprevious contents of this pixel.

Figure 10.13: Thesourceand destinationoperators letonlyone color through, insteadof combiningthem as theirbrothers SourceOver or DestinationOver do.

311 10 TheGraphics Library“Arthur”

In QPainter::CompositionMode_Destination, thedestination operatorignores the newlydrawnpixel andtakesintoaccount onlythealreadyexisting image content. This is illustrated in Figure 10.13: theleftcircle, drawninsourcemode,completely coversover therectangle andthe transparentarea. On theright,onthe otherhand, no circle is visible,because thetransparentdestination pixelsthemselves dominate thesourcepixels. Bothproceduresbehaveinthe same way,asSourceOver andDestinationOver if source anddestination colors areopaque, that is,iftheyhaveanalpha value of 255.

Figure 10.14: Acomparisonofthe complementary SourceIn and DestinationIn operators

With theoperators discussedbelow,the alphachannelplaysanimportant role. With the SourceIn procedure(QPainter::CompositionMode_SourceIn), thecolor of thenewpixel can be derived from thesource, reducedbythealpha value of thedestination.The reductionofthe alphachannelheremeans ahighertrans- parency, 15 as can be seen on theleftinFigure10.14. Conversely,the newcolorcan be derived in the DestinationIn procedure(QPainter::CompositionMode_Destinati- onIn)fromthe destination, reducedbythealpha value of thesource. Theresultof this mode can be seen on theright in thefigure. The SourceOut and DestinationOut modesfunction in thesamewayas SourceIn andDestinationIn,byusingthe alphachannel. Theonlydifferenceconsistsinthe value to be subtracted:WithSourceOut(QPainter::CompositionMode_SourceOut), theinverse of thesourceissubtracted,and withDestinationOut(QPainter::Com- positionMode_DestinationOut), theinverse alphavalue of thedestination is sub- tracted.Each of theseisdefinedasfollows:

inverseAlpha =255 − alpha

Figure 10.15showsthe circle andthe rectanglefromthe previous examples,painted in SourceOutmode (onthe left)and in DestinationOut mode (onthe right).There

15 An alphavalue of 0makesapixel fullytransparent; 255 makesitfullyvisible.

312 10.10 ComplexGraphics

theright half of thecircleisinvisible, sincethe alphavalue for thesepositions amountsto0. Of course,the SourceOver andSourceInmodesand theircounterpartsDestina- tionOver andDestinationIn can be combined.These aregiven thenames SourceA- top and DestinationAtop. In theSourceAtopmode (QPainter::CompositionMode_SourceAtop),the source is overlayed on thedestination,and thealpha value of thesourceisreduced bythe alphavalue of thedestination.

Figure 10.15: TheSourceOut and DestinationOut operators work like SourceIn and DestinationIn, butuse an inverse alpha value.

In theinverse DestinationAtop mode (QPainter::CompositionMode_Destination- Atop) thesourceoverlaysthe destination. ThedifferencefromSourceAtopbecomes clearinFigure10.16fromthe dominance of thefill colorfor circle andrectangle. In addition,thismode subtractsthe alphavalue of thedestination pixel from that of thesourcepixel.

Figure 10.16: SourceAtop and DestinationAtop combineSourceOver andSourceInaswell as DestinationOver andDestinationIn.

313 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 #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- eratorsinthisexampleare 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

r te ap 11 Ch

Input/OutputInterfaces

Youcan hardlyimagine anyapplicationtodaythat 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: QIODevice Thebaseclass QIODevice andits specializations

QAbstractSocket QBuffer QFile QProcess

QTemporaryFile

QUdpSocket QTcpSocket

QIODeviceisanabstract class, so thedeveloperonlyever instantiates itssubclasses. It represents thelowestcommondenominator of alltypesofinput andoutput operations. Figure 11.1provides an overviewof theinput/output classesthatare basedonQIODevice.

11.1.1 DerivedClasses

QAbstractSocketcannotbeuseddirectlyas 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 sendsdata. 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. TheQBufferclass 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 itsabilityto 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 otherfilesbymistake.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 basedonQIODevice. This classenablesprocessestobestarted withadditional argumentsand permitscommunication withthemvia theQIODe- vicemethods.Inaddition theclass can selectivelymanipulate theenvironment variablesofthe process.

11.1.2Opening I/ODevices

EveryQIODevicemustfirstbeopenedbeforeitcan be used.The open() method is available for this purpose, andits 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().

Flag Value Description Table11.1: QIODevice::NotOpen 0x0000 Deviceisnot open(notauseful detail Parameters of the for open()) QIODevices::open() method QIODevice::ReadOnly0x0001Deviceisopenedfor reading QIODevice::WriteOnly0x0002Deviceisopenedfor writing QIODevice::ReadWrite ReadOnly| Deviceisopenedfor reading and WriteOnly 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 onlywanttoreadfromafile, 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 #include #include 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 FILE 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 notmatch 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 objectslocated on theheapare deleted beforethe program terminates,anexplicit close()isnot necessary,because the QFile destructor doesthisfor 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 thestandardconstructor:

QByteArrayencodedData; QBufferbuffer(&encodedData); buffer.open(QBuffer::WriteOnly); QDataStreamstream(&buffer);

322 11.3 SerializingObjects

To serializethe dataofaQByteArray,therefore, you essentiallyuseaQBuffer. We alreadyknowoneapplicationofthisfromSection 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 without 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 aspecific version using setVersion(), then it will be sent correctlyin this format, even in later Qt versions, andwillbereadable on theother side. In ordertoreaddataintoadatastreamyou useits << operator:

QByteArrayencodedData; QDataStreamstream(&encodedData,QIODevice::WriteOnly); QString text ="Nowcomesatimestamp"; QTime currentTime =QTime::currentTime(); stream<

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 #include #include 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.Wealso includeinformation on theversion in asecondfield(here,1). Then weserialize each dataset in thelist, write it to thefile,and then closeit:

// record/main.cpp

#include #include #include "record.h"

using namespace std;

void saveData(const QList &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 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(); } // Version qint32 version; ds>> version; if (version != 1) { qWarning("Wrong version!\ n"); returnQList(); } QList 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 saveData(). Then wedelete allthe entriesinthe list, so that afterwarditcan be refilledwiththe resultsfrom readData():

// record/main.cpp (continued) intmain() { QList 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 thelastnames 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

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 them 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,because 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 theQApplicationmethod 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 corresponding member variable:

// lineparserprocess/lineparserprocess.h

#ifndef LINEPARSERPROCESS_H #define LINEPARSERPROCESS_H

#include

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 #include #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 readData()wereadinall datatothe final linebreak in thedatastream, with readLine(), linebyline. This procedureissafe,because weknowthat \n is the last charactertobereadinthe output,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 #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 -Rlensures arecursiveand detailedlisting of allfilenamesbeneath thecurrent path. Finallywedisplaythe widgetand enter theeventloop. If you tryoutthisexample, 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

Themodule consists of theQIODevicesubclassesQAbstractSocket, QTcpSocket, and QUdpSocket, andalsocontainsaclassQTcpServer that 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 applicationproxy,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 lookupHost(), which expects aslotasanargument that operates furtheronthe QHostAddressobject passed. Thefollowingcode ...

QHostInfo::lookupHost("www.example.com", this,SLOT(doSomething(const QHostInfo&)));

...thereforelooksupthe 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 (nottobeconfusedwiththe ACKpacket of TCP). Theserver uses theeventloop of thesystem to do this:Each call, therefore, returns immediately,and resultsare delivered via signals, 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

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 #include

#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 #include #include 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 both 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 #include "timeserver.h"

intmain(intargc, char * argv[]) { QCoreApplication app(argc, argv); TimeServerts; ts.listen(QHostAddress::Any,4711); returnapp.exec(); }

336 r te ap 12 Ch

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, QCoreApplicationalwayscreates 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

class TimeServer:public QTcpServer { Q_OBJECT public: TimeServer(QObject * parent=0) :QTcpServer(parent) {} protected: void incomingConnection(intsocketDescriptor); } ; #endif // TIMESERVER_H

338 12.1 UsingThreads

In theimplementationwecreateaseparatethreadfor each incoming connection. After this thread hasdoneits work, it should delete itself,which weimplement byconnectingthe finished()signaltothe deleteLater() slot.Onceitisstarted,the thread prepared in this wayalternates withthe otherexisting threads at regular intervals:

// threadedtimeserver/timeserver.cpp

#include #include

#include "timeserver.h" #include "connectionthread.h" void TimeServer::incomingConnection(intsocketDescriptor) { ConnectionThread * thread=newConnectionThread(socketDescriptor); connect(thread, SIGNAL(finished()),thread, SLOT(deleteLater())); thread->start(); }

TheactualworkisdonebytheConnectionThreadclass.AsaQt thread, it inherits from QThreadand mustthereforeimplement,inaddition to theconstructor,the purelyabstract run() method. run() is aprotected method that is calledbystart(), so it mustimplement thefunctionalityof thethread:

// threadedtimeserver/connectionthread.h

#ifndef CONNECTIONTHREAD_H #define CONNECTIONTHREAD_H

#include #include class ConnectionThread:public QThread { Q_OBJECT public: ConnectionThread(intsocketDescriptor,QObject * parent=0); void run();

private: intsocketDescriptor; } ; #endif // CONNECTIONTHREAD_H

Theconstructor also remainsemptyhere,and weonlyinitializethe socketDescrip- torand initializethe QThreadbaseclass withparent:

339 12 ThreadingwithQThread

// threadedtimeserver/connectionthread.cpp

#include #include #include "connectionthread.h"

ConnectionThread::ConnectionThread(intsocketDescriptor,QObject * parent) :QThread(parent),socketDescriptor(socketDescriptor) { }

In run() wegenerateaQTcpSocketobject andpassthe descriptor to it.Ifsomething should go wrong during this step,weissueawarning andcancelthe procedure:

// threadedtimeserver/connectionthread.cpp(continued)

void ConnectionThread::run() { QTcpSockettcpSocket; if (!tcpSocket.setSocketDescriptor(socketDescriptor)) { qWarning("ERROR:%s",qPrintable(tcpSocket.errorString())); return; } QDateTime time =QDateTime::currentDateTime(); tcpSocket.write(time.toString(Qt::ISODate).toUtf8());

forever { if (!tcpSocket.waitForReadyRead(10 * 1000)) { tcpSocket.write("ERROR:Timeout while waiting forACK\ n"); break; } QString reply=tcpSocket.readAll(); if (reply.isEmpty()) { qWarning("ERROR:%s",qPrintable(tcpSocket.errorString())); break; } elseif(reply== "ACK\ n") break; else tcpSocket.write("ERROR:Invalid command\ n"); }

tcpSocket.disconnectFromHost(); if (!tcpSocket.waitForDisconnected(1000)) qWarning("WARNING:Could notdisconnect");

}

If you need more preciseinformation at this point aboutthe error, you can callthe QTcpSocketmethod error(), which returnsamember of theSocketError enumerator.

340 12.2 SynchronizingThreads

As in theexamplefromSection 11.5.2, wenowobtain thecurrent dateand time andwrite both as aQString to thesocket. Thewrite()callimmediatelyreturns. However,since wewanttoprocess onecallafter anotherinthe thread, weuse the waitFor*methods,which wealreadyused on page 329 for blocking calls. Directlyafter thewrite()weenter into an infiniteloop. Just likeforeach(), forever is akeyworddefinedbyQt, anditcorresponds to while(true).Inthe loop wefirst waitfor datawithwaitForReadyRead(). In theargument wecan specifyatimeout in milliseconds,which weset here to 10seconds. If themethod returnsfalse,the timeouthas been exceeded.Inthiscasewenotifytheclient that wewillnolonger waitand leavethe loop withbreak. Otherwise, datahas been received from theclient andwereaditout.Ifthe returned byte arrayis empty,thatmeans an errorhas occurred,and weclose theconnection to be on thesafe side.Ifits contents correspondtothe ACKstring, weclose theconnection. Otherwise, wecomplaintothe client aboutreceivinganinvalid command andwaitfor anotherreply. Afterissuingthe calltoclose theconnectionwithdisconnectFromHost(), wewait asecondfor possible errors andgiveawarning if oneoccurs. However,the run() method then terminates,sotcpSocketisdeleted andthe connectionisclosedau- tomaticallyat this point anyway. Themain() method of this threadedTimeServer is no different from theone on page 336. We againcreateaQCoreApplicationand aserver object,get it to listen to aport, andstart theeventloop:

// threadedtimeserver/main.cpp

#include #include "timeserver.h" intmain(intargc, char * argv[]) { QCoreApplication app(argc, argv); TimeServerts; if (!ts.listen(QHostAddress::Any,4711)) qWarning("Servercannotlisten on thisport! \ n"); returnapp.exec(); }

12.2SynchronizingThreads

In theexamplejustdisplayed,each thread onlymanipulates thedataonits stack (i.e., itslocal variables).Ifthreads need to sharedata, problems can arisebecause theoperating system can putone running processtosleep andexecuteanother in itsplace at anytime.For instance,athread mightbeinthe middleofchaging

341 12 ThreadingwithQThread

shared datawhenbeing sent to sleep.Inthiscase, thenextthreadwould operate on that inconsistentdata, leading to unpredictable results. To master this situationwemustprotecteach critical sectioninthe code to ensure that onlyonethreadcan executeitatanygiven time.Acritical section is aportion of code that needstorun consecutivelywithout beinginterrupted. Other threads that wanttorun though this code sectionwilljusthavetowait. This exclusiveaccess is guaranteedbytheQMutexclass. Thename mutex stands for mutualexclusion ,and,asits name suggests, if athreadisexecutingasection of code protected byamutex,thenanyotherthreadwanting to enter thecriti- cal sectionmustwaituntil that thread leaves thecritical sectionand releases the mutex. Athreadthathas to waitfor access to shared resourcesshouldbeable to putitself to sleep for awhile,without thesystem havingtocontinuallykeep track of its status ( busy-waiting). Forthispurpose, thereisthe QWaitCondition class: It can be used to putathread to sleep until acertain conditionoccurs.

12.2.1The Consumer/ProducerPattern

Thetwoprinciplesdescribedaboveare puttogood useinthe so-called con- sumer/produceruse-case ,inwhich aproducerthreadmakesdataavailable for processing byaconsumerthread. To movedatafromone thread to theother,the producerplacesitinaqueue, from which theconsumertakesthemout. As asharedresource, thequeue mustbeprotected byamutexbeforeitisaccessed byeither theproducerorthe consumer. If theconsumerworks more quicklythan theproducer, it is possible that thequeue willbecomeempty.Ifthe producerworks faster than theconsumer, thequeue mayfillup. In either case, oneofthe two threads hastosleep:the consumeruntil more dataare available,orthe producer until thequeue hasfreespace. We implementthisscenariobelowusingonlyQt tools. To do this wefirstdeclare afewglobal variables: As the datastructure for theg_queuequeue in which we saveinteger data, weuse theQQueueclass,explainedinmoredetail in AppendixB on page 404. This contains themethods enqueue(), which places adatum into the queue, anddequeue(), which reads outthe oldest datum. Theinteger g_maxlenspecifiesthe maximum number of elements in thequeue, andthe mutexg_mutexwill protectthe code sections that accessthe queue. The wait condition g_queueNotFullshouldwake thesleepingproducerthreadwhen thequeue hasfreespace. Likewise, g_queueNotEmptywakesthe sleepingcon- sumerwhenthere areelementsavailable in thequeue:

// producerconsumer/producerconsumer.cpp

#include

342 12.2 SynchronizingThreads

#include

QQueueg_queue; intg_maxlen; QMutexg_mutex; QWaitCondition g_queueNotFull; QWaitCondition g_queueNotEmpty;

First, wewilllook at theproducer. Therun() method entersaninfinite loop in which it alternates between producing amessage andthengoing to sleep for threesec- onds.The actualfunctionalityis implemented here in produceMessage(). We use themutexto protectsectionsofcode that accessg_queuebycallingthe mutex’s lock()method immediatelypriortothe sectionand itsunlock()method immedi- atelyafter:

// producerconsumer/producerconsumer.cpp (continued) class Producer:public QThread { public: Producer(QObject * parent=0) :QThread(parent) {}

protected: void produceMessage() { qDebug() << "Producing..."; g_mutex.lock(); if (g_queue.size() == g_maxlen) { qDebug() << "g_queueisfull, waiting!"; g_queueNotFull.wait(&g_mutex); }

g_queue.enqueue((rand()%100)+1); g_queueNotEmpty.wakeAll(); g_mutex.unlock(); }

void Producer::run() { forever { produceMessage(); msleep((rand()%3000)+1); } ; } } ;

In thecritical sectionwecheck whether thereisstill room in thequeue.Ifthisis notthe case, thewaitcondition comesintoforce.Since themutexmustnot be blockedduringthe waiting period, wehandittobelooked after bytheg_mutex

343 12 ThreadingwithQThread

method. This unlocks themutex,provided thethreadiswaiting.Inconsequence, you need to ensure that thereisroom in thequeue if you wanttowake up the thread, andthenwake up theconsumerthreadvia therelevantwaitcondition object. If thethreadhas been woken,orifthe queueinthiscyclewas notfull, weadd arandomnumber between 1and 100.Since thereisnowat leastone element in thequeue,wecan wake Consumerthreads that havegonetosleep if thekey was emptybeforethe enqueue()call, andwaitfor achangeinthe waitcondition g_queueNotEmpty.Nowwenolongerneed to accessg_queue, so wecan unlock themutex. We leavethe otherside, theconsumer, to sleep alittlelongerinrun() than the producer, to obtain afullqueue.Tolockthe mutexin this caseweuse aQMu- texLocker, which simplifies this task.The constructorofthe classcalls up lock(), while itsdestructorreleasesthe mutexwithunlock(). In this wayit is outofthe question that you couldforgetanunlock(), thus provokingadeadlock .Insucha case, neitherthreadcan anylonger getthrough to thecritical sections protected bythemutex—thenthe program hangs. Nowtheconsumerfirsttests to seeifanyentrieshavebeen stored in thequeue.If this is notthe caseitgoestosleep,but in contrast to theproducerthread, it uses thewaitcondition g_queueNotEmpty.Ifthe thread is woken up againafter this or if thequeue was previouslynotempty,weremoveanelement from thequeue and then wake anyproducerthreads that mightbesleepingand waiting for data:

// producerconsumer/producerconsumer.cpp (continued)

class Consumer:public QThread { public: Consumer(QObject * parent=0) :QThread(parent) {}

protected: intconsumeMessage() { qDebug() << "Consuming..."; QMutexLockerlocker(&g_mutex); if (g_queue.isEmpty()) { qDebug() << "g_queueempty,waiting!"; g_queueNotEmpty.wait(&g_mutex); } intval=g_queue.dequeue(); g_queueNotFull.wakeAll(); returnval; }

void run() {

344 12.3 Thread-dependent Data Structures

forever { qDebug() << consumeMessage(); msleep(( rand()%4000 )+1); } } } ;

To testthe consumer/producerscenario, wewillwrite asmall main() function in which werestrictthe number of elements in thequeue to ten,createaproducer andaconsumer, andstart them both:

// producerconsumer/producerconsumer.cpp (continued) intmain() { g_maxlen =10; Producerproducer; Consumerconsumer; producer.start(); consumer.start(); producer.wait(); consumer.wait(); return0; }

Since start()returnsimmediately,but wedonot enter into anyeventloop, we waitfor theend of therespectivethread, withwait(). Since both threads never return from run(), theprogram mustbeended at this point from theoutside,using ✞ ☎ ✞ ☎ ✝ Strg ✆+ ✝ C ✆,because thereturn statementwillnever reach theprogram.

12.3Thread-dependent DataStructures

If you wanttostore datainathread context, thesolutionistobefound in the QThreadStorage template class. Because of compilerlimitations it can onlytake in pointer-based objects, which is whydata structures stored in it mustbeallocated usingnew. Asmall exampleshouldexplainthe commonusage of this class: Assume we wanteach thread to maintain itsownlistwithtimestamps. Since thecontentof QThreadStorage hastobeapointer,wedeclare theclass as QThreadStorage*>:

// threadstorage/storingthread.cpp

#include #include

345 12 ThreadingwithQThread

#include

class StoringThread:public QThread {

private: QThreadStorage* >storage;

public: StoringThread(QObject * parent=0) :QThread(parent) {}

protected: void StoringThread::run() { forever { if (!storage.hasLocalData()) { storage.setLocalData(newQList); qDebug() << objectName() << ":Creating list." << "Pointer:"<< storage.localData(); } storage.localData()->append(QTime::currentTime()); qDebug() << objectName() << ":" << storage.localData()->count() << "datescollected"; msleep((rand()%2000)+1); } ; } } ;

In run() wecheck in each thread, first withhasLocalData(),whether QThreadStorage hasalreadybeen initialized here.Ifthisisnot thecase, wecreatethe list. Nowwecan insert thecurrent time in each loop pass. Thedebug output shows theamount of datacurrentlycollected bythis thread. To be able to differentiate between theresults of thethreads,weselectarandom waiting time between 1and 2,000 milliseconds beforeweagainenter theloop. Nowtheexecutionsequenceof thethreads hasbeen moved to arandomone. Nowwewillwrite asmall testprogram withthree threads.Theyareall given names—also awayto distinguishtheminthe debugoutput. Then westart them andensure, withwait(), that theyremain in theirinfinite loop:

// threadstorage/storingthread.cpp (continued)

intmain() { StoringThreadthread1; StoringThreadthread2; StoringThreadthread3; thread1.setObjectName("thread1"); thread2.setObjectName("thread2");

346 12.4 UsingSignalsand Slots Between Threads

thread3.setObjectName("thread3"); thread1.start(); thread2.start(); thread3.start(); thread1.wait(); thread2.wait(); thread3.wait(); return0; }

Thedebug output on theconsole showsthateach thread in theQThreadStorage in- stants reallydoesfind adifferent datastructure.Wecan seethisfromthe different address for therespectivepointer each time:

"thread3" :Creating list. Pointer:0x8051b80 "thread2" :Creating list. Pointer:0x8052090 "thread1":Creating list. Pointer:0x80573f0

Even after afewsecondsthere arealsodifferences in theamount of datacollected:

"thread2" :4datescollected "thread1":7datescollected "thread3" :6datescollected

Foreach process,Qtallowsamaximum of 256 QThreadStorage objects. In most cases this is notaproblem. It is more important to knowthat theclass automati- callydeletes thedatafor athreadassoon as this thread is ended.

12.4Using Signalsand SlotsBetween Threads

In Qt 4itispossible to connect signalsand slotsacrossthreads.Thiscan be done thanks to so-called queued connections,which existinQt4in additiontothe traditional direct connections. Bydirect connections wemeanconnections withinathread or process,asyou learnedinChapter 7. With queued connections,possible argumentsofsignals arecopied,and theseare handed over to therecipient thread on thenextthread change.Usually, worker threads emit signalscontainingmessagesinthe formof thearguments, which aretakenupbyaslotinthe main thread. Thereverse is also possible,but then theworkerthreadrequiresits owneventloop (see Section12.5 on page 350). We can demonstratethisprinciple byextendingthe thread variation of theTime- Server examplefromChapter 12.1(page 338) so that theworkerthreads emit messagesthatarriveinawindowin theGUI thread.

347 12 ThreadingwithQThread

To do this weinsertthe followingsignalintothe twodeclarationsofthe worker thread classConnectionThreadand theTimeServer class:

void message(const QString&message);

Nowweadjustthe ConnectionThreadsothatittriggers themessage() signal with an errordescription for everypossible error. In ordertoidentifytheconnection, we obtain thehostnameofthe client,using peerAddress():

// threadedtimeserverslots/connectionthread.cpp

void ConnectionThread::run() { QTcpSockettcpSocket; if (!tcpSocket.setSocketDescriptor(socketDescriptor)) { emitmessage("ERROR:"+ tcpSocket.errorString()); return; } QByteArrayerror; QString peerHostName =tcpSocket.peerAddress().toString(); emitmessage("INFO:"+ peerHostName +"connected."); QDateTime time =QDateTime::currentDateTime(); tcpSocket.write(time.toString(Qt::ISODate).toUtf8());

forever { if (!tcpSocket.waitForReadyRead(10 * 1000)) { error="ERROR:Timeout while waiting forACK"; tcpSocket.write(error+"\ n"); emitmessage(peerHostName+":"+error); break; } QByteArrayreply=tcpSocket.readAll(); if (reply!= "ACK\ n") { error="ERROR:Invalid command: "+reply.simplified() ; tcpSocket.write(error+"\ n"); emitmessage(peerHostName+":"+error); } else break; }

tcpSocket.disconnectFromHost(); }

We nowextendthe TimeServer classsothatitforwards themessage of each thread via itsownmessage() signal:Since this connectioncrossesthreadboundaries, it is aqueuedconnection:

348 12.4 UsingSignalsand Slots Between Threads

// threadedtimeserverslots/timeserver.cpp

#include #include

#include "timeserver.h" #include "connectionthread.h"

TimeServer::TimeServer(QObject * parent) :QTcpServer(parent) { } void TimeServer::incomingConnection(intsocketDescriptor) { ConnectionThread * thread=newConnectionThread(socketDescriptor); connect(thread, SIGNAL(message(const QString&)), SIGNAL(message(const QString&))); connect(thread, SIGNAL(finished()),thread, SLOT(deleteLater())); thread->start(); }

Figure 12.2: Signals overcome the threadbarrier:The logwindow receives itsmessagesvia a queued connection from theconnection threads.

We modifythemain() function so that it nowinstantiates aQApplication—wewant to haveagraphical logwindow,after all. We implementthisasaQTextBrowserand giveitawindowname:

// threadedtimeserverslots/main.cpp

#include #include #include "timeserver.h" intmain(intargc, char * argv[]) { QApplication app(argc, argv); QTextBrowserlogWindow; logWindow.setWindowTitle(QTextBrowser::tr("Log window"));

349 12 ThreadingwithQThread

TimeServerts; QObject::connect(&ts,SIGNAL(message(const QString&)), &logWindow,SLOT(append(const QString&))); if (!ts.listen(QHostAddress::Any,4711)) qWarning("Servercannotlisten on port 4711!\ n"); logWindow.show(); returnapp.exec(); }

Afterwehaveinstantiated theserver weconnect itsmessage() signal to theap- pend() slot.Thisinvolves adirectconnection, sincethe TimeServer is in thesame thread as thelog window.Nowwecan startthe server withlisten() anddisplaythe logwindow.The result after afewconnections is showninFigure12.2.

12.5YourOwn Event Loops forThreads

Each thread can haveits owneventloop andthus make useofclassesthatcan onlyworkinaneventloop, such as QTimer or QHttp.Threads witheventloops mayhaveslots,which function as recipients of queued signals. Furthermore, direct connections also function withinsuchathread-dependenteventloop. To startaneventloop in athread, thelastmethod in run() mustbeexec(). Theloop is thereforestarted withthe same blocking callasinthe caseofQApplication. The eventloop of threads can also be terminated bythequit()slotorthe exit() function. Thesefunction calls correspondexactlyto thoseinQApplication. Belowwewillcreateathread that writes themessagescomingfromthe indi- vidualConnectionThreadinstances to afile.Thisthreadreceives apublic slot for this purpose, calledappend(), which welater connect in themain() method to the message() signal:

// threadedtimeserverslots/loggerthread.h

#ifndef LOGGERTHREAD_H #define LOGGERTHREAD_H

#include #include

class LoggerThread:public QThread { Q_OBJECT public: LoggerThread(const QString&fileName, QObject * parent=0); void run();

350 12.5 Your OwnEvent Loopsfor Threads

public slots: void append(const QString&message);

private: QFile file; } ; #endif // LOGGERTHREAD_H

In theconstructor wespecifythefilename for thefile butdonot openit. In run() weonlystartthe eventloop of thethreadvia exec(). append()opens thefile for each incoming message,writes thecontents of message,together withalinewrap, andclosesthe fileagain. Although this is notveryefficient,wecan assume that ouroutputwillimmediatelyappearinthe logfile:

// threadedtimeserverslots/loggerthread.cpp

#include #include #include #include "loggerthread.h"

LoggerThread::LoggerThread(const QString&fileName, QObject * parent) :QThread(parent) { file.setFileName(fileName); } void LoggerThread::run() { exec(); } void LoggerThread::append(const QString&message) { file.open(QIODevice::WriteOnly|QIODevice::Append); file.write(message.toUtf8()+’\ n’); file.close(); }

Then weinsertthe followinglines in frontofthe ts.listen() callinthe main() method from page 349:

LoggerThreadlogger("timeserver.log"); QObject::connect(&ts,SIGNAL(message(const QString&)), &logger,SLOT(append(const QString&))); QObject::connect(&app,SIGNAL(lastWindowClosed()), &logger,SLOT(quit()));

351 12 ThreadingwithQThread

Thefirstlinecreates aloggerinstance, while thesecondone forwards themessage() signal to theappend() slot.Thisalsoisaqueued connectionthatacceptsthe recipientthread, thanks to theeventloop.

12.5.1Communication viaEventsWithoutaThread-based EventLoop

It is still possible to usethe method employed in Qt 3tosendmessagestoob- jectsinother threads via events.Inthisprocess theQCoreApplication::postEvents() method unloads theeventinthe correctthread, because it is generallyriskyto call aQObject-based object from anotherthreadthanthe oneinwhich it was created. To avoidproblems,you should establishaconnectiontoanother thread onlyvia postEvent(), via queued signal/slotconnections,orvia mutex-protected buffers. HowpostEvent()functionsisdescribedinChapter 7onpage 185.

352 r te ap 13 Ch

Handling XMLwithQtXml

File formats basedonthe Extensible Markup Language (XML) arebecomingmore andmorecommon. In theQtXml module,Qtprovides notone,but twoAPIs for handlingXML files,each of which makesuse of adifferent approach:the Simple APIfor XML(SAX) andthe Document Object Model (DOM). DOM,probablythemorewell-knownrepresentativeofXML APIs,isastandard of theW3C. Here thecontents of an XMLdocument aretransferred to an object model, atree-likestructure,which preciselymirrorsthe structureofthe document. Allmodern web browsers usethe Document Object Model internally,for example, because theycanaccess anyelementwhatsoever of theXML tree andreaditout. Butother applications also useDOM because aDOM tree in memorycan be trans- ferred back to an XMLdocument.But this also meansthatthe DOMisnot suitable for largeXML documents, duetoits notinconsiderable memorymemoryneeds. DOM is specified at various levels, wherebytheQtimplementationcomplieswith thestandards of DOM level 2.1

1 Seehttp://www.w3.org/DOM/.

353 13 Handling XMLwithQtXml

When parsing an XMLdocument,onthe otherhand, SAXsets off various events, for example, each time it comesacrossanopening or closingtag. Each eventis represented byamethod. Theprogrammerhas to reimplementthe methods and can decide which tags or attributes he or shewants to save. Thefact that the resultsinthiscaselie in datastructuresdefinedbytheusermeans that SAXisby design apurelyreading API, in contrast to DOM.Its strengthscometothe forein itsefficientanalysisoflarge documents. ThespecificationofSAX hasnowbeen takenover bytheSAX Project.2 If you wanttouse thefunctionalityof either of thesetwoAPIs,you mustin- struct qmake, withthe followingdirective, to linkthe QtXml module to thecurrent project:

QT +=xml

Classimplementations anddeclarationsinwhich classesfromthe QtXml module aretobeusedcan either usethe classnames as includes,orinsteaduse themeta- includeQtXml:

#include

This chapter willexplainbothAPIs in detail andwilllook at important differences between theguidelines of theW3C andthe SAXProject.Amore in-depth treat- ment of thetwostandards is notpossible at this point,however,due to thehigh complexityof thesubject.

13.1The SAX2 API

We shallfirstturnour attention to theSAX API. It corresponds as faraspossible to thereference implementation of theSAX Project, which is in Java. Thename conventionshavebeen slightlyadjusted byTrolltech, however,tofitinwiththe namesgiven byQt.

13.1.1How It Works

TheSAX APIparsesXML source textonaneventbasis.For example, theparsersets off events whenitcomes acrossatagortext. As apractical examplewewilllook at thefollowingextract from an XML-based XHTMLdocument:

2 Seehttp://www.saxproject.org/.

354 13.1 TheSAX2API

Click here

TheSAX parsertriggers thefollowingevents from this extract:

Starttag found () Starttag found () Endtag found () Starttag found (

) Textfound (Clickhere) Endtag found (

) Endtag found ()

Theparse resultsare caughtbyimplementing certainmethods in handlerclasses. We areinterested here in twoofthe most frequentlyused classes: theQXmlCon- tentHandler,which operates on tagand textevents,and theQXmlErrorHandler, which goestoworkassoon as theparserfindssyntactical errors in theXML. Youneed theQXmlDTDHandler andQXmlDeclHandlerclasseslessoften.Bothtreat (different)events that triggerthe Document Type Definition(DTD) of an XMLdoc- ument.Inaddition you can operate on XMLentitieswiththe QXmlEntityResolver class, whereas themethods of theQXmlLexicalHandlerclass aretriggeredbylexical events.These arenot discussedhere. Thehomepage of theSAX Projectdoeshave adetailedinterfacedocumentation, however. Allmethods of thehandler classesare designed as interfaces,thatis, theyarepurely virtual.Tospareprogrammers theburdenofimplementingeach method andeach handlerindividually,QXmlDefaultHandlerexists.Itinherits from allhandlersand implements theirmethods in such awaythat allevents areignored.Itisnowthe task of theprogrammertointercept theevents he or sherequiresand to process them.

13.1.2ReimplementingaDefault HandlertoReadRSS Feeds

Howyou reimplementadefaulthandler anduse it for yourownpurposes is demon- strated in thefollowingexample, in which wewillwrite aparserfor thewell-known RSSformat.3 We deliberatelydo notevaluate allthe tags.Atypical RSSfile looks somethinglikethis:

3 Theabbreviation standsfor Really SimpleSyndication .Thisformathas been used forsome time to publishblogentries or newsarticlesinXML.

355 13 Handling XMLwithQtXml

Feed title Weblink Short feed description en-en copyrightinformation creation date image link image title Blog URL orlink tonews. Creation time Title of first article Link totoarticle on the webpage Authorof article <email address> Ashort article summary ...

Theroot tag can consistofone or several channels,specifiedbythe tag. Thechannelisseen as theactualnewsfeed. In addition to itscontent, achannelcontainsaseries of descriptivedetails,suchas itstitle or an image.The actualblogornewsentries in thechannelare wrappedin tags.Each item in turn contains thetimeofpublication, atitle,alinkto thecomplete content, thenameofthe authors, andateaser (specifiedwiththe tag),which can containeitherasummaryor theentireentry. OurRssHandlerwillfill aQStandardItemModel(seepage 249) withdataonthe cur- rent feed.Itinherits from QXmlDefaultHandlerand interceptsthe events startEle- ment(), endElement(), andcharacters(), which originatefromQXmlContentHandler, as wellasfatalError()fromQXmlErrorHandler. TheerrorString() function also orig- inates from theerror handler, butthisdoesnot representanyevent:

// rssreader/rsshandler.h

#ifndef RSSHANDLER_H #define RSSHANDLER_H

#include #include #include

356 13.1 TheSAX2API

class QDocumentModel; class QStandardItemModel; class QXmlParseException; class RssHandler:public QXmlDefaultHandler { public: RssHandler(QStandardItemModel * model);

bool startElement(const QString &namespaceURI, const QString &localName, const QString &qName, const QXmlAttributes&attributes); bool endElement(const QString &namespaceURI, const QString &localName, const QString &qName); bool characters(const QString &str); bool fatalError(const QXmlParseException &exception); QString errorString() const;

private: bool rssTagParsed, inItem; QStandardItemModel * itemModel; QString errString; QString currentText; } ;

#endif // RSSHANDLER_H

We requirethe Boolean variablesrssTagParsedand inItem so that wecan remember states:Ifyou look at theRSS format, you willsee that it uses some tagnames in multiplecontexts;for example, titleisusedbothtodescribe thefeed (i.e., the root)and to describe anewsentry(i.e., an ).Weuse thestatusvariablesto differentiate thetwofunctions. currentTexthelps here to collect thetextbetween apairofopening andclosing tags,and errStringsaves an errormessage.The implementation of errorString() willreturn this value if oneofthe methods returnsfalse. In theconstructor weinitializethe member variable itemModel withthe standard modelpassedand allstatusvariableswithfalse.Inaddition weadd headers to the first twocolumns in themodel: Thecontents of the tagshouldlater be found in thefirstone,and thesecondone is reserved for thecontents of <pub- Date>.</p><p>// rssreader/rsshandler.cpp</p><p>#include <QtXml> #include <QtGui> #include <QDebug> #include "rsshandler.h"</p><p>357 13 Handling XMLwithQtXml</p><p>RssHandler::RssHandler(QStandardItemModel * model) { itemModel =model; rssTagParsed =false; inItem =false; model->setHeaderData(0,Qt::Horizontal, QObject::tr("Title")); model->setHeaderData(1, Qt::Horizontal, QObject::tr("Date")); }</p><p>Plausibilitychecks take placeinparticularinthe startElement() method, which is calledassoon as an opening tagisbeing hitbytheSAX parser. This is also respon- sible for appropriatelysetting status variables, together withendElement(), which runs as soon as theparserarrives at closingtags:</p><p>// rssreader/rsshandler.cpp (continued)</p><p> bool RssHandler::startElement(const QString &/ * namespaceURI * /, const QString &/ * localName * /, const QString &qName, const QXmlAttributes&attributes) { if (!rssTagParsed && qName != "rss") { errString =QObject::tr("Thisfile isnotanRSS source."); returnfalse; }</p><p> if (qName == "rss") { QString version =attributes.value("version"); if (!version.isEmpty() && version != "2.0") { errorStr =QObject::tr("Canonlyhandle RSS 2.0."); returnfalse; rssTagParsed =true; } elseif(qName == "item") { inItem =true; itemModel->insertRow(0); } currentText =""; returntrue; }</p><p>Thefirsttwoargumentsinvolvethe treatmentofthe namespace.Since RSSdoes notuse anynamespaces, wecan ignore this.qName contains thenameofthe tag, andthe QXmlAttributes classencapsulates anyexisting attributes. Thefirstlines of themethod containaplausibilitycheck: If thevariable rssTag- Parsed still hasthe value false,asset in theconstructor,and thecurrent tagis not <rss>,there mustbeanerror,because theroot taginthe document mustbe<rss>. Once wecan findthe <rss> tag, weset rssTagParsedtotruelater in thecode.In this waywehaveformulated thecondition “the root node mustbe<rss>,” using</p><p>358 13.1 TheSAX2API</p><p> astatusand twocomparisons.Ifthisisnot thecaseweset an errormessage and return from themethod withfalse. If, on theother hand,wecomeacross<rss>,wefirstcheck theversion number. We onlysupportRSS version 2.0(orasubset of this). We thereforerejectother versionsasapreventivemeasure.Nowit is also time to setrssTagParsedtotrueso that weare notwronglyrejected bythefirstcheck on elements that weparse later. If wecomeacross<item>, then wechangethe status of inItem to true to distin- guishthe contextofthe tags,asdescribedabove. We also add anewlinetothe model, which wewillthenfill withvalues. If anewtagstarts, weshouldalsoemptycurrentText, sincethisvariable should onlycontainthe textbetween apairofstart andend tags. Next, thecharactersmethod is used to read in thedatathatliesbetween apair of startand endtags.Ifthe parserinterprets this textasseveral consecutivetexts, for example, anormaltextand aCDATA sectioninwhich datamaybe enclosed in diamondoperators, without it beinginterpreted as XML, wecombineall thetexts into one. Since no errorcan ariseherefromour perspective, wereturn true in all circumstances:</p><p>// rssreader/rsshandler.cpp (continued) bool RssHandler::characters( const QString &str ) { currentText +=str; returntrue; }</p><p>We insert thetextcollected in this wayin endElement()intothe ready-to-useline of themodel. Againweare notinterested in namespaces, butmerelyin thecurrent tag, which is waiting in theqName variable:</p><p>// rssreader/rsshandler.cpp (continued) bool RssHandler::endElement( const QString &/ * namespaceURI * /, const QString &/ * localName * /, const QString &qName ) { if (qName == "item"){ inItem =false; } elseif(qName == "title"){ if (inItem) { QModelIndexidx=itemModel->index(0,0); itemModel->setData(idx,currentText); } } elseif(qName == "pubDate"){ if (inItem) { QModelIndexidx=itemModel->index(0,1);</p><p>359 13 Handling XMLwithQtXml</p><p> itemModel->setData(idx,currentText); } } elseif(qName == "description"){ if (inItem) { QModelIndexidx=itemModel->index(0,0); QString preview; if (preview.length() >= 300 ) preview=currentText.left(300)+"..."; else preivew=currentText; itemModel->setData(idx,preview,Qt::ToolTipRole); itemModel->setData(idx,currentText,Qt::UserRole); } } returntrue; }</p><p>If wecomeacrossan<item>tag, weleavethe contextofanitem, andwetherefore set<inItem>back to false.Ifweare currentlylookingatthe contents of thetags <title>, <pubDate>, or <description>, wemustbesureineach casethatweare locatedwithinanitem, which is whywealsoneed to checkinItem in thesecases. Since weinsertthe dataintolinezero—after all, weinserted thenewelementinto this lineaswell—wewillspecifythemodelindexin column zeroasthe title. There weset currentText, that is thetextreadin, as thecontentbetween thetags.The same is donewithpubDate, exceptthatweselectthe first column here. We proceed in twowayswiththe descriptionfrom<description></description>. On onehand, wearbitrarilycutoff thefirst300 characters to provideatextpreview in thetooltip. To indicate that thetextcontinues, weattach an ellipsis(...) to it. 4 In addition,weplace datafor thefirsttimeinthe UserRole, in this casethe com- plete contents of <description>. We willuse this later to displaythecontents of thecurrent entryin aQTextBrowser. In thefinalpartofthe RssHandlerimplementation, wetake alook at errorhandling. On page 357 it was brieflymentionedthaterrorsthattriggerthe implementation of ourclass is retrievable for theparservia errorString(). This is whythis method simplyreturnsthe last errordescription,written to thevariable errorString:</p><p>// rssreader/rsshandler.cpp (continued)</p><p>QString RssHandler::errorString() const { returnerrString; }</p><p>4 Since weare in themiddleofanHTMLtag,there is no guaranteethatthe user will actuallysee thethree dots. Aproperfeed reader would havetouse abetteralgorithm to cutthe text.</p><p>360 13.1 TheSAX2API</p><p>This error, as wellasfatal errors that originatefromthe parseritself, andwhich preventthe continuedprocessing of thedocument,sets off acalltothe fatalError() method, butonlyon thefirstparsererror,unlesswereturn true.Events arenot processedfurther after an errorhas occurred:</p><p>// rssreader/rsshandler.cpp (continued) bool RssHandler::fatalError( const QXmlParseException &exception ) { QMessageBox::information(0,QObject::tr( "RSS-Reader" ), QObject::tr( "Parseerrorin line %1, columne %2: \ n%3" ) .arg(exception.lineNumber() ) .arg(exception.columnNumber() ) .arg(exception.message() )); returnfalse; }</p><p>We passthe errorontothe user bymeansofQMessageBox.The parameter excep- tion provides details on theerror that hasoccurred.</p><p>13.1.3Digression:Equipping theRSS ReaderwithaGUI and Network Capability</p><p>Nowourparsercan be built into afeed readerthatusesanHTTP address to down- load an RSSfeed,parse it,and displayit.Figure13.1showshowtheapplicationis constructed:The lineeditwaits for theaddressofthe feed,the contents of which aredisplayed byaQTextViewon theleft-hand page.Onthe rightwesee thearticle selected from thelistinaQTextBrowser.</p><p>Figure 13.1: TheSAX-based RSS readerdisplays the blogs of KDE developers.</p><p>To download thefile from awebserver,weuse theQHttp class, which enables asynchronouscommunication withwebservers. This is oneofthe networkclasses introducedinChapter 11, butwehavenot yet discusseditinmoredetail. We also</p><p>361 13 Handling XMLwithQtXml</p><p> come acrossthe QBufferclass again, where wetemporarilystorethe contents of theRSS file. Later on weneed theinteger jobIdinconnectionwithQHttp.Our windowis basedonQMainWindow,among otherthings, because wewilluse its status bar:</p><p>// rssreader/mainwindow.h</p><p>#ifndef MAINWINDOW_H #define MAINWINDOW_H</p><p>#include <QMainWindow></p><p> class QLineEdit; class QTextBrowser; class QTreeView; class QHttp; class QBuffer; class QModelIndex;</p><p> class MainWindow:public QMainWindow { Q_OBJECT public: MainWindow(QWidget * parent=0);</p><p> protected slots: void readResponse(intid, bool error); void retrieveRss(); void showArticle(const QModelIndex&index); void showRss();</p><p> private: QHttp * http; QLineEdit * lineEdit; QTextBrowser * textBrowser; QTreeView * treeView; QBuffer * rssBuffer; intjobId; } ;</p><p>#endif // MAINWINDOW_H</p><p>In theconstructor wegivethe windowanameand arrangethe subwindowin a table layout. Here wedragthe lineeditover onelineand twocolumns,which can be seen in thefourthand fifth parametersofthe first addWidget()details.We insert thetreeviewandthe textbrowserinthe second line, in thefirstand second columns respectively:</p><p>// rssreader/mainwindow.cpp</p><p>#include <QtGui></p><p>362 13.1 TheSAX2API</p><p>#include <QtXml> #include <QtNetwork> #include "mainwindow.h" #include "rsshandler.h"</p><p>MainWindow::MainWindow(QWidget * parent) :QMainWindow(parent),jobId(0) { setWindowTitle(tr("RSS Reader")); QWidget * cw=newQWidget; QGridLayout * lay=newQGridLayout(cw); lineEdit=newQLineEdit; lay->addWidget(lineEdit,0,0,1,2); treeView=newQTreeView; treeView->setRootIsDecorated(false); lay->addWidget(treeView,1,0); textBrowser=newQTextBrowser; lay->addWidget(textBrowser,1,1); setCentralWidget(cw);</p><p> rssBuffer=newQBuffer(this); rssBuffer->open(QIODevice::ReadWrite);</p><p> http =newQHttp(this);</p><p> connect(lineEdit,SIGNAL(returnPressed()),SLOT(retrieveRss())); connect(treeView,SIGNAL(activated(const QModelIndex&)), SLOT(showArticle(const QModelIndex&))); connect(treeView,SIGNAL(doubleClicked(const QModelIndex&)), SLOT(showArticle(const QModelIndex&))); connect(http,SIGNAL(requestFinished(int,bool)), SLOT(readResponse(int,bool)));</p><p> statusBar()->showMessage(tr("Welcome toRSS Reader!")); }</p><p>Theentirelayoutliesonasimple QWidgetbythenameofcw,which weinsertinto themainwindowas thecentral widget. Finally,wegenerateabufferand openit for read andwrite access. In addition wecreatethe QHttp object.Thisclass works in apurelyasynchronous manner, so thereisnot even thetheoretical possibilityof placingthe object on thestack, which would block processing until an eventispresent.Instead, allcalls immediatelyreturn.Whenthe QHttp object hastreated therequest,itsends outa signal. Forthisreason, wecreatesignal/slot connections at theend,the last oneofwhich is connected withthe QHttp instance.Assoon as theusersets off theinputsinthe ✞ ☎ lineeditwith ✝ Enter ✆,retrieveRss()beginsdownloading thefile.The second andthird connect() calls connect akeyactionoradouble-clicktoanentryin thelistview withthe showArticle()method, which displaysthe corresponding article. Finally,</p><p>363 13 Handling XMLwithQtXml</p><p> weconnect therequestFinished() signal,which triggers QHttp after an operation hasbeen completed,withthe slot wehavewritten ourselves,readResponse(). In retrieveRss()wetransferthe textfromthe lineeditintoaQUrl object.Ittries automaticallyto parse aURL from thetextpassedtoit:</p><p>// rssreader/mainwindow.cpp (continued)</p><p> void MainWindow::retrieveRss() { QUrlurl(lineEdit->text()); if(!url.isValid || url.schema() != "http") { statusBar()->showMessage(tr("Invalid URL:’%1’") .arg(lineEdit->text()); return; } http->setHost(url.host()); jobId=http->get(url.path(),rssBuffer); statusBar()->showMessage(tr("Getting RSS Feed ’%1’...") .arg(url.toString())); }</p><p>If thetextdoesnot yield avalid URL(i.e., isValid() returnsfalse)orifthe scheme (i.e., theprotocol) is not http://,wedonot need to continue,and return without leavingbehindanerror message.Wenowsetthe name of theserver from which wewanttoobtainthe RSSfeed,using setHost(). Thematchinghostnameisalready stored for us byurl.host(). Because of theasynchronousnatureofQHttp,all method calls that workonthe server arearrangedintothe queueand performedone after theother.Each method callreturnsajobID. As soon as ajob hasbeen processed, QHttp emitsthe re- questFinished()signal, thefirstargument of which is thejob ID. Forthisreasonwemake anoteofthe jobID(in themembervariable jobId) for the Getrequest to theserver.Asarguments, theget()method demandsthe pathtothe fileand apointer to aQIODevicewhere it can storethe retrieved file. Finally,we informthe user that weare downloading theRSS feed. In thereadResponse() slot wefetch onlytheresultofthe Getjob.The second parameter specifies whether an erroroccurred during thefile download, perhaps because theserver was notavailable or thepathwas incorrect. If this is notthe case, weprocess thedatavia showRss()and issueathree-second success message in thestatusbar.Otherwise, an errormessage willappear for thesamelengthof time:</p><p>// rssreader/mainwindow.cpp (continued)</p><p> void MainWindow::readResponse(intid, bool error) {</p><p>364 13.1 TheSAX2API</p><p> if (id == jobId) { if (!error) { showRss(); statusBar()->showMessage( tr("RSS-Feed loaded successfully"),3000); } else statusBar()->showMessage( tr("Fehlerwhile fetching RSS feed!"),3000); } } showRss()doesthe actualwork. Here wecreateastandardmodelwithtwocolumns that welater passontoRssHandler:</p><p>// rssreader/mainwindow.cpp (continued) void MainWindow::showRss() { QStandardItemModel * model =newQStandardItemModel(0,2); RssHandlerhandler(model); QXmlSimpleReaderreader; reader.setContentHandler(&handler); reader.setErrorHandler(&handler); rssBuffer->reset(); QXmlInputSource source(rssBuffer); if (!reader.parse(source)) return; deletetreeView->model(); treeView->setModel(model); }</p><p>QXmlSimpleReaderisresponsible for parsing thefile usingthe RssHandlers.Since RssHandlerinherits from QXmlDefaultHandler, andthus from allhandlers, butwe haveimplemented onlythefunctionalityof QXmlContentHandler andQXmlEr- rorHandler, wemustregister theRssHandlerasbothacontentand errorhandler withthe readerobject. As the document source for QXmlSimpleReader, theQXmlInputSourceclass is used, which obtainsits datafromaQIODevice. Butbeforeweinstantiate such an input source,passing on thebufferasanargument at thesametime, wemustset the read position in thebuffertothe beginning of theinternalQByteArraywithreset(), so that thecontentjustwritten can be read out. reader.parse() nowstarts the actualparsing process. If this runs successfully,wefirstdelete anyalreadyexisting modellinkedtothe tree view,and then passour model, equippedwithfresh content, to theview. In thefinalstep wenowneed to implementthe showArticle()slottodisplaythe entryselected in thetreeviewin thetextbrowser. To do this weaccess thedata()</p><p>365 13 Handling XMLwithQtXml</p><p> method of theactivemodel. We obtain theindexof thecurrent entryfrom the argument of theslot. As theroleweselectUserRole, where wepreviouslystored the complete contents of the<description>tag. We nowconvertthis, usingtoString(), from aQVariantback to aQString andpassthistothe textbrowserasHTML:</p><p>// rssreader/mainwindow.cpp (continued)</p><p> void MainWindow::showArticle(const QModelIndex&index) { QVarianttmp=treeView->model()->data(index,Qt::UserRole); QString content=tmp.toString(); textBrowser->setHtml(content); }</p><p>NowourrudimentaryRSSreaderisfinished.The obligatorymain() method instan- tiates QApplication, andthe MainWindowobject displaysthe windowandsets it to an initialsizeof 640 × 480 pixels. Theapplicationthenentersthe eventloop:</p><p>// rssreader/main.cpp</p><p>#include <QtGui> #include "mainwindow.h"</p><p> intmain(intargc, char * argv[]) { QApplication app(argc, argv); MainWindowmw; mw.show(); mw.resize(640,480); returnapp.exec(); }</p><p>This simple examplealreadydemonstrates that SAX2 allowsyou to parse docu- mentswithcomparativelysmalloutlay.But themoreexact thechecks become,the more complexthecode.Ifyou wanttoavoidthiscomplexity,and you onlyprocess smalldocumentsanyway,you should take alook at theDocument Object Model, which followsacompletelydifferent approach.</p><p>13.2The DOM API</p><p>QDom,the DOMAPI of Qt, is averyconvenient wayof accessing XMLfiles. The QDomDocument classhererepresentsacomplete XMLfile.Its setContent()method is capable of generating DOMtrees outofXML files andconverselywriting the contents of aDOM tree to an XMLdocument. TheDOM tree itself consists of DOM elements (QDomElement). Theirstart tags may containattributes.Between thestart andend tag, DOM elements maycontaintext</p><p>366 13.2 TheDOM API</p><p> or child elements.InthiswaytheDOM tree is built up from theXML structure, and itselementsare without exceptionDOM nodes(QDomNodes). QDomNodesknowtheprinciple of parenthood: If theyareinserted in anotherpart of thetree, theyarenot copied,but change theirlocationinthe tree.The node into which aQDomNode is inserted nowfunctionsasits newparentnode.Not everynode mayposesschild nodes, however.Ifyou try,for example, to givea child to an attribute node,the object willinsertthe newnode as asiblingnode of theattribute node.Thisdeviates from theDOM specification,which at this point divergentlydemandsthatanexceptionshouldbethrown. Here ageneral distinctionfromthe DOMspecificationcan alreadybe seen:Qtdoes notuse exceptions to announceerrors, buteitherusesreturn valuesorchooses an alternativebehavior. This is whyit is recommended that you excludecases of error in advance of acallbymaking as manychecks as possible in themethod’scode, andalsocheck return valuesofmethods after calls.</p><p>13.2.1Reading in andProcessing XMLFiles</p><p>ThefollowingHTMLfile is written in XHTML:</p><p><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"lang="en"xml:lang="en"> <head> <title>Title

Example.com Example.net Example.org

We wanttoload this into atestapplicationinaQDomDocument andworkwithit in ordertoobservedifferent aspectsofQDom. Forthispurposeweopenthe file withQFile andreadout itscontents.Thenweinstantiate aQDomDocument and passthe byte arrayread outofthe filetothe QDomDocument withsetcontent(). We useusing namespace std; to simplywrite cout (insteadofstd::cout) to display dataonthe standardoutput.

// xhtmldomparser/main.cpp

#include #include

367 13 Handling XMLwithQtXml

#include

using namespace std;

intmain(intargc, char * argv[]) { QCoreApplication app(argc, argv); QFile file("index.xml"); if (!file.open(QIODevice::ReadOnly)) return1; QByteArraycontent=file.readAll(); QDomDocumentdoc; QString errorMessage; intline, col; if (!doc.setContent(content,&errorMessage, &line, &col)) { cout << "Errorin Line "<< line << ",column "<< col << ":"<< qPrintable(errorMessage)<< endl; return1; }

setContent()parsesthe inputand returnsafalse if thereisaparsererror.If—like us—you wanttolearn more aboutthe error, you passtothisfunction,apart from thebytearray,apointer to aQString andtwointegers. If thereisanerror,the function fills theselastthree valueswiththe problemdescription,aswellasthe relevantlineand column in thedocument.

Figure 13.2: Everyobjectinthe DOMtreegenerated from theXML is a QDomNode as well.

368 13.2 TheDOM API

TheDOM tree parsedinthiswayis showninFigure13.2. We willnowworkwith this.First wereadout thenameofthe document type andthe tagnameofthe root elementinthe document.docType() provides us withthe document type in a QDomDocumentType object.Its name—thatis, thepartwhich stands directlyafter DOCTYPE—is calledhtmlinthiscase. This is revealedtousthrough thename() method:

// xhtmldomparser/main.cpp (continued)

QDomDocumentTypetype=doc.doctype(); cout << "Documenttype: "<< qPrintable(type.name()) << endl;;

QDomElementroot=doc.documentElement(); if (root.hasAttribute("xmlns")) { QDomAttr attr =root.attributeNode("xmlns"); cout << "xmlns:"<< qPrintable(attr.value()) << endl; }

if (root.hasAttribute("lang")) { QDomAttr attr =root.attributeNode("lang"); cout << "lang: "<< qPrintable(attr.value()) << endl; }

QDomNode node =root.firstChild(); while(!node.isNull()) { if(node.isElement()) { QDomElementelem =node.toElement(); cout << "Child of rootnode: "<< qPrintable(elem.tagName()) << endl; cout << "Its text:"<< qPrintable(elem.text()) << endl; } node =node.nextSibling(); }

We obtain theroot elementand saveitasaQDomElement via theQDomDoc- ument method documentElement(). Theattributes of elements areprovided by attributeNode(). In theexampleweextract theattributes xmlns andlang. If the filedoesnot containthem, attributeNode() would return an emptyQDomAttr ob- ject.Thisiswhywecheck whether theelement hasacorresponding attribute at all, usinghasAttribute(). Finally,using value(), weobtainthe value of theattribute. Then wereadout allthe child nodesofthe root element. We usefirstChild() to giveusthe first DOM node (thatis, head),and allthe others (here,justbody) weobtainwithnextSibling(). If this is just an element, weconvertthe node to a QDomElement.Ifwecomeacrossanattribute or comment node,thiswillnot work, of course.Toobtainthe name of aQDomElements, weuse thetagName()method. Thetext()method collectsthe textnodesofanelement andits child elements in aQString.Herewereceivethe textofthe headers from theheadelement,and on

369 13 Handling XMLwithQtXml

thenextloop passthe texts of allthree referenceelements() beneaththe body element. We nowfillthe node variable withthe nextsister nodesand repeat theprocedure. If no more sister nodesare available,thennextSibling()returnsanullnode,which no longer fulfils theloop condition. Theoutputofthe aboveexampletherefore appears as follows:

Documenttype: html Roottag: html Documenttype: html xmlns:http://www.w3.org/1999/xhtml lang: en Child of rootnode: head Its text:Title Child of rootnode: body Its text:Example.comExample.netExample.org

Theconversion of theQDomNodesreturned byfirstChild() andnextSibling()into QDomElementscan be left out, bytheway,ifyou usefirstChildElement()and nextSiblingElement()instead. Theprocedure used here makesparticularsense if you want, for example, to filter outadditional comments(represented byQDom- Comment)ortext(represented byQDomTextand QDomCDATASection).

13.2.2Searching forSpecific Elements

Nowthat wehaveseen howwenavigateinaDOM tree, wewilllook at themethods wecan usetosearchspecificallyfor certainelements. DOM provides theelements- ByTagName()function for this purpose. It expectsthe name of an elementtype.If you callitasamethod of aQDomDocument instance,thenitwilllook throughall theelementsinthe entire document,whereas themethod of thesamenameinthe QDomElement classlooksthrough allthe elements beneaththe elementreceiving themethod call. Bothfunctionsreturn aQDomList. This is not atype definitionfor QListbut is adifferent kind of datastructure,which is specified in theDOM spec- ifications. We can thereforenot processthislistwithforeach(). Insteadweuse a for() loop to iteratethrough thelist, as shownhere:

// xhtmldomparser/main.cpp(continued)

QDomNodeList anchors =doc.elementsByTagName("a"); for(uinti=0; i

370 13.2 TheDOM API

Thenumber of elements is determinedbythelength() method. Beforewecan read outthe attributes from thecurrent DOMnode,wemustconvertitback to an element. TheDOM APIalwaysprovides onlyoneQDomNodeList for lists.The attribute("href")callisashortformfor attributeNode("href").value() andreturns thevalue directlyas aQString.The output for ourexampleaccordinglyappears as follows: http://www.example.com http://www.example.net http://www.example.org

TheQDomNode::childNodes()method returnsall subnodes, also in aQDomNodeList.

13.2.3Manipulatingthe DOMTree

Of course,wecan also insert newelements into thetreeoreliminateexisting ones. To delete aDOM node from thetree, you callthe removeChild() method of thepar- entnode,which can be determinedbyparentNode(), andpassthe node in question to this method as aQDomElement:

// xhtmldomparser/main.cpp (continued)

QDomElementexamplecom =anchors.at(0).toElement(); examplecom.parentNode().removeChild(examplecom);

We nowwanttoconvertthe remaininglinks into anonclickable text, butthe text should still be emphasized,for which weuse a tag. Ideally,wewould there- forechangethe tagnameofthe elementand removethe href attribute withthe followingcode:

// xhtmldomparser/main.cpp (continued)

for(uinti=0; i

Alternativelywecould havecreated anewelement, copied thetext, looked for the parentnode withparentNode(), andfromthere replaced thehreftag usingthe replaceChild() method. As arguments, this expectsthe node to be replaced and then thenewnode. Nextwecreateanewpartialtreeand insert it into theDOM tree.Asabasisfor partialtrees,the classQDomDocumentFragmentcan be used to savetrees that

371 13 Handling XMLwithQtXml

do nothavetocontain well-formedXML.Thismeans that such apartial tree may containseveral direct child elements,whereas in aQDomDocument,one element at themost, theroot element, mayexist. QDomDocumentFragments playaspecial role in methods such as appendChild(), insertBefore(), or insertAfter(): If thesecontain afragmentasaparameter,then theyinsert allits sub-nodes. To create anode (thatis, onebased on aQDomNode subclass),wemustuse oneof thefactorymethods from QDomDocument,which allstart withcreate. Theonly exceptionisthe QDomDocument itself,which wecan instantiatedirectly.Ifyou just instantiateanode without initializingitusing theappropriate factorymethod, then it is considered to be undefined. This behaviorrepresentsasource of errors that is noteasyto detect. In thefollowingcode wewillgenerateafragmentand insert in it an italic element (i). Into this elementweplace atextnode byfirst creating aQDomTextand then appending it to theitalic elementwithappendChild(). After this wecreatean XMLcomment (QDomComment)and insert both thenewitalic elementand the comment into thedocument fragment:

// xhtmldomparser/main.cpp(continued)

QDomDocumentFragmentfragment=doc.createDocumentFragment(); QDomElementitalic =doc.createElement("i"); QDomText text =doc.createTextNode("some linksforyou:"); italic.appendChild(text); QDomCommentcomment=doc.createComment("converted links:"); fragment.appendChild(italic); fragment.appendChild(comment); QDomNode para=doc.elementsByTagName("p").at(0); para.insertBefore(fragment,para.firstChild());

To enter both elements in ourdocument abovethe links,welocatethe first p elementand insert thefragment’scomponents as children, via insertBefore(), in frontofwhatwas thefirstchild until now.

13.2.4The DOMTreeasXML Output

Thetreeobtaineduptothispoint can againbedisplayed byQDocument as an XMLstructure.The methods toString() andtoByteArray() can be used for this.The latter is of particular interestifyou wanttowrite theXML fileback to aQIODevice. Theparameter specifies thenumber of emptyspacesthatshouldbeusedwhen indentingthe XMLstructure.Ifthisismissing,Qtsets theindentdepth to one emptyspace perlevel. In thefollowingexamplewewillwrite thecurrent status of theDOM tree into the fileopenedfor writing,out.xml.Thenweclose this andsendittothe standard

372 13.2 TheDOM API

output withtoString(). In both cases weuse twoemptyspacesper level when indentingthe elements in theoutput:

// xhtmldomparser/main.cpp (continued)

QFile outfile("out.xml"); if (!outfile.open(QIODevice::WriteOnly)) { cout << "Could notwritefile: " << qPrintable(outfile.fileName()) << endl; return1; } QByteArraydata=doc.toByteArray(2); outfile.write(data); outfile.close(); // unicode string representation QString string =doc.toString(2); cout << qPrintable(string)<< endl; return0; }

373

r te ap 14 Ch

Internationalization

Manyprogramstodayareintendedtoreach usersinmanydifferent countries.For this reason it is veryimportant that an applicationcan be modifiedeasilyandflex- iblyto theparticularities of anotherlanguage.One aspect of this is thetranslation of allvisibletexts into thetargetlanguage.The directionoftextflow,onwhich the arrangementofwidgets is based, is also of centralimportance. In this chapter wewillfirsttranslate theapplicationCuteEdit, which wecreated in Chapter 4using thetoolsofQt. In addition wewillget to knowafewuseful classes which,whenusedduringthe developmentprocess,willhelptoavoidproblems later on whentranslating thesoftwaretoanother language.

14.1TranslatingApplications into OtherLanguages

Qt includes several mechanisms to prepareapplicationprogramsfor translation into otherlanguageslater on (see page 50andalsopage 123).

375 14 Internationalization

We nowrepeat once againthe twomostimportant points: Alltranslatable strings in theprogram code mustalwaysbeenclosedbytheQObject method tr(). In ad- dition,variablesinstrings maynever be directlyconcatenated,since it would be impossible to produce thecorrect wordorder in thetargetlanguage,asthe follow- ingEnglish-to-German sentence conversion illustrates:

QString filename ="file.txt"; QString message =tr("Could notsave") +filename;

In theGermantranslation,the wordorder should be “Couldfile.txtnot save,”but theexpression used to constructthe message assumesEnglishwordorder andso cannotproduce thedesired phrase.Todothe translationcorrectly,you need to use placeholders, as shownbelow.

QString filename ="file.txt"; QString message =tr("Could notsave%1.").arg(filename);

Nowtr() is able to translatethissentence withthe correctinverted (relativeto English) wordorder,Could %1 notsave. We willnowexplainhowthis works.

14.1.1Preparing theApplication

TheCuteEditversion used here is different from theone in Chapter 4, in that it uses Englishstrings in thecode.Thisisnot necessarybutitdoesmake sense, as Englishis normallyused as thelinguafranca, allowingexternalprogrammers whose mother tongue is notGermantoworkonthe program. Forthe translationofQt-basedapplications,Qtprovides theprogramslupdate, linguist, andlrelease. Thetranslation processisnot aseparatetask, completely isolated from thecode development, butisintegrated into theQtproject manage- ment.Ifour projectfile up until nowlookslikethis:

#cuteediti18n/cuteediti18n.pro

TEMPLATE =app SOURCES =main.cpp mainwindow.cpp HEADERS =mainwindow.h FORMS =mainwindow.ui RESOURCES =pics.qrc

then allthatismissing is theentryfor TRANSLATIONS,which expectsone or more translationfilesasarguments. Adding translationsupportfor German,French, and Italianwilllook likethis:

376 14.1 Translating ApplicationsintoOther Languages

#cuteediti18n/cuteediti18n.pro(continued)

TRANSLATIONS =cuteedit_de.ts \ cuteedit_fr.ts \ cuteedit_it.ts

Using thelupdatetool,weextract thesefilesfromthe projectsources,the files registered under SOURCES, HEADERS,and FORMS. Thefollowingcommand is suf- ficient to do this:

lupdatecuteediti18n.pro

This extractsall thestrings in thesources that need to be translated.These trans- lation sources arenowavailable in an XML-based format. If newstringsare added during furtherprogram development, lupdatecuteed- iti18n.pro updates thetranslation sources, andtranslators can workonthe new strings.

14.1.2Processing TranslationSources with Linguist

Themostconvenient wayto openand edit translationsources is withthe program Qt Linguist.Thisworkcan be donebypeopleworking independentlyof theQt softwaredevelopers, such as freelance translators. Figure 14.1showsthe main windowof theLinguistafter it hasloadedthe filecute- edit_de.ts. Thecontextdockwindowon theleft-hand page gives an overviewof thetranslation context, anditusuallydisplaysthe name of theclass in which a string appears. If you select acontext, thestrings for translationinthiscontextwill appear. The field in thecenter provides space foranindividualtranslation. Since thereare standardtranslationsfor manycommonplace phrasesand menu items, you can findsuggestions from so-called phrasebooks .Qtprovides such collections of suggestions for manycommonlanguagesunder Phrases → Open Phrasebook.... If theseare loaded, suggestions willappear in thelower-right win- dowif Linguist finds similarities to theword(s) beingtranslated. Untranslated stringsare given abluequestionmark, andtranslated ones an orange question mark.IfLinguistdiscoversaninconsistencyin thetranslation,suchas missing “. ..” in menu items, it places ared exclamation mark in frontand displays theprobleminthe status bar. If you aresatisfiedwithatranslation, you confirm it ✞ ☎ ✞ ☎ with ✝ Ctrl ✆+ ✝ Enter ✆.Itisthengiven agreen checkmark.

377 14 Internationalization

Figure 14.1: Usingphrasebooks, theQtLinguist helps youtofind matching translationsand checks thesefor consistency.

When allstrings aretranslated,the resultsjustneed to be saved:The .tsfile now contains thetranslated strings. Thecommand

lreleasecuteediti18n.pro

creates files from (complete or partial) translationsources in aspecial binaryformat that theQtprogram can use. In ourcasethese arecuteedit_de.qm, cuteedit_fr.qm, andcuteedit_it.qm.

14.1.3Using Translations in theProgram

Loading thecorrect translationwhenthe program starts is thetaskofthe QTrans- latorclass.Itwillsearchfor thetranslation files in theworking directoryof the applicationifitisnot given apathasthe second argument whenitiscalled. To determine thenameofthe translationfile for therespectivesystem environ- ment,weuse QLocale::system(). Thestaticmethod outputsaQLocaleobject with information on thecurrent system locale. Thename()function returnsthe localeto us as astring, consisting of alanguage code andacountrycode in capitals, which

378 14.1 Translating ApplicationsintoOther Languages

for Germanywould be de_DE.Therefore, ourfilename is cuteedit_de_DE,and this is turned into cuteedit_de_de as aprecaution,using toLower():

// cuteediti18n/main.cpp

#include #include "mainwindow.h" intmain(intargc, char * argv[]) { QApplication a(argc, argv);

QTranslatorcuteeditTranslator; filename =QString("cuteedit_%1").arg(QLocale::system().name()); filename =filename.toLower(); cuteeditTranslator.load(filename); app.installTranslator(&cuteeditTranslator);

QTranslatornowlooksfor thefilename according to afixed pattern: Firstitadds .qmtothe file, then it trieswithout this extension.Ifithas still notfound anything, it removes allthe numbersfromthe endofthe name up to thefirstunderscoreor dot,and triesagain. In ourcasethe search sequence would look likethis: cuteedit_de_de.qm cuteedit_de_de cuteedit_de.qm cuteedit_de cuteedit.qm cuteedit

Thealgorithm alreadyfound somethinginthe thirdstep.Through thecountry code,localizationisalsopossible between countries that usethe same language butwithdifferences in vocabularyor usage.Thus en usuallymatchesAmerican English, whereas theapplicationwould make adjustmentsaimed towardthe lan- guage customsofGreat Britain if thelocalewereset to en_UK. If weinclude thetranslation in ourQApplicationinstancewithinstallTranslator(), theapplicationshowsatranslated user interfaceafter show() hasbeen called. In addition weinstall aQTranslator,which contains allthe stringsofthe Qt library.

// cuteediti18n/main.cpp (continued)

QTranslatorqtTranslator; QString filename("qt_%1").arg(QLocale::system().name()); filename =filename.toLower(); qtTranslator.load("qt_" +QLocale::system().name()); app.installTranslator(&qtTranslator);

379 14 Internationalization

QCoreApplication::setOrganizationName("OpenSourcePress"); QCoreApplication::setOrganizationDomain("OpenSourcePress.de"); QCoreApplication::setApplicationName("CuteEdit");

MainWindowmainWindow; mainWindow.show();

returna.exec(); }

Thedirectory$QTDIR/translationscontainsthe rawtranslationsources in thefile qt_untranslated.ts, together withaseries of translations for different languages, such as qt_de.tsand qt_de.qmfor Germany.All you need to do is copythecor- responding files to thecurrent directoryso that theQTranslator object willfind them. Since theorganizationand applicationname, as wellasthe domain, areusedin thepathfor configurationsfiles, thesestrings should notbetranslated.

14.1.4Adding Notes forthe Translation

If astring’smeaning is notunique, for examplebecause it just consists of oneword, this can lead to problems in translations.For instance,the translator who just sees thewordasasingle word, andnot in itsentireusage context, hasnoclues as to whether theword stop means“stop thecurrent operation”or“busstop.”For this reason thetr()method allowsatranslationcomment to be placed as thesecond argument.The code

QString busstop=tr("Stop","bus stop"); QString stopaction =tr("Stop","stopaction");

generates twodifferent stringswithcorresponding comments, after lupdatehas been runonthe translationsource.

14.1.5 Specifying theTranslation Context

Forstrings occurring in global functionsthatdonot belong to anyclass, thereis no classtouse as adefault translationcontext. It is neverthelesspossible to assign acontexttosuchastring bycallingthe actualstaticmethod tr() of aparticular class:1

1 TheQApplicationmethodtranslate() alwaysdemands details of thetranslation contextanyway (see page 50).

380 14.1 Translating ApplicationsintoOther Languages

void global_function(MyWidget * w) { QLabel * label =newQLabel(MyWidget::tr("foo"),w); } lupdatethenacceptsthe label“foo” in thecorrect correcttranslation context(in this case, MyWidget).

14.1.6InternationalizingStrings OutsideQtClasses

Forreasons of space,certain dataisoften stored in astaticarray.Inorder that lupdatecan also record entriesfromsuchchararrays, theymustbeenclosedby themacroQT_TR_NOOP. tr() then searches for itstranslationsasbeforefromthe catalog. This is illustrated in thefollowingexample, in which westore several citynames in astatic, null-terminated array.After wehaveinstantiated theQCoreApplication object andinstalledthe translator there, theprogram displaysthe localized city names, via thetr()instruction,assoon as atranslation fileisavailable:

// trnoop/main.cpp

#include #include intmain(intargc, char * argv[]) { static const char * cities[] = { QT_TR_NOOP("Cologne"), QT_TR_NOOP("Munich"), QT_TR_NOOP("Rome"), 0 } ;

QCoreApplication app(argc, argv);

QTranslatortranslator; filename =QString("trnoop_%1").arg(QLocale::system().name()); filename =filename.toLower(); translator.load(filename); app.installTranslator(&translator);

inti=0; while (cities[i]) qDebug<

return0; }

381

Appendixes

x ndi pe A Ap

Debugging Help

Erroranalysis, often referred to as debugging,isone of themosttime-consuming activitiesinsoftwaredevelopment. Apartfromusing adebugger, themethod of choice is to augmentthe code withinstructionsthatproduce debugoutput(e.g., thecalculationsofalgorithms)atstrategicallychosen points. Youcan also recon- struct howandwhenvarious program partsare calledbyusingselected output messages.

A.1 Debugging Functions

Thereare twodifferent approaches to outputting debugging code,reflecting the differences between Cand C++. Cdevelopers workpredominantlywithprintf(),

385 A Debugging Help

whereas C++programmers prefer to send output throughstreams,via cout and cerr, sincenoformatstringisrequiredinthiscase. Bothapproachescause problems as soon as program developmentisfinished:Prior to each release, thedebugging output mustbedeactivated,althoughcertain criti- cal messagespossiblyshould remain. Qt allowsanalternativeapproachthatsupports both of thecommonlyused meth- ods andalsosolves theaforementioned problems:Itsuppressesdebug messagesif theQT_NO_DEBUG_OUTPUT macroisdefinedduringthe compilingprocess.Thisis automaticallythecaseifyou build theQtprogram without debugsupport. This is ensuredbyinsertingthe CONFIG -= debuglineinthe projectfile. To usethe functionsand macros describedbelow,noadditional includes areneces- sary—apart from files that otherwisemanage without Qt elements.Inthiscaseyou mustinclude theQtGlobalheader:

#include

If QT_NO_DEBUG_OUTPUT is undefined, whatisdonewiththe errormessagesde- pendsonthe operating system.Under Unix-type operating systemssuchasLinux or OS X, theoutputappearsonthe standarderror output,better knowntoUnixafi- cionados as stderr. On Windowsmachines this output landsinthe debugger. If you wanttodivertthe output elsewhere,you can defineyourownmessage handler, as describedinSection A.1.3onpage 388. Qt provides threefunctionsthatare responsible for thedebug output andimple- ment theaforementioned debugging output behavior: qDebug(), qCritical(), and qFatal(). Theyonlyvaryin terms of theireffects, buttheyareusedinanidentical manner. Theirsignaturesshowclosesimilarities withthe Cfunction printf(). They even usethe same formatoptions.

A.1.1SimpleDebug Output

qDebug() is thetool of choice for “normal” debugoutput:

void qDebug(const char * msg, replacement_values_for_format_strings )

This function is used,for example, to examinedatastructuresatruntime. The followinginstruction displaysthe number of entriesinthe QListobject list:

qDebug("Amountof list entries:%d",list.size());

In this caseqDebug()replacesthe formatting placeholders, which areprefixed by thepercentage sign,withthe actualparametersthathavebeen suppliedafter the

386 A.1 Debugging Functions

message string,one byone—in this example, thanks to the%dformatspecifier, qDebug() outputsthe number of listentries as adecimal number.The parameter listcan be anylength at all. Undernocircumstances should you specifyavariable as thefirstargument andassume that qDebug() willproduce aproperoutput, sinceqDebug()accessesthe system-dependent printf() for theoutput, andseveral printf() implementationsfirstexpect aformatting string. QStrings or byte arraysconvertthe helper function qPrintable() into C-compliant strings(constchar*)thatqDebug()can output directly: 1

QString str ="Hello, world!"; qDebug("Myfirst application printed ’%s’.\ n",qPrintable(str));

Aconsiderablymore pleasant andmoremodern type of output is available if you includethe header. Then thereishardlyanyneed for manualserial- ization,2 sincemanyQt typesalreadyprovideserializationoperators for usewith qDebug(). Forexample, qDebug() << "Brush:"<< myQBrush; displaysall thepropertiesofthe myQBrush paintbrush. This works for allmajor Qt types, so that qDebug() can also displaystringsand byte arrayswithout the diversion via qPrintable(). Theconcept is verysimilartoredirecting output using cerr, butdiffers from this in twoimportant points: On onehand, qDebug() addsa space between thevarious outputs, andonthe otherhand, it takescareofthe line wrap, without aendlhavingtobeexplicitlyserialized as thefinalelement,asisthe casewiththe C++operatorcout. Theserializationoperators aredefinedonlyfor qDebug(), butnot for thefunctionsdescribedbelow.

A.1.2Errorsand Warnings

Forerror messagesoccuring during program execution, Trolltechprovides theqCrit- ical() method. This works exactlylikeqDebug(), according to theconventionsused for theprintf()command: void load(const QString &fileName) {

1 qPrintable() corresponds to thecallstr.toLocal8Bit().constData(), which returnsaconstchar*. However,the result is onlyvalid foraslongasthe underlyingstringremainsunchanged.Ifyou requireaCstringthatpersists while theQtstringchanges,thenyou mustduplicatethe result withqstrdup()and delete thecreated Cstringlater,withfree(). 2 is understood as theconversion of an object to adatastream accordingtoawell- defined encoding.Thisisnecessaryhere if you wanttolistall therelevantobject propertiesin thedebug output.

387 A Debugging Help

QFile file(fileName); if (!file.exists()) qCritical("File ’%s’ doesn’t exist!",qPrintable(fileName)); }

Nevertheless, qCritical() doesnot releasethe programmerinthe slightestfrom buildinggracefulerror-handlingcapabilitiesintothe application’sinterface, be- cause (for agraphical applicationinparticular) it cannotbeassumedthatwhen somethinggoeswrong,auser willbelookingatconsole output—or, under Win- dows, even haveadebuggerrunning.For this reason,errorsshouldbepassedon to theuserasoften as possible in acomprehensible form, such as via adialogbox. Anotherdebugging tool optionistouse warnings .These strings, issued via qWarn- ing(), alwaysappear whenever QT_NO_WARNING_OUTPUT is not activated during compilation of theprogram.Ifyou also setanenvironment variable bythenameof QT_FATAL_WARNINGS,the program willclose thefirsttimeaqWarning()isissued.

If theprogram also is to terminate in therelease version whenaparticular behavior occurs, you can usethe function

void qFatal(const char * msg, ... )

In this casethe value 1isreturned.Inthe debugversion thebehaviordepends on theplatform: UnderUnixtheprogram triestogenerateacore dump,whereas un- derWindowsitannounces a_CRT_ERROR, givingthe debuggeraclue.Anexample of an errorthatitwould be useful to treatusing qFatal() is divisionbyzero:

intdivide(inta,intb) { if (b== 0) // Error! qFatal("Division byzeroisnotallowed!"); returna/b; }

Foreach of thedebug helper functionswithCprintf()–style semantics—that is, qDebug(), qFatal(), qWarning(), or qCritical()—theinternalcharacter bufferisre- stricted to 8,192 bytes.Thisincludesthe terminating \0character. In addition, Trolltechwarns that passing (const char *)0as aparameter can lead to theprogram crashing on some platforms.The reasonsare flawed implementationsofprintf(), thefunction which Qt accessesfor debugging functions.

A.1.3Customizing theOutputofDebugging Functions

UnderWindows, Qt sendsall debugoutputtoadebugger. Developers who areused to thecommand lineand don’t workwithVisualStudiosometimeshaveaproblem

388 A.1 Debugging Functions

withthis. However,Qtallowsyou to divertdebug messagestowherever you want byusingso-called message handlers. Thefollowingcode indicateshowto implementyourownmessage handlerincase theapplicationisrunning under Windows. It uses themacroQ_WS_WIN, which onlyexists in Qt for Windows: intmain(intargc, char * argv[] ) { QApplication app( argc, argv); #ifdef Q WS WIN qInstallMsgHandler(debugWinMsgHandler); #endif ... }

This includeguard ensuresthatthe handlerreallyis onlyaccessedunder Windows.3 In general, thereisnothing to stop you from activatingthe handleronall platforms on which consoleoutputispreferred.Adebughandler that displaysdebug output in aseparatewindowcouldlook likethis, for example: void debugWinMsgHandler(QtMsgTypetype, const char * msg) { static QTextEdit * edit=newQTextEdit(); edit->setWindowTitle("Debugwindow"); edit->show();

switch (type) { caseQtDebugMsg: edit->append(QString("Debug: \ %1").arg(msg)); break; caseQtWarningMsg: edit->append(QString("Warning: \ %1").arg(msg)); break; caseQtCriticalMsg: edit->append(QString(" Critical: \ %1").arg(msg)); break; caseQtFatalMsg: QMessageBox::critical(0,"Debug-Fatal",msg); abort(); } }

Thesignature of themethod, as is usualwithcallbacks,isdefinedinadvance,but thefunction name can be freelychosen.Itisimportant onlythat qInstallMsgHan- dler() specifies thecallback method withits correctname, andalsowithout brackets

3 Alternatively,there existthe macros Q_WS_MACfor MacOSX,Q_WS_X11for X11-basedplat- forms, andQ_WS_QWSfor theembeddedvariantsofQt. Seehttp://doc.trolltech.com/4.1/qtglo bal.html foralistofall compiler- andplatform-dependentmacros.

389 A Debugging Help

andarguments. Youcan thinkofacallback as apointer to afunction.Whoever holdsthe pointer can invokethe function from an abitraryplaceinthe application at anygiven time.However,thisassumesthatthe callerknowswhich arguments thecallback function takes. In this case, therequiredargumentsfor themessage handlercallback function aredocumented in theQtdocumentation. Thecoreofour handlerconsistsofaswitch statementthatdistinguishes between themessage types(theQtDebugMsgs given outbyqDebug(), theQtFatalMsgs gen- erated byqFatal(), etc.)sothattheycan be treated specifically. Oursimpleimplementationshowsall debugoutputinaQMessageBoxtextwindow. Since theeditpointer is declared as static,newis used for initializationonlyon the first cycle. This removes theneed to declareglobalvariablesand makesaseparate classunnecessary.Each time it is invoked withamessage,the function ensures that thewindowis visible andthatthe newmessage appearsinthe window. Since QTextEditcan understandprimitiveHTML, wetake advantage of this in the code for ourdebug windowto formatthe textclearly.Onlyfatal errors, which cause theprogram to be immediatelyterminated,are shownbytheroutine in a modal message boxthat blocks therestofthe application. If theuserconfirms such amessage byclicking OK,the program terminates immediately. Theroutine suggested here doeshaveasmalldisadvantage,however:The program onlyterminates whenthe debugwindowhasbeen closed byhand.

A.2 WaystoEliminateErrors

Cand C++alreadyhaveawiderange of methods for sniffingout errors. Qt addsa fewotherusefulones, which replaceexisting functionswithmoreportable varia- tions.

A.2.1CheckingAssertions

TheCfunction assert() interruptsprogramscompiledinthe debugmode if the expression specified in brackets (the assertion )evaluates to false.Itonlyworks if theNDEBUGmacrois not defined andrequiresanadditional #include,usuallyof assert.h. Qt hasthe Q_ASSERT() macro, which interruptsthe program just likeassert() if certainconditionsare notfulfilled. It is often used to testpreconditions or post- conditions for specificcode segments of methods.Incontrasttoassert(), theQt variant distinguishesbetween therelease andthe debugging versions 4 of apro- gram:Inthe debugversion,Q_ASSERT() breaks off withanerror message.

4 Howyou compile aprogram as adebug or as areleasevariation is describedstartingonpage 27.

390 A.2 Ways to EliminateErrors

ThefollowingQtassertioninstruction,for example, checks whether theprogram was given oneormorearguments:

Q_ASSERT(argc >1);

If this is included in themain() function in line12ofthe filemain.cpp, theprogram willoutputthe followingtothe command lineifthere is an error:

ASSERT:’argc >1’in file main.cpp,line 12

If wecompiledthe program in debugmode,the program willthenterminate. In addition to reporting filenames andlinenumbers, it is often also useful to specify acontextthatprovides information on thepurposeofthe assertion. Qt hasthe macroQ_ASSERT_X()for this purpose:

Q_ASSERT_X(argc>1, "main()","Noarguments passed!");

This makesthe program considerablymore verbose andspecifiesthe contextfor theassertion:

ASSERT failureinmain():’Noarguments passed!’, file main.cpp,line 12.

A.2.2CheckingPointers

Unpleasant surprisescan often waitinstore for developers of portable programs, particularlywhenyou need to allocate largeamountsofmemoryor to referencea pointer acrosslibraryboundaries. Let’slook at thefollowingprogram extract: char * lots_of_memory=newchar[1024 * 1024 * 1024]; Q_CHECK_PTR(lots_of_memory);

If apointer allocation goeswrong,newsets thepointer to thevalue 0(the null pointer). In this case, theQ_CHECK_PTR() macrousedherereactswithanerror message as shownbelow,and terminates theprogram:

Infile main.cpp,line 14: Out of memory

If you usethismacrowhenever you requireverylargeamountsofmemory,you will saveagreatdealofworkwhensearching for thecausesofmemorybottlenecks. This is of immenseimportanceparticularlyin porting to architectureswithlittle main memory.

391 A Debugging Help

Since Q_CHECK_PTR(),justlikeQ_ASSERT andQ_ASSERT_X, is onlyexecuted in debugmode,itisimportant that you don’t useittocarryoutoperationsthat may,under some circumstances,influencethe normal running of aprogram.Such operations, also called side effects ,can in particular cause theprogram to only workcorrectlyin debugmode.For example, thefollowingvariation of ourabove examplewillmostlikelyalwayscrash in therelease version:

char * lotsofmemory; Q_CHECK_PTR(lotsofmemory=newchar[1024 * 1024 * 1024]);

This is because thepointer to thenewmemoryarea is never initialized in therelease version.Thatmeans that anyaccesstolotsofmemorywill then be invalid,which willcause theprogram to crash.

A.2.3CommonLinkerErrors

If thelinkerannounces errors containing thekeywords vtbl, _vtblor__vtbl, you should first search forthe probleminthe meta-object compilermoc.Itgenerates an additionalfile for allclassescontainingthe lineQ_OBJECT,which mustbelinked to theproject. qmakemay“forget” aboutthisbecause of an incorrectlysetsystem time (which can also triggermanyotherproblems,since thebuild system reliesonhavingthe correcttimestampsonthe source files), butthere arealsoother causes. In this case theproblemcan be solved bycallingqmake byhand.

392 x ndi pe B Ap

Tulip: Containersand Algorithms

Anyonewho wants to program complexalgorithms in C++willoften make use of the StandardTemplateLibrary (STL),acollectionofalgorithms andcontain- ersthatimplement datastructures. Containers areusuallycreated as atemplate class, whereas algorithms operate either on containers or arethemselves template functionsthatworkwiththe datatype required in each case. Yeteven now,the STLhas various disadvantages: Anumber of compilers, partic- ularlyolderones, do notfullyimplementsomelibraryparts, or implementthem incorrectly.Also, sinceloop contents havetostand in aseparatefunction,some algorithms cannotbeexpressedinanintuitivemanner, as demonstrated in the followingcode,which outputstothe screen each elementofthe vectorvvia the print_element()function:

393 B Tulip: Containersand Algorithms

// stl/main.cpp

#include #include

using namespace std;

void print_element(intelement) { cout << element<< endl; }

intmain() { vectorv;

v.push_back(1); v.push_back(2); v.push_back(3);

for_each(v.begin(),v.end(),print_element);

return0; }

Trolltechthereforeprovides alightweightaddition to theSTL withthe name of Tulip. 1 Tulip is STL-compatible,soitincludesall thenecessarymethods,suchas push_back(), butinaddition it also hasequivalentfunctionswithmoreintuitive names. Thus push_back()ismerelyasynonymfor append(). In addition,Tulip containers make useofJava-style iterator concepts. Theyprovide theirowndatastructuresand matching algorithms for this purpose, which are somewhateasiertohandlethanthose of theSTL.

B.1Iterators

When theelementsofalistneed to be accessed, manydevelopers usethe tradi- tional index-based accesswithinafor loop:

QStringList list; list << "Dog"<< "Cat" << "Mouse"; for(inti=0; i

1 Thenameisderived from theEnglishterm Tool Lib (tool library).

394 B.1 Iterators

This hasanumber of disadvantages. Basically,iteratingvia indexaccessisslowfor manydatastructures, especiallyfor structures that do notallowdirect addressing via indices(such as some listtypes, or in trees). 2 This is whywehave iterators . 3 Theseare objectsspecialized for useintraversing datastructures. Theypoint to an elementofadatastructure andatthe same time providemethods to movearound withinthe datastructure,startingoff from the currentelement.Inthiswaytheyabstract thedatastructure from theiteration, thus avoiding code that uses direct indexed access Anotheradvantage is that iteratorsallowtheconcrete implementation of thedata structureonwhich theyoperate to be replaced without theneed for theiteration code to be modified. If you realizethatanembeddedlistisbetter for certain problems than anormalpointer-based QList, you can simplysubstitute thenew implementation without this change havinganyinfluence on code that uses the iterator. In addition,iteratorsprovideacertainamount of protection from coding errors that arisebecause of changesinthe containeronwhich theyareworking.For example, if an elementisaddedtothe beginning of alistasthe consequenceof an iteration(which is often thecaseinthe specialized listQQueue),index-based addressing returnsthe wrong elementonthe nextaccess unlessthe code hasbeen written to adjustthe indexvariable to account for theinsertion.Thisextracareis often unnecessarywhenwriting code that uses iteratorstomovearound thedata structure. Tulip makesadistinctionbetween twoiterator typyes:STL-style andJava-style, which arebased on different concepts.

B.1.1STL-Style Iterators

Themostwell-knowniteratorsinthe C++environment,STL-style iteratorsare im- plemented byTulip in allcontainer classes. STLiteratorsalwayspoint to elements. If theyreach theend of thecontainer andare advancedpastthe final element, theythen point to anonexistentelement.Ifyou then tryto accessthe element pointed at bytheiterator,the result is undefined. Using aQStringList,which is basicallyjust aspecialized QList,the exam- pleabovewill look likethis:

// foriterator/main.cpp

#include #include

2 Agood exampleofthisisthe QLinkedListdiscussedonpage398. 3 In database circlesthese areknownas cursors .

395 B Tulip: Containersand Algorithms

using namespace std;

intmain() { QStringList list; list << "dog"<< "cat" << "mouse"; QStringList::iteratorit; for(it=list.begin(); it!= list.end(); ++it) { qDebug() << * it<< endl; } return0; }

If you applythe*operatortoit, you willreach thelistelement to which theiterator currentlypoints, sinceQtoverloads theoperator*for iteratorsfor this purpose. In such for loops you mustremembertouse thepre-increment operator(++it) insteadofthe usualit++: This operatorworks without an unnecessarytemporary object for each for loop. If theelementsaccessedinthe loop should onlybe read butnot modified, a const_iterator is used,which ensuresimproved performance.

B.1.2Java-StyleIterators

Apartfromthe STLiterators, Qt also hasJava-style iterators. Thoseare self-con- tained classeswhich Trolltechhas namedaccordingtothe patternQcontainer- nameiterator. Conceptually,Javaiteratorsare fundamentallydifferent from STLiteratorsina number of ways. Forone thing, theypoint between twoelementsofadatastruc- ture,and not to oneofthem. This resultsinJavaiteratorsoften beingeasierto handle,but somewhatslower than theirSTL-compatible colleagues. Anotherdifferenceisthatbydefaulttheydo notallowwrite accesstothe data structure, in contrast to theirbrothersofthe same kind in theSTL world.There aretworeasons for this.Onone hand,datastructuresare more often read than written during traversal. An immutable (thatis, an unchangeable)Javaiterator or const iterator (STL) saves time throughthis. On theother hand,animmutable iterator ensuresthatthe datacannotbechanged byan errorinthe program. To obtain write accesstothe elements of thedatastructure,changeable iterators, or mutableiterators,are used.For QList-basedlists thechangeable iterator is called, for example, QMutableListIterator. ThefollowingexampleshowshowJava-style iteratorsare handled. It operates on an existing integer QListcalledlist:

QListIteratori(list);

396 B.1 Iterators

while(i.hasNext()) { cout << i.next() << endl; } hasNext()checks whether thenextelement exists.next()not onlyjumpsforward, butalsoreturnsthe nextvalue.After this,the iterator is located after this element, sinceJava-style iteratorsalways“standinthe spacesbetween elements.” If you only wantthe value of thenextelement,without movingthe iterator,you should use peekNext(). In thesameway,hasPrevious(), previous(), andpeekPrevious() also exist, allowing iterationinthe oppositedirection.toBack()takesthe iterator just beyondthe final element, andtoFront() takesitinfront of thefirstone. Furthermore, findNext()allowssearching for elements withaspecificvalue.Ifsuch an elementexists,the function returnsatrue value,justlikehasNext(), andposi- tionsthe iterator after theelement found.LikewisewehavefindPrevious(), which if successful places theiterator in frontof thevalue found.Ifnoelement is found, theiterator landsbeyondthe final element, or beforethe first one. This is demon- strated bythefollowingcode:

// qlistdemo/main.cpp

#include #include intmain() { QListintlist; intlist << 2<< 5<<2<< 4<<2;

intfindings=0; QListIteratorit(intlist);

while (it.findNext(2)) findings++; qDebug() << findings; // output:3 // Iteratorispositioned afterthe last ’2’ element while (it.findPrevious(2)) findings--; qDebug() << findings; // :0 // Iteratorispositioned beforethe first ’2’ element return0; }

In addition to this,changeable iteratorscan insert an elementatthe currentposi- tion withinsert(), andthenjump to itsdestination.Thisisillustrated bythenext example: It jumpsbeyondthe final element, where it addsafurther2.Because the iterator nowstands after this last 2, thealgorithm comesupwithfourmatches:

397 B Tulip: Containersand Algorithms

// mutableiterator/main.cpp

#include #include

intmain() { QListintlist; intlist << 2<< 5<<2<< 4<<2;

QMutableListIteratormit(intlist); mit.toBack(); mit.insert(2); qDebug() << intlist; // output:(2,5,2,4,2,2)

intfindings=0; while (mit.findPrevious(2)) findings++; qDebug() << findings; // output:4 return0; }

Thefollowingoperationsappear somewhatstrange,astheyviolate theimmutabil- ityprincipleofJavaiterators: Theymanipulate or inspectelements, although the iterator in factnever directlypointstoone of them. Theoperationsremove(), value(), andsetValue() thereforeoperate on thefinalelement to be jumped over. Thefollowingexampledeletes allelementswiththe value 2:

// mutableiterator2/main.cpp

#include #include

intmain() { QListintlist; intlist << 2<< 5<<2<< 4<<2;

QMutableListIteratormit(intlist);

while (mit.findNext(2)) mit.remove(); qDebug() << intlist; // output:(5, 4) return0; }

B.2Lists

Listsrepresent fundamentaldatastructuresfor most applications.Qthas three different types: QList, QLinkedList, andQVector.

398 B.2 Lists

Theseare templates that in theirbasic formare notspecialized to aspecific data type.Consequently,you can make alisttype outofnearlyeveryclass. Theclass only needstobeassignable,thatis, to haveacopyoperatoraswellasanassignment operator: class Assignable { public: Assignable() {} Assignable(const Assignable &other); // copy operator Assignable&operator=(const Assignable &other); // assignmentoperator private: ... } ;

Tulip containers arevalue based, buttheycan also handle pointers, sinceapointer is,firstofall, nothingmorethananinteger value,namelyamemoryaddress.This meansthat

QList myDateList; is just as permissible as

QListmyDateList;

It mustberememberedherethatQtdoesnot delete theelementsbehindthe point- erswhenitdeletes thelistofpointers. This shortcomingisrectifiedbythehelper function qDeleteAll()(seeSection B.5.7onpage 418),which accepts either acom- plete containerortwoiteratorsasarguments. In thelatter caseitonlydeletes theelementsbetween thestart andend iterator,including theelement to which begin() points(thelastiterator is invalid anyway). Thepointersthemselves remain,however.Ifyou wanttouse thelistagain—as in thecaseofthe followingcode—theymustadditionallybe deleted withclear():

// listpointerdemo/main.cpp

#include #include intmain(intargc, char * argv[]) { QApplication app(argc, argv); QListwidgetList; for(inti=1;i<10; i++) { widgetList.append(newQWidget);

399 B Tulip: Containersand Algorithms

} // the following isequivalenttoqDeleteAll(widgetList); qDeleteAll(widgetList.begin(),widgetList.end()); // deleteall nowinvalid pointers widgetList.clear(); // append newitem, thisisnowthe first list item widgetList.append(newQWidget); return0; }

If you wanttosaveyourselfalotoftyping where containers areconcerned,you can defineyourownnames for thedesired listtypes, usingthe C++keywordtypedef:

typedef QList QDateList;

Forreadaccess to listelements, it is advisable to usenot theindexoperator([]), but theat()method. Forcode such as

QListlist; ... QImage image =list[i];

theindexoperatorreturnsonlyaconst referencefor aconst-declaredmethod. In allother cases it returnsanormal reference. Even though this is actuallyagood thing(after all, const correctness4 helpsyou to write more efficient programs), you maynotalwayswanttoimmediatelygenerate acomplete copy.Thisproblem doesnot occurifyou usethe at() method:

QImage image =list.at(i);

B.2.1SimpleList(QList)

STLprogrammers frequentlyuseastd::vectorbecause this—in contrast to theSTL listcontainer,which is implemented as alinkedlist—is veryfasttoiterateover. TheQList,the listcontainer most frequentlyused in Qt, is,fromthe perspectiveof implementation,anarrayof pointersthatpoint to objects, in contrast to std::list. Provided that you wanttoaccess objectsdirectly,thissolutionisquickerthanan embeddedlist. Insertingatbothendsofalistusing prepend()and append()isalso veryfast—for lists withuptoathousandentries,QList is usuallythefastestsolution of allthose provided bytheQtand STLclasses.

4 const correctness refers to theuse of theconst keywordfor referenceparametersoffunctions andfor member functionsthatdonot modifytheobject(s)withwhich theyarecalled. Access methods that pass theinternalstate of an object to theoutside should thereforealwaysbe declared as const, forexample.

400 B.2 Lists

B.2.2LinkedList(QLinkedList)

If you frequentlyneed to insert elements into largedatacollections,you arebetter off withaQLinkedList(seeFigures B.1and B.2).Thisclass is implemented as a linkedlist. It hasthe disadvantage,however,thataccess to largelists bymeansof theindexoperatororat()can become veryslow.Thisisexplainedbythewaya linkedlistworks:Each elementcontainsapointer to thenextone,and therefore in ordertoaccess theelement withaspecified index,the containermustvisit all elements from thefirstone to theone beingsought.

Figure B.1: To insert an element into alinked list,only onepointer (the longer line) needsto be movedtothe new element ...

Figure B.2: ...and another pointer is added, that pointsfromthe new element to theone that followed until now.

B.2.3Vectors ( QVector)

AQVector is particularlysuitable if you need contiguous memory.Ifneeded,space for newdataelementsisonlyallocatedimmediatelyafter theexisting memory. Theclass thus ensuresthatthe dataalwayslies adjacent in memory.The data() method makesuse of this fact: It provides an arrayof thesamedatatype as the oneusedtodeclare thevector.

Figure B.3: QVector provides a dynamicarray,like std::vector .

Avectorbehaves likeavariable array:Internally,the datastructure alwaysreserves slightlymore memorythan currentlyrequired,which is whyappendoperationsat theend of thevectorare veryfast(Figure B.3).

401 B Tulip: Containersand Algorithms

Thefollowingexampledoubleseveryvalue stored in vector:

// vectordemo/main.cpp

#include #include

intmain() { const intn=10; QVectorvector(n); int * data=vector.data(); // fill vector for(inti=0; i

Theresources used in thesecondfor loop arethe same as thoseusedtoaccess a normal Carray.Inaddition onlyconstant costsare causedbythecallofdata(), sinceQByteArrayworks internallywithaC++array.The result of data()istherefore valid onlyuntil thesizeofthe vectorchanges. Theindexoperatorreturnsareferencetothe elementatthe requested position, provided that theindexspecified is notlargerthanthe number of elements in the vector. With vectors that arenot declared as const,and which arethereforefreely writable,you can thus fabricate thefollowingcode:

// vectordemo/main.cpp (continued)

QVector strvector; strvector.append("short string"); if (strvector[0]== "short string") strvector[0]="extraloooong and verbosestring"; qDebug() << strvector; return0; }

Access to avectorelement is denoted in thesamewayas areadaccess to an array, which—asisfamiliarfromC++—begins at index 0 .Hereweinsertthe first element into thevectorusing append()insteadofallocating ten entriesinthe constructor, as in theprevious examplewithvector(n). This is less efficient than usingaQList, butitisstill possible. To be absolutelysure that you never accessanindexpositioninwrite mode by mistake,even if theelement is notdeclaredasconst,you should useat()insteadof theindexoperator.

402 B.3 Stacks and Queues

B.3Stacksand Queues

B.3.1Stacks ( QStack)

Stacks aredatastructuresthatworkaccordingtothe so-calledLIFOprinciple ( last in, first out ). As withareal stack,you can onlyremovethe topelement,thatis, thelastelement placed on thestack.

Figure B.4: QStack lookslikea vectorturned on its sideby90degrees, andthe classactually doesinherit from QVector .

Placingavalue on aQStack is donebythepush() method, which expectsanin- stance of thetype used as theQStack type parameter.Conversely,pop()takesthe topelement from thestack andreturnsit. Thefollowingprogram outputsthe digits entered in reverse:

// stackdemo/main.cpp

#include #include intmain() { QStackstack; stack.push(1); stack.push(2); stack.push(3); while (!stack.isEmpty()) qDebug() << stack.pop(); // output:3,2,1 return0; } top()returnsareferencetothe topelement without taking it from thestack. This couldbeused, as describedinSection B.2.3, to look deeper into thestack, since it is actuallyonlyavector(Figure B.4).Thiswould violate thesemantics of the

403 B Tulip: Containersand Algorithms

stack datastructure,however.For this reason it is recommended that you usethe methods provided directlybyQStack. As aJava-style iterator,QVectorIterator is used;itfunctionswiththe semanticsof theQVector base class.

B.3.2Queues(QQueue)

In manyapplications you willnot be able to avoidhavingtouse aqueue.The possibilitiesare wideranging—queuesare used to implementbuffers,5 as temporary memoryfor tree-based algorithms such as breadth-first search,and muchmore. Qt provides theQQueueclass for queues.Thisismerelyaspecializationofthe QList. It is easyto seethe thinking behind this design decision,because aQList performs wellwheninserting anddeleting at thebeginning andend of thelist. To getavalue into thefront of thequeue,the enqueue()method is used.Asa parameter it expectsavalue of thetype that was used as thetype parameter in the declaration of thequeue.dequeue() removes thelastelement from thequeue and returnsit. As aJava-style iterator,the iterator of thebaseclass is used (inthe same wayas QStack),thatis, QListIterator.

B.4AssociativeArrays

At first glance,the containerclassesQMapand QHashseem to servethe same pur- pose: Theysavealistofkey-value pairs in which accesstothe resultingcollection of valuesisusuallyperformedbyspecifyingnot an index,but akey.Nevertheless, thereare differences between thetwoclasses, both in theirimplementationand in theirperformanceinspecific circumstances.

B.4.1Dictionaries(QMap)

QMap providesadictionaryandisthe slower of thetwodatastructures, butit also sortsthe key-value pairs automatically.Thisisofparticularrelevance to the programmerifhewants to iterateover thedatastructure:Whenusing QMap iterators, theoutputisalreadysorted bykey. ThefollowingexampleshowshowaQMapthatassociatesastring to an integer value is created:

5 NottobeconfusedwithQBuffer, which represents an input/outputdevice; seeChapter 11.

404 B.4 Associative Arrays

// mapdemo/main.cpp

#include #include #include intmain() { QMapmap;

map["one"]=1;//insert using the [] operator map["two"]=2; map.insert("seven",7); // insert using insert()

qDebug() << map["seven"];//readusing the [] operator qDebug() << map.value("seven"); // readusing value()

QMapIteratori(map); while (i.hasNext()) { i.next(); qDebug() << i.key() << ":"<< i.value(); } return0; }

With thehelpofthe indexoperatororbyusinginsert(), wefill up thedictionary mapwithvalues. Theargument in brackets or thefirstargument to insert() is the key,for which,inthiscase, weuse valuesofthe QStringtype.Itisworth your while to useinsert()ratherthanthe indexoperator, bytheway:The latter is often significantlyslower wheninserting entries. Cautionmustbeusedwhenaccessing theQMap, however.The value() method and theindexoperatorbehaveinthe same wayonlywithobjectsdeclaredasconst. Otherwise, theindexoperatorhas asometimesnastyside effect:Ifthe keybeing sought is missing,itcreates anewemptyentry.Asaresult,aninstanceofQMap can become hugelyinflated,particularlyafter manyad hocqueries havebeen made against it,inwhich thousandsofunsuccessful accessestake place.Accessing the QMap bymeansofvalue() protects it from this side effect. At the endofthe exampleaQMapIterator goes throughthe listentrybyentry.In contrast to theiteratorsintroduceduntil now,thisone hasthe methods key() and value() to do justicetothe nature of thedatastructure. Data typesthatyou definemustfulfillspecial conditions in ordertobeusedas keysindictionaries.Adatatype whose valueswillappear as keysinaQMap must implementthe less-thanoperator(operator<()) to allowthemembers of theQMap to be sorted.Wecarrythis outinthe nextexampleusing adataset classthat provides arecordwithfields for an employee’sfirstand last name:

405 B Tulip: Containersand Algorithms

// customvaluedemo/datensatz.h

#ifndef DATENSATZ_H #define DATENSATZ_H

#include #include

class Record { public: Record(const QString &surname, const QString &forename) { m_forename =forename; m_surname =surname; }

QString forename() const { returnm_forename; } QString surname() const { returnm_surname; }

private: QString m_forename; QString m_surname; } ;

Nowweimplement therequiredless-thanoperator:

// customvaluedemo/datensatz.h(continued)

inline bool operator<(const Record&e1, const Record&e2) { if (e1.surname() != e2.surname() ) returne1.surname()

Thefollowingprogram saves some datasets in aQMap, together withanIDthat displaysthe personnelnumber:

// customvaluedemo/main.cpp

#include "datensatz.h" #include #include #include #include

intmain() { Recordd1("Molkentin","Daniel"); Recordd2("Molkentin","Moritz");

406 B.4 Associative Arrays

Recordd3("Molkentin","Philipp");

QMap map; map.insert(0,d1); map.insert(1, d2); map.insert(2,d3);

QMapIterator mi(map); while (mi.hasNext() ) { mi.next(); qDebug() << mi.key() << ":" << mi.value().surname() << mi.value().forename(); }

We requirethe QHashheaderfile for theextensionsonpage 410,where wewill make ourclass compatible withhashes.

Requirements of KeyElements

As wehavejustseen,because aQMapkeepsits entriessorted according to key value,the classthatisusedasthe keytype musthavealess-thanoperator(here, <),sothatthe containercan setupanorderingofits elements.Ifyou tryto define aQMapusing aclass without such an operatorfor thekeytype parameter,the compilercomplains that theless-thanoperatorisnot defined.

B.4.2Allowing SeveralIdentical Keys ( QMultiMap)

QMap hasafurtherlimitationthatmaybe adisadvantage in some situations:It doesnot allowdistinct entriesinacontainertohavekeyswiththe same value. (Thus,the sequence of keyvaluesinthe sorted containerisstrictlymonotone.) If asecondcalltoinsert()ismade usinganalreadyexisting keyvalue,the datavalue currentlyassociatedwiththe keyvalue is overwritten withthe newdatavalue. Butwhathappensinthe followingscenario? Asawmill receives dailydeliveriesof different tree trunks. Aworkeristaskedtorecordthe number of trunksand the type of wood. However,itisimportant for theoperatortosaveindividualdeliveries as separatedatasets for later statistical evaluation.AQMap is inadequatehere, because it couldonlyrepresentthe most recent deliveriesofeach type of wood.6 Trolltechprovides theQMultiMap classfor this.Thisvariesconsiderablyfrom QMap in anumber of respects.AQMultiMapcan contain several datasets allhavingthe same keyvalue.Also, QMultiMapdispenses withthe indexoperatorfor technical

6 Admittedly,inrealitysuch aproblemwould probablybe solved withanSQL database.Ifyou are interestedindatabaseaccess,werefer you to Chapter9,where thesubject of SQLdatabases is treated in more detail.

407 B Tulip: Containersand Algorithms

reasons. Also,value() andreplace() operate on theelement that was last inserted into theQMultiMap instance. To read outall datasets covered byaspecific key,values()isthe method of choice. When given aspecific keyvalue as aparameter,itreturnsaQListofall values associatedwiththatkey. Thefollowingcode implements thesawmill examplewiththe help of aQMultiMap. Each insert() instructioninserts anewelementwithout overwriting apossiblyex- isting key.The integer listbeech, which is created usingvalues(), contains allthe incoming beechtrunksstartingwiththe value last inserted:

// multimapdemo/main.cpp

#include #include

intmain() { QMultiMaptrunks; trunks.insert("Beech",100); trunks.insert("Umbrellapine",50); trunks.insert("Maple",50); trunks.insert("Beech",20); trunks.insert("Fir",70); trunks.insert("Beech",40);

QListbeeches=trunk.values("Beech"); qDebug() << beeches; // output:40,20,100 return0; }

QMultiMapalsoprovides theaddition operators +and +=, which can be used to combineseveral associativetablesintoone single one. Forour examplethismeans that wecan verysimplysummarizethe incoming goods from several differentmills bysumming thecorresponding QMultiMaps.Inthiscaseitmayalso be worthwhile to make atype definitionfor thespecializationofQMultiMap that is in use:

typedefQMultiMapTrunkCountMultiMap; ... TrunkCountMultiMapmill1result=mill1.incoming(); TrunkCountMultiMapmill2result=mill2.incoming(); TrunkCountMultiMapmill3result=mill3.incoming();

TrunkCountMultiMaptotal=mill1result+mill2result+mill3result;

We assume here that thealreadydefined objectsmill1,mill2,and mill3 havea incoming() method, which returnsaTrunkCountMultiMap. Afterthe code executes, thetotal QMultiMapcontainsthe combined goods from allfactories.

408 B.4 Associative Arrays

B.4.3HashTableswith QHash

Thedatastructure QHashisverysimilartothe QMap in howit functions. However, whereas aQMapsorts itsentries bykeyvalue,QHash uses ahashtable internally to storeits entries. This meansthataQHashisunsorted.Compensatingfor this,it is slightlyfaster than QMap whensearching for entrieswithspecifiedkeys. TheAPIs of thetwodatastructuresare almost identical,and so wecan rewrite the QMap examplefrompage 404touse QHashinsteadjustbymaking some simple substitutionsinthe code:

// hashdemo/main.cpp

#include #include #include intmain() { QHashhash;

hash["one"]=1;//insert using [] operator hash["two"]=2; hash.insert("seven",7); // insert using insert()

qDebug() << hash["seven"];//valueusing [] operator qDebug() << hash.value("seven"); // valueusing value()

QHashIteratori(hash); while (i.hasNext()) { i.next(); qDebug() << i.key() << ":"<< i.value(); } return0; }

As withQMap, theindexoperatorinQHash is dangerous,since it insertsanew entryinto thecontainer if thekeyvalue is notfound.Aremedyis againprovided bythevalue() method. This generates an emptyentryif thevalue is missing in the hash,but it onlyreturnsit, anddoesnot insert it into thehash. Things become interesting whenyou startcreatingyourownclassestouse as keys. Such classesmustimplement an equalitycomparison operator(operator==()) as wellasahelper function bythenameofqHash() that implements thehash function. Let’sre-implementthe exampleprogram from page 406. Theindexoperatoris quicklyimplemented:Itcompares thefirstand last name stringsofbothdatasets, andreturnstrueiftheyareequal;otherwise, it returnsfalse. Calculatingagood hash value is muchmoredifficult, because this number must

409 B Tulip: Containersand Algorithms

distinguishthe elementasmuchaspossible from otherelementsinaQHashin- stance.Too manyelements withthe same hash value result in performancepenal- ties.Since theqHash() helper method is implemented for primitivedatatypesand thosespecifiedbyQt, wecan make useofthe specifichashfunction of theQString classtocalculate ahashvalue for first andlastnames (insteadofdoing thecalcu- lation entirelyfrom scratch). Combiningthe resultsusing an exclusiveor(^) in turn generates oneuniquehashvalue for theentirerecordfromthe hash valuesfor the twoparts of therecord:

// customvaluedemo/datensatz.h(continued)

inline bool operator==(const Record&e1, const Record&e2) { return(e1.surname() == e2.surname()) && (e1.forename() == e2.forename()); }

inline uintqHash(const Record&key) { returnqHash(key.surname()) ˆqHash(key.forename()); }

#endif // DATASET_H

Nowwecan useour datastructure withQHash in exactlythesamewayas wedid withQMap. Fordemonstration purposes,the exampleherealsodisplaysthe hash value for each entryin thehash:

// customvaluedemo/main.cpp (continued)

QHash hash; hash.insert(0,d1); hash.insert(1, d2); hash.insert(2,d3);

QHashIterator hi(hash); while (hi.hasNext() ) { hi.next(); qDebug() << hi.key() << ":" << hi.value().surname() << hi.value().forename(); qDebug() << qHash(hi.value()); }

Just likeQMap, QHashalsohas asubclassthatallowsdistinctentries withidentical keystoberecorded. It is calledQMultiHash, anditchanges thebehaviorofinsert() so that it no longer overwrites an alreadyexisting entrywithaspecified key,and it also reimplements replace()sothatitreplacesthe most recentlyinserted entryif several entriesinthe hash table havethe same key.

410 B.4 Associative Arrays

Like QMultiMap, QMultiHash also allowsyou to combineseveral QMultiHashes into onehashwiththe +operator.

B.4.4Hash-basedAmountswith QSet

If whatyou need is notanassociativearray,but just asimplelistthatdoesnot havetobesorted andisveryfasttosearch, then QSet maybe thebestchoice. QSet is implemented internallyas aQHash,but it provides manyof thesemantics of QString, such as cyclingthrough allelementswithforeach(). We can illustrate this usingour dataset example, in which wefirstinsertsome previouslygenerated entriesintoacustomized QSet. To do this weuse the<< operator. We cyclethrough thelistitselfwithforeach():

// customvaluedemo/main.cpp (continued)

QSet set; set<< d1 << d2<< d3;

foreach(Recordd,set) qDebug() << d.surname() << ":"<< d.forename();

return0; }

In addition,QSetprovides allthe basicoperationsfor sets knownfrommathemat- ics, such as setunion andset difference. Thefollowingexamplefirstcreates two sets andthenforms theset differenceofone of thetwosets in terms of theother one. Thesubtract() method responsible for this operates directlyon theset object that receives thecall, which in this caseisset1.Itremoves from this setall the elements that also existinthe setpassedtoitasanargument,hereset2:

// setdemo/main.cpp

#include #include intmain() { QSetset1; set1<<1<< 2<< 3<< 4<<5<< 6; QSetset2; set2 << 4<<5<< 6<< 7<< 8<<9; set1.subtract(set2); // output:1,2,3 qDebug() << "set1remainders:"<< set1;

411 B Tulip: Containersand Algorithms

return0; }

In thesamewaythereare themethods unite(), for theunion,and intersect(), for making intersections.These also change theQSetinstancewithwhich theyare called.

B.5Algorithms

B.5.1The foreach Keyword

As an alternativetoconst iterators, thereisthe foreach() macro:

// stringlistdemo/main.cpp

#include #include

intmain() { QStringList names; names<< "Patricia"<< "Markus" << "Uli"; foreach(QString name, names) qDebug() << name; return0; }

Thosewho do notliketotaint theC++ namespace (C++ inventorStroustrupis working on anativeforeach keywordinthe coming language versions) can instead usethe synonymQ_FOREACH(). Themacroisslightlyslower than aconst iterator, butthisisonlynoticeable withverylargedatastructures. In addition Q_FOREACHsupports allthe validationcharacteristics for variable dec- larationsthatfor() also has. This meansthatavariable declared in theloop header is no longer valid outsidethe loop for ISO-compatible compilers. It is important to bear in mind that foreach() creates acopyof thedatastructure. In theloop shownhere, anymodification of thelistthereforehas no effect on theoriginallist. If you areworried that Qt makesacomplete copyof thelist, you needn’tbe: Even withlists,Qtmakesuse of implicit sharing(seepage 40). Thefact that foreach() creates copies of thedatastructure haseven more positive aspects:

foreach(QString results,results()) ...

412 B.5 Algorithms

If results()containsanoperation withcost k beforereturningthe datastructure andthe function returnsacontainerwith i entries, atotal cost would arise, with anormalfor loop of k ∗ i .The copyensuresthatthere is acachingeffect,which brings downthe cost for k to aexpenditureof k + i withcosts of O (1) for k , because results()isonlycalledonce.

B.5.2Sorting

Tulip also contains functionsfor sortingdatainsidecontainers. Themostfrequently used of theseiscalledqSort() andexpectsacontainerasanargument,which it sortswiththe heap sort algorithm.

// listdemo/main.cpp

#include #include intmain() { QListvalues; values<< 1<<10<< 5<<6<< 7<< 3; qSort(values); qDebug() << values; // output:(1, 3,5,6,7,10) return0; }

This is also veryefficient withlarge amountsofdata, as it works in linear-logarithmic time ( O ( n log n ) ). Duringthe stepsofthe sortingprocess,the qSort()function makesuse of theC++ comparison operatoroperator<() to determine whether twoelementsshouldbe swapped.Iftwoobjectsare equalfor thepurposeofcomparison, it is left up to the implementation of qSort()whether theyareswapped or not. If operator<() does notcompareall object properties, theresultmayvarysubtly. Forthisreasonthere is an additionalfunction qStableSort(), which is also imple- mented bymeansofthe heap sort algorithm. In contrast to qSort(), however,it ensuresthatelementsthatare “equal”toone anotheralwaysremainintheir orig- inal sequence in thefinal, sorted list. Bothfunctionsalsohaveanoverloadedvariation:Insteadofacomplete container, theyalternativelyexpect twoiterators, thefirstofwhich pointstothe first element to be sorted andthe second to theelement after thelastobject to be sorted. This variant can acceptafunction pointer that references afunction implementing acomparisonoperation otherthanoperator<() to be used during thesort. This comparison function mustaccept andcomparetwoparametersofthe same type:

// sortdemo/main.cpp

413 B Tulip: Containersand Algorithms

#include #include

bool caseInsensitiveLessThan(const QString &s1, const QString &s2) { returns1.toLower()

intmain() { QStringList list; list << "AlPha"<< "beTA"<< "gamma"<< "DELTA"; qSort(list.begin(),list.end(),caseInsensitiveLessThan); qDebug() << list; // ("AlPha","beTA","DELTA","gamma") return0; }

B.5.3Sorting in Unsorted Containers

To findavalue in acontainer,Tulip hasthe function qFind(). This finds thevalue specified as thethird argument,startingfromthe elementtowhich theiterator namedasthe first argument points. Thelastelement of thesearchareaisthe element in frontof (thatis, before) theobject to which theiterator passedas thesecondargument points. Thefunction returnsaniterator pointing to thefirst matching object if thesearchvalue is found,otherwiseitreturnsthe iterator value end(). Thefollowingexamplesearchesinalistoffruit namesfirstfor theword Pear and then for Orange:

// finddemo/main.cpp

#include #include

intmain() { QStringList list; list << "apple"<< "pear" << "banana";

QStringList::iteratori1 =qFind(list.begin(),list.end(),"pear"); // i1 == list.begin() +1

QStringList::iteratori2=qFind(list.begin(),list.end(),"orange"); // i2== list.end()

return0; }

414 B.5 Algorithms

Afterthiscode hasrun, theiterator i1 remainsonthe second element, whereas i2 pointstoend(), which accordingtoSTL iterator logicisthe (undefined) element after banana.

B.5.4Copying ContainerAreas

TheqCopy() function allowsseveral elements to be copied from onecontainer to another. Here thefunction expectstwoiterators, specifyingthe first elementto be copied andthe object after thelastelement to be copied.The thirdparameter namesthe positionatwhich thefirstcopied elementshouldappear in thetarget container:

// qcopydemo/main.cpp

#include #include #include intmain() { QStringList list; list << "one"<< "two"<< "three";

QVector vect(list.size()); qCopy(list.begin(),list.end(),vect.begin()); qDebug() << vect; // output:("one","two","three") return0; } qCopyBackward()isalmostidentical to qCopy(), butexpectsthe positionofthe last elementtobecopied as thethird parameter,ratherthanthe first.Itinserts thevaluestobecopied from thespecifiedelementsofthe second containerfrom back to front, so that whenreadforwardtheyretain theircorrect order:

// backwardcopy/main.cpp

#include #include #include intmain() { QStringList list; list << "one"<< "two"<< "three";

QVector vect(5); qCopyBackward(list.begin(),list.end(),vect.end()); qDebug() << vect; // output:("","","one","two","three")

415 B Tulip: Containersand Algorithms

return0; }

Theexampleshowsthatthe target containermustalreadyhavesufficient space beforethe specified insertionpoint to hold allofthe elements that arecopied from thesourcecontainer.Here, this is ensuredfor thetargetQVector bypassing the constructorthe listsize5beforethe calltoqCopyBackward()thatcopies thethree elements in theQStringList.Thisisrequiredbecause theTulip algorithms commonly do notallocate extraitems.

B.5.5BinarySearchinSortedContainers

If alistissorted,the cost of searchingitcan be reducedfromlinear(O ( n ) )to logarithmic(O (log n ) )timewiththe help of thebinarysearch algorithm. qBina- ryFind()implementsbinarysearch in Qt. Thefunction expectsthe listtobesorted in ascendingorder,and it takesasparameterstwoSTL iterators, which mustpoint to thepositions at thebeginning andjustafter theend of theareatobesearched. Athird parameter is thevalue to be searched for.(To sort alistinascending order, qSort()isideal;see page 413.) Thefollowingexamplelooksthrough alistofnumbersfor thenumber 6, andthe iterator returned as theresultofthe calltoqBinaryFind()pointstothe thirdele- ment:

// binaryfinddemo/main.cpp

#include #include

intmain() { QListnumbers; numbers << 1<<5<< 6<< 7<< 9<<11; QList::iteratorit; it=qBinaryFind(numbers.begin(),numbers.end(),6); // it== numbers.begin() +2 qDebug() << * it; // 6

As soon as several valuesoccurthatare recognized as equalbytheoperator<() used,problems arise, however,since it is notdefinedastowhich of the(same) valuesthe returned iterator points:

// binaryfinddemo/main.cpp (continued)

numbers.clear(); numbers << 1<<6<< 6<< 6<< 9<<11; it=qBinaryFind(numbers.begin(),numbers.end(),6);

416 B.5 Algorithms

// it== numbers.begin() +1or // it== numbers.begin() +2or // it== numbers.begin() +3 qDebug() << * it;

return0; }

This doesnot matter if anyelementmatchingthe search value willsuffice, butit becomescrucial to haveawell-defined result if thelocationofthe elementfound willbeusedtodetermine theinsertpositionfor anewelement. Forsuchcases thereare themethods qLowerBound() andqUpperBound(). Theyboth expect the same parametersasqBinaryFind()and also performabinarysearch.But after this theybehavedifferently. qLowerBound() returnsaniterator pointing to thefirstoccurrence of thesearch element. If theelement sought doesnot existinthe container, theiterator remains after theinsertpositiondeemed to be suitable.Ineithercase, asubsequentinsert() insertsthe value into thecorrect position, as thefollowingexamples show:

// upperlowerbound/main.cpp

#include #include #include intmain() { QListlist; list << 3<< 3<< 6<< 6<< 6<< 8;

QList::iteratorit; it=qLowerBound(list.begin(),list.end(),5); list.insert(it,5); qDebug() << list; // output:(3,3,5,6,6,6,8)

it=qLowerBound(list.begin(),list.end(),12); list.insert(it,12); qDebug() << list; // output:(3,3,5,6,6,6,8,12)

it=qLowerBound(list.begin(),list.end(),12); list.insert(it,12); qDebug() << list; // output:(3,3,5,6,6,6,8,12,12)

In contrast to qLowerBound(), qUpperBound() places theiterator after thevalue found.Otherwiseitsharesall thepropertiesofqLowerBound(). If thesearchvalue was notfound,the iterator that was passedasfirstargument is returned. qUpperBound() andqLowerBound() can thus be used to bracket elements of the same value from both sides, as showninthe followingexample, which copies arun of equalvaluesintoanewcontainer:

417 B Tulip: Containersand Algorithms

// upperlowerbound/main.cpp (continued)

QVectorvect; vect<< 3<< 3<< 6<< 6<< 6<< 8; QVector::iteratorbegin6= qLowerBound(vect.begin(),vect.end(),6); QVector::iteratorend6= qUpperBound(vect.begin(),vect.end(),6); QVectorvect2(end6-begin6); qCopy(begin6,end6,vect2.begin()); qDebug() << vect2; // output:(6,6,6)

Bysubtractingthe twoiteratorsfromeach otherweobtainthe number of equal elements.Werequire this to create avectorwithasufficientnumber of emptyel- ements,because qCopy() doesnot insert anynewelements into thedatastructure.

B.5.6Countingthe Number of OccurencesofEqual Elements

TheqCount() method countshowoften an object or value occurs withinacon- tainer.Asthe first parameter it expectsaniterator pointing to thefirstelement to be tested,followed byan iterator pointing to theelement after thelastelement to be tested andaniterator pointing to theobject to be counted.Thismustbeof thesametype as thetype stored in thecontainer.Asthe last argument,qCount() expectsaninteger variable in which it saves thenumber of occurrences. Thefol- lowingexampleillustrates howqCount() works,using alistofinteger values:

// upperlowerbound/main.cpp (continued)

qCount(vect.begin(),vect.end(),6,count6); qDebug() << count6; // output:3 return0; }

B.5.7DeletingPointersinLists

ForQtcontainers, such as aQList,thatare filledwithpointerstoobjects, asimple list.clear()isnot sufficient, sincethisonlyremoves thepointersfromthe listand doesnot delete thelistorfreethe objectsthatare referenced bythepointers. Forthispurpose, theqDeleteAll()method is used,which exists in twovariations. Oneexpectsacontainerfilledwithpointersand deletes allthe objectsthatare pointed at bythecontainer’s elements.The otherexpectstwoiteratorsand deletes theobjectspointed at bythecontainer elements between thetwoiterators. Thefollowingcode exampleremoves from memoryallthe objectspointed at by theelementsinalistofpointers, andthenempties thelistitself:

418 B.5 Algorithms

... QListlist; list.append(newFruits("pear")); list.append(newFruits("apple")); list.append(newFruits("orange")); qDeleteAll(list); list.clear(); ...

B.5.8CheckingthatData Structures Have Identical Elements

Sometimesitisnecessaryto comparetwolists that,althoughtheyaretwodiffer- entdatastructures, maintain contents of thesametype.One exampleofthisis provided bythedatastructuresQStringList andQVector.The string list corresponds to QList,sothathere, valuesofthe same datatype (namely, QString) lie in twodifferent containers. TheqEqual() function is in apositiontocompareportionsoftwosuchstructures withone another. In ordertodothis, it expectsthree parameters: twoSTL iterators, oneofwhich marksthe beginning of theareainthe first datastructure containing theelementstobecompared,and theother,which marksthe endofthisarea. Thethird parameter is an iterator on theseconddatastructure andpointstothe elementfromwhich thecomparison(which comestoastopatthe endofthe container) should start. Thefollowingprogram accordinglycreates twocontainersand compares allthe elements for equality:

// qequaldemo/main.cpp

#include #include #include intmain() { QStringList list; list << "one"<< "two"<< "three";

QVector vect(3); vect[0]="one"; vect[1]="two"; vect[2]="three";

bool ret=qEqual(list.begin(),list.end(),vect.begin()); qDebug() << ret; // output:true

419 B Tulip: Containersand Algorithms

return0; }

If wenowchange oneofthe elements in oneofthe datastructures(such as in the vector, as follows):

vect[2]="ten";

then qEqual() willdetect inequalities.

B.5.9FillingData Structures

Sometimesitisnecessaryto fillcertain partsofalistwithavalue.InQtthisis donebytheqFill()function,which expectstwoiteratorsasparameters: thefirst onespecifiesthe beginning of theareatobeoverwritten,and thesecondspecifies theend of thearea. Thethird parameter specifies thevalue to be filledin. If wewanttooverwrite thecomplete list, weuse thebegin() andend() iteratorsof thelist:

// fillzero/main.cpp

#include #include

intmain() { QListvalues; values<< 1<<4<< 7<< 9; // contentof values:1,4,7,9 qFill(values.begin(),values.end(),0); qDebug() << values; // output:(0,0,0,0) return0; }

If weuse aQVector insteadofaQList, wecan also usethe QVectormethod fill() insteadofqFill(). Usually,QVector is thebetter choice whenfillingparts of acon- tainer withaspecified value is necessary.

B.5.10 Swapping Values

TheqSwap()function exchangesthe valuesofanytwodatacontainersofthe same type,including ordinaryvariables:

inta,b; a=1;b=2;

420 B.5 Algorithms

qSwap(a,b); qDebug() << "a="<< a<< "b="<< b;//output:a=2b=1

B.5.11 Minimum, Maximum, andThresholdValues

To determine thelargeroftwoelementsinterms of value,Qtprovides thetemplate functionsqMin() andqMax(). Each takestwoarguments, both of which mustbe of thesametype.Ifthistype is notaPOD 7 butavalue-based class, theclass must implementthe operator

// qmindemo/main.cpp

#include #include intmain() { // compareinstancesof aPOD and look forminimum intmax=qMax(100,200); // max== 200

// compareinstancesof aclass (QString):looksfor // the lexicographic minimum QString s1="Daniel"; QString s2 ="Patricia"; QString min =qMin(s1, s2); qDebug() << min;//output:"Daniel" }

If it is essentialfor avalue to lie withinaspecificrange,qBound() can be used. This template function takesthree arguments: alower bound,atestvalue,and an upperbound.Itreturnsthe upperorlower limit value if thetestvalue is larger than theupperbound or smallerthanthe lower bound,respectively.Otherwise, thetestvalue is returned. Thefollowingmethod for ahypothetical radiotunerclass ensuresthatthe user cannotselectanyfrequenciesoutside theUKW frequenciesvalid for Europe: intTuner::createValidFreq(qrealfreq) { returnqBound(87.5, freq,108.0); }

Neither qBound() norqMax() andqMin() change theinput data. Theyreturn aconst referencetothe value determinedbythefunction in each case.

7 PlainOld Datatype,thatis, alldatatypesdefinedbythelanguagesuchasint or bool.

421 B Tulip: Containersand Algorithms

B.5.12 DeterminingAbsoluteValue

TheClibraryenablesthe absolute value of an integer value to be calculated via the abs()function.Inthe same waythat thefabs()function calculates theabsolute value for floating-point numbers, Qt defines theqAbs()method, which can calcu- late theabsolutevalue for allPODs.Thisworks withall classesthatimplement the unaryminus andthe comparison operator>=and allowscomparisontoaninteger 0as theneutral element. Themethod itself doesnot allowtheprogrammerto specifyaneutral element. Thefollowingcode example, which couldappear in awindowmanagerclass,en- suresthatthe topright point of awindowcannotlie abovethe point (0, 0),that is,the topright corner,and assumesthatpositivevalueswereactuallyintended:

void WindowManager::placeWindow(WIdwin, const QPoint&topRight) { ... QPointactualPosition =qAbs(topRight);

... }

B.6Qt-specific Type Definitions

To ensure that Qt hasthe same propertiesonall supported platforms,the library uses itsowndefinitionsfor most PODs.Moredetailedinformation on therequire- mentsfor platform-independentdatatypesisprovided in abook byBrian Hook. 8 Trolltechdefinesall thesetypeswiththe typedefcommand,soitusesnomacros. This hasthe advantagesthatthe compilercan workwithsuchdefinitionsbetter andthaterror messagesrefer to theQttypes, simplifyingerror searches.

B.6.1Integer types

Signed types

qint8variablesare 8bitswide(value range: − 128 to +127 ).

qint16 valuesoccupy16 bits(value range: − 32 768 to +32 767 ).

qint32 integersuse 32 bits(value range: − 2147 483 648 to +2 147483 647).

8 WritePortableCode byBrianHook (NoStarchPress,2005).

422 B.6 Qt-specificTypeDefinitions

qint64 valuesare 64-bit values(value range: − 2 32 to +232 − 1 ). To generate such largevaluesasliterals, theQ_INT64_C()macroexists,since anumber of compilers do notsupport64-bit literalsdirectly:

qint64value=Q INT64 C(932838457459459);

qlonglongisasynonymfor qint64.

Unsigned types

quint8 valuesare 8bitsinsize(value range: 0 to 255). quint16valuestake up 16 bits(value range: 0 to 65 535). quint32valuesare 32 bitswide(value range: 0 to 4294 967 296). quint64integerstake up 64 bits(value range: 0 to + 2 64 − 1 ). To generate 64-bit literals, you should usethe Q_UINT64_C()macro, because anumber of compilers do notsupportsuchlarge integer literalsdirectly:

quint64value=Q UINT64 C(932838457459459);

Similartoqlonglong,qulonglongisasynonymfor quint64.

B.6.2Floating-point Values

With qreal, Qt defines afloating-point number of double precision. This corre- sponds to theC++ type double.

B.6.3Shortcutsfor CommonTypes

Thefollowingdefinitionsdonot improveplatformindependence, butmerelyspare theworkofhavingtoenter unsigned, for thosewho do notuse thePOD type definitions employed byQt:

ucharcorresponds to unsignedchar. uint corresponds to unsignedint. ulongcorresponds to unsignedlong. ushort corresponds to unsignedshort.

423

Index

Symbols accept()(slot) 71, 162 alphablending311 -= (operator)28 access control alphachannel274, 297 =(operator)28 forobjects63 alphatransparency 312 defining324 accessibility209 alternatingbackgroundcolor & AccessibleDescriptionRole in tables233 in labeling90 in Interview209 Amarok 264 ... AccessibleTextRole ampersand see & in menu entries108 in Interview209 animation [] see indexoperator action see QAction in SVG302 >> (operator) Action Editor anti-aliasing 277 defining325 of theDesigner108 apparent crashes 186 << see serialization operators Active-X components 47 append() 394 < (operator)407 ActiveQt 47 application 16-bit integer addition operator calling29 signed see qint16 QMultiHash 411 determiningnameof169 unsigned see quint16 QMultiMap408 generating with qmake28 32-bit integer addStretch()148 withoutGUI 44 signed see qint32 addWidget()30, 32, 34, 144, 145 quitting 36 unsigned see quint32 in QGridLayout 148 Aqua style88 64-bit integer with splitters 150 Arabic characters40 signed see qint64 with stackedlayouts 157 ARGB32297, 310 unsigned see quint64 algorithms 412–422 arranging 8-bit integer binarysearch 416–418 on agrid see QGridLayout signed see qint8 copy 415–416 horizontally see QHBoxLayout unsigned see quint8 heap sort 413 vertically see QVBoxLayout search 414–418 widgets 29 A sort see sorting artifacts aboutbox 117, 173 aligning duetoopaqueresizing151 modal118 DisplayRoledata209 ASCII114 abs()422 on agrid see QGridLayout assert() see assertions absolute value horizontally see QHBoxLayout, assertions390–391 determining422 145 assignment operator abstract classes 211 text 105 defining324 accelerator 107 vertically see QVBoxLayout Assistant 47–48 vs.shortcut 109 widgets in QSplitter150 startpage59

425 Index

useasdocumentationbrowser Cancelbutton 162 (enumerator) 43, 46 canceling selectiondialog see QColorDia- asynchronousdevice328, 330 program in case of error see log at() 229 qFatal() transparent272 attaching casting colorgradient elements to alist400 of objects see qobject cast linear 288, 310 attributes primitivedatatypes 325 radial 287 of an XMLelement 369, 371 forQKeyEvent 193 colorpalette 298 autoDefault (property) 87 forQTimerEvent 189 definingyourown 276 CDATA see QDomCDATASection colorspaces B centeringtext202 CMYK see CMYK backgroundcolor centralwidget92, 101, 363 HSV see HSV alternating(in tables) 233 cerr 386, 387 RGB see RGB in views209 changingsize coloringinQPixmap 277 backslashes opaque151 column numbers in regularexpressions70 channel in theQGridLayout 34 backwardcompatibility with RSS356 combobox 214 with Qt3 see Qt3Support characters editable 247 base class 56 counting 123 command-lineprogram details in class documentation replacing123 with Qt 44 60 checkbox 234–237 running 328 Bezier curve 309 in frontofactions see QAction, commentlines Bidi 156–157 selectable removing in afile 320 bidirectional languages see Bidi checking inputs see validator comments binarynumber CheckStateRole 234 readingout fromXML docu- converting to string72 in Interview209 ments see QDomComment binarysearch child objects comparing in sorted containers 416–418 andautomatic memory man- listcontents419 blindpersons agement31 objects290 alternativedescription for209 Chinesecharacters40 QStringList with QVec- blocking calls circle, drawing278 tor< QString > 419 with waitForDisconnected() class comparison operator 409 341 documentation59 compatibility with waitForReadyRead() 329, name 92 with Qt3 see Qt3Support 341 clear mode 314 compiler breadth-firstsearch 404 clicked()(signal)36 affectinglength of compiling Buddy 90 clip region 308 44 buffer318, 323, 404 clipboard201–205 compiling button clipping 280, 307–308, 315 Qt 23,259 honoringstyle guidefor 83 closingwindowsautomatically 62 aQtprogram 27–29 push see QPushButton CMYK274 aVisualStudioproject 29 radio see QRadioButton Code::Blocks 52 compositing tool see toolbutton collapsible widgets 150 Porter-Duff 310–315 ByteArray see QByteArray color see QColor compositionoperators310 available options272 .config 136 C forDisplayRole data 209 CONFIG (qmake variable) 386 callback functions 35 predefined see GlobalColor configuration dialog 161

426 Index

implementation in KDE157 crash setting 69 with QStackedLayout 34 apparent 186 defaultsize configure 23 duetoconst char* 388 defining see sizeHint() connection duringInterview programming delegate 210, 228, 245–247 closing336 225 fordatabase queries267 delayed see queued connec- duetoinvalid indices225 QItem234 tions only in thereleaseversion 392 DELETE statement(SQL) 262 connectSlotsByName() 97 when usingDesigner-generated deleting QObject-based objects336 const classes 93 demarshalling322 as argument type 71 critical errors demo programs correctness400 defining see qCritical() included 58 declarationofget methods325 critical messages see error, dialog deserializing 322 andinheritance 227 CRT ERROR Designer 48–49, 81–100 iterator 396, 412 generating 388 Action Editor 108 constchar* 387 cursor (database) 395 deleting widgets 82 as reasonfor crashing388 custom widget99 enablingdockwindow mode const cast 247 Cyrillic characters40 81 consumer-producerpattern 342– fileformat49 345 D PropertyEditor 56, 109 container see Tulip data structures Resource Editor 58, 99 contenthandler filling420 workingwithviewclasses214– registeringwithQXmlSim- thread-dependent345–347 216 pleReader 365 database see QtSqlmodule Designer-generatedfile see ui files forSAX see QXmlCon- drivers258 destinationmode 311 tentHandler support42, 43, 45 DestinationAtop mode 313 contents temporary265 DestinationInmode 312 readingout of avector 401 dataChanged()(signal)229, 237 DestinationOut mode 312, 314 context datastream see QDataStream DestinationOvermode 311 in translations 51, 377, 380 date details see QDate device control see widget DB2database driver 258 asynchronous328, 330 element see widget DBus 43, 47 dialog see QDialog conversion deadlock 344 closing71 QPixmapsinQImages 297 debuglibraries colorselection see QColorDia- converting generating in Windows28 log to local8-bit encoding see installinginUnix28 creatingwithDesigner82 toLocal8Bit() debugversion forcritical messages see error, to Unicode see fromLocal8Bit() foranapplication64 dialog coordinate system generating 28 editor see Designer of thegrid layout 34 debugging 385–392 fileselection see QFileDialog scaling292 your ownmodel227 hiding see hide() transforming see QMatrix debugging functions forinformation see QMessage- coordinates changingoutputof see mes- Box storing see QPoint sagehandler as main window 62 copyingcontainer areas 415–416 Qt-dependent386 formessages see QMessage- counting identicalelements418 DecorationRole Box Courier106 in Interview209 modal161–162 cout 386 defaultbutton 68,87 non-modal163–164, 176

427 Index

print see QPrintDialog DOM 43, 45, 353, 366–373 removing at thebeginning and forquestions see question dia- element see QDomElement endofline322 log nodes see QDomNode encapsulation95, 96 ready-made 166–184 SVGmanipulation46, 301 Enterkey see Returnkey semi-modal 164 dotted lines 295 enum 105 vs.statusbar 124 double 423 enumeration types foruncritical messages see in- double buffering282 alignment 105 formationdialog switch off283 CheckState 209 forwarnings see warningdia- drag anddrop194–201, 323 DockWidgetFeatures 131 log buginQt4.1.2 245 EchoMode 181 your ownfile selection214– of images 194 format 297 221 implementing in models 241– orientation224 dictionary see QMap 245 policy146 dimensions drawing SocketError 340 setting see setGeometry() with Qt 271–317 environment storing see QSize re-˜awidget296 influencing ˜ofaprocess328 direct connections 347 re-˜ thescreen 280 environment variable Direct Object Model see DOM on widgets 280–283 influencing ˜ofaprocess319, directory drawingpath see Painterpaths 329 defaulticon330 drop actions LANG 329 listing contents 332 types244 QT FATAL WARNINGS 388 operations see QDir DTD QT NO DEBUG OUTPUT386 selecting178 handlinginSAX see QXmlDTD- QT NO WARNING OUTPUT388 directoryhierarchy Handler readingout ˜ofaprocess329 presenting 212–214 dynamictext error DisplayRole generating 124 critical see qCritical() font type 209 dynamic cast 248 dialog 173 in Interview209, 224 fatal see qFatal() text alignment 209 E issuing387–388 text color209 ECMA script linker392 division in SVG301 in multipleinheritance 97 by zero 388 editor non-integrated layouts67 DLLs Kate 55 searching for see debugging of theQtdebug libraries28 widget see QTextEdit staticMetaObject 65 DNSnameresolution see EditRole unknown signalsorslots 64 QHostInfo in Interview209, 225 unresolvedsignals or slots 79 dockwindow 101, 130–136 element-based views251–255 unresolvedsymbols 65, 79 dockwindow mode ellipse vtbl-linkererror message392 enablinginDesigner81 drawing278, 295 errorhandler DocumentObject Model see DOM fillingwithpattern 295 registeringwithQXmlSim- document type ellipsisinmenuentries108 pleReader 365 determiningfromXML files embeddeddatabases 264 forSAX see QXmlErrorHandler 369 embeddedversion 20 errormessages documentation47 emit (signaldesignator)78 issuing174 browser see Assistant empty lines errorsource startpage59 removing in afile 320 with QMap usage405 on theWeb 59 empty space Esc key162

428 Index

assigning 168 your own214–221 fractions /etc/xdg136 filename entering see getDouble() eventfilter 190, 247 selecting178 frame shape85 eventhandler 35, 97, 186–190 fill()420 free text fordragand drop 202 fillingdatastructures see qFill() readingin181 fordropping 199 filtering212 freedesktop.org43 eventlistener35 data in models 231 fromLocal8Bit() 330, 331 eventloop 26, 31, 185–186, 320 of datasets 234 FTP see QFtp ending 26, 36 Firefox client 45 fornon-graphicprograms44 non-modaldialogs 164 programming 332 flicker G starting in dialogs 162 avoiding see double buffering GCC forthreads350–352 screen 283 undefinedsymbols 65 events 35,185–201 floating dockwindows133 unresolvedsymbols 79 occurringwhendropping 198– floating number see floating-point forWindows52 199 types getmethod56 in threads352 floating-pointinputs see getDou- getDouble() 180 triggeringmanually 188 ble() getInteger()179 example(directory) see example floating-pointtypes GIFsupport23 programs in Qt 423 GlobalColor(enumerator)272 exampleprograms font metrics see QFontMetrics GNOME83 included 58 font selectiondialog see QFontDia- GoogleEarth 19 exclusiveOr see XOR log GPL exec() 26, 162 font type versionfor Windows52 Extended Markup Language see changing106, 251, 253 gradient XML forDisplayRole data 209 linear 288, 310 FontRole radial 287 F in Interview209, 251 graphics fabs() 422 fopen() 113 integrating57 factorymethod153 forloop 394–396 MIME encoding 195 fatalerror foreach151, 411–413 scaling100 defining see qFatal() andQDomList370 graying father see parent widget foreignkey relations actions129 FiFo container see QQueue resolving266 grid layout see QGridLayout file forever341 groups access 320–322 forking337 of actions128 defaulticon330 format in configuration files 138 dialog see QFileDialog images 297 GUI format see format QDataStream 323 avoiding locking 332, 337 open113, 320 QImage297 design viamouse click see De- restricting selectiontodirecto- RSS355 signer ries178 translationsources377 editor see Designer save see saveFile() format strings thread 338 selectingindividual175 fordebug functions 386 GUI classes selectingseveral 177 in debugoutput386 integrating43 FILEpointer 113, 320, 322 FORMS (qmake variable) 49 notintegrating 43 fileselection dialog see QFileDialog forwarddeclaration 63,122

429 Index

H HSV273 QLineEdit handle 34 colorselection dialog 275 restricting 70 handler HTML 354, 367 input/outputinterfaces317–336 classes forSAX 355–361 centeringtext202 opening 319 forevents see eventhandler in QMessageBoxes 171, 390 input/outputoperations handles125 fortables226 iconsfor 330 adjusting153 in tooltips226 INSERT statement(SQL) 262 hanging HTTP see QHTTP installation path becauseimagesare too large client 45 definingfor Qt 24 305 integerinputs see getInteger() hash table 409–411 I integertypes hash value I/O317–336 in Qt 422 calculating410 icon bar see toolbar integervalues hash-based amounts411 icons125 accepting see getInteger() hash-based sets412 forinput/outputoperations checking duringinput 70 hasNext()397 330 converting strings to 72, 73 hasPrevious()397 in Interview209 inter-processcommunication 337 header columns selectingfor actions127, 129 fortable andtreeviews 211, selectingfor messageboxes database driver 258 233 167 internationalization375–381 header file26, 43 standard330 interprocess communication322 adding to projects 106 in thestatusbar 121 intersection multiplyincluded see include identity matrix see unitmatrix forming412 guards images see graphics Interview207–255 HEADERS (qmake directive) 64, 106 implicit sharing40, 412 anddatabase access 258, 265– heap includeguards62, 104 270 generating objectson32 indexoperator 229, 400 introspection97 with threads337 access to QLinkedList 401 invisible widgets heap sort 413 access to QVector 402 causes of 67 Hebrew characters40, 156 with QHash 409 IP addresses333 height with QMultiMap408 IPv6333 readingout ˜ofsplitterwidgets vs.value() 405, 409 isNull()176 151 individual preferences see user ISO8859-1 114 setting see setGeometry() scope ISOLatin 1114 help infiniteloop item 207, 251 browser see Assistant with forever341 cloning254 longer help text 110, 129 info box see QMessageBox sorting255 hexadecimalnumber on theprogram 117, 173 item-based display207 checking as inputvalue 70 informationdialog171–172 iterator 394–398 converting to string72 inheritance immutable 396 hide() 304 multiple see multipleinheri- Java-style 396–398, 404 home directory tance mutable 396 determining176 sequence 97 forQMap405 horizontally input forQQueue404 aligning 145 checking 70 forQStack404 arranging see QHBoxLayout dialogs see QInputDialog STL-style 395–396 host name 333 fieldsfor line-by-lineinput see unchangeable 396

430 Index

J adjustingfor labels 86 using336 JavaScriptinSVG 301 libQtCore debug.so 28 lrelease49, 376 JUnit46 library ls,callingasexternalprogram 332 generating with qmake28 lupdate 49, 376, 377 K LiFo container see QStack recordingentriesoutside classes Kate 55 lightweight processes see threads 381 KDE linedrawing see QPen Amarok 264 lineedit see QLineEdit M implementation of configura- linenumbers MacOSX tion dialogs 157 in theQGridLayout 34 .plistfiles136 mediaURL 200 linewrap projectfiles53 non-modaldialogs 164 preventing 106 Qt installation 23 QMimeDatawithseveral URLs removing 123 specialfeatures of qmake53 200 Linguist 49–51, 376–378 storingsettings137 Rundialog164 linker macros style88 frequent errors 392 foreach see foreach KDevelop 53 problems with undefined˜sym- forever see forever keybinding bols 104 NDEBUG 390 application-wide 109 problems with unresolved˜ platform-dependent389 keyboard usage107 symbols79 Q ASSERT 390, 392 Linux Q ASSERT X391, 392 Hebrew 156 printing largeimages305 Q CHECK PTR 391–392 listview157–159, 210 Q FOREACH412 L withoutInterview see QList- Q UNUSED 223 label see QLabel Widget Q WS MAC389 labelingtext85 selectingcolumn fromthe Q WS QWS389 landscape(printformat) 306 model227 Q WS WIN389 LANG (environment variable) 329 string-based 207, 221 Q WS X11389 languagesoriented from right to lists see QList QT TR NOOP 381 left see Bidi changeable iterators396 MagicNumber 326 layout filling420 main widget26 adding widgets to see addWid- linked see QLinkedList centralwidget105 get() QQueue see QQueue creatingwithDesigner82 automatic see layout system QStringList see QStringList separating page listfrom153 grid see QGridLayout QVector see QVector size grip 118 horizontal see QHBoxLayout swapping values 420 status bar118 insertingother layoutsinto your own400 main window see main widget 149–150 locale 188 class see QMainWindow manual141–143 determining378 MainActor 19 nested 65–68, 149–150 forcing default331 make 29 removing in Designer 84 forcing standard329 Makefile stretch67, 83 localization generating with qmake28 vertical see QVBoxLayout of an application49 Makefile.DebugPackage 28 layout system 29, 141–161 of images 58 Makefile.ReleasePackage 28 advantages 31 influence on thelayout143 marshalling322 LCDdisplay see QLCDNumber of shortcuts110, 127 matrix lettering see labelingtext loopbackinterface inverse291

431 Index

singular 291 modalityofadialog 162 nmake 29 maxima model208 note determining290, 421 fordatabase usage265–270 yellow see tooltip maximum size making writable 227 number() 72, 73 setting 151 model-view concept see Interview memory monitor O allocating 391 flicker283 object cast see qobject cast bottleneck391 monospaced font objectName (property) 92 memory management 31–33 setting 106 objects automatic 31, 68 Motif47 counting equal418 of item classes 251 mouse generating on thestack 32, 33 menu bar101 events triggeredby see hierarchy of 31–33 adding in Designer 107–108 QMouseEvent namesof85 insertingactions into 127 wheel 293, 296 serializing 322–328 menu entry mousePressEvent()295 tree structure of 31 clicking 112 moving see translation ODBC database driver 258 definingfont129 Mozilla-likesplitterhandle153 OK button 162 menu separator 108 multipleinheritance Onlinehelp see documentation messagebus 43, 47 of Designer-generatedinter- opaque273, 274 messagedialog see QMessageBox faces95 resizing 151 messagehandler 388 restrictions97 OpenGLsupport42, 45 installing389 multipleselection of files 216 OpenSUSE messagewindow see QMessageBox mutable iterator 396 installingSQL support259 meta-object compiler see moc mutex 342 operators MFC47 MySQL 45 -= 28 Microsoft database driver 258 =28, 324 SQLserverdatabase driver 258 establishing connection260 << see serialization operators styleguide 169 making queries261 addition ˜for associative arrays VisualStudio see VisualStudio problems with stored proce- 411 Windows see Windows(Mi- dures263 addition ˜for dictionaries408 crosoft) addition ˜for QMap 408 MIME type N assignment 399 forthe clipboard201 name comparison 409 fordragand drop 194–195, of aclass created in Designer composition310 242 92 copy 399 your own194 determininganapplication’s definingassignment 324 MinGW24, 52 169 definingserialization325 minima resolution 333 index229, 405, 408, 409 determining290, 421 namespace less than 405–407 minimum size Qt 105 new31 defining289 NDEBUG (macro) 390 operator==()409 setting 143, 145, 150 network operator>>() 325 moc56–57, 64, 79, 392 integrating˜support43, 332 operator< () 405, 406, 413 problems 392 programming 42, 45, 332–336 operator<<() 325 andRTTI 248 newoperator 31 serialization 387 moc file56 newsfeed 356 Or modaldialogs see dialog, modal next() 397 exclusive see XOR

432 Index

orientationenumerator 224 PostgreSQL 45 of views216 overwriting database driver 259 PropertyEditor 56, 85, 109 areas in data structures see integratingdrivers260 proxy333 qFill() preferences proxymodel231, 270 saving136–140 designing237–241 P system-wide see system scope public (objects) 63 page bar user-defined see user scope push back() 394 separating frommainwidget preview 153 in theDesigner88 Q page margins previous() 397 Q ASSERT (macro) 390, 392 taking into accountwhenprint- printdialog see QPrintDialog Q ASSERT X(macro) 391, 392 ing306 printinterface see QPrinter Q CHECK PTR (macro) 391–392 paintevent 280, 283 printf() 385, 386, 388 Q FOREACH(macro) 412 forcing 296 printing 302, 305–307 Q INT64 C()(macro) 423 paintbrush see QBrush .pro file see projectfile Q OBJECT (macro) 63, 64, 104 Painterpaths 280, 309–310, 314 problems with moc 392 Q UNUSED (macro) 223 paintEvent() 280–282 processes Q WS MAC(macro) 389 palette see colorpalette controlling328–332 Q WS QWS(macro) 389 parent widget30 starting 319, 328–332 Q WS WIN(macro) 389 PDF processing logic Q WS X11(macro) 389 generating 306 separating fromGUI 74–76 QAbstractButton 42,87 peekNext() 397 program crash see crash QAbstractItemDelegate245 peekPrevious() 397 program names QAbstractItemModel211, 266 pen see QPen definingapplication-wide 137 QAbstractItemView 211 phrasebook determining169 QAbstractListModel211 fortranslations377 progressbar QAbstractProxyModel212, 231 piechart 283 duringadownload119 QAbstractScrollArea211 Plastique style88 in thestatusbar 121 QAbstractSocket318, 333 .plistfiles projectcreation see qmake QAbstractTableModel211 generating 136 projectfile QAction108–110, 126–128 plugins generating with qmake27 chooseicon109 fordatabase driver 259 integratingDesignerfiles49 grouped128 PODsinQt325, 422 integratingGUI descriptions49 select icon 127, 129 pointer forMac OS X53 selectable 128 between twoelements396 forMicrosoft Visual Studio 52 shortcuts110 checking validityof391–392 specifying Qt librariestobe signal when clicking 112 deleting in lists 418 linked43 toggling126, 129 polygon see QPolygon forXcode 53 QActionGroup 128 Porter-Duff 310 properties56, 85 qApp 112 porting in class documentation60 QApplication26 Qt 3programstoQt4 46 of QAction109–110 base class for44 Portland project175 of QMessageBox 166 andQDesktopWidget304 position of QSplitter150 quit() 36 definingfor awidget152 of QTextEdit106 translate() see translate() setting forawidget see setGe- querying 56, 85 QAssistantClient 46 ometry() setting 56, 85 qBound() 421 Post-It note see tooltip size of 143, 150 QBoxLayout 42, 145

433 Index

QBrush 277 qEqual() 419 QItemSelectionModel see selection definingcolor 277 QErrorMessage174 model tiledpattern 295 qFatal()386, 388, 390 QLabel QBuffer 318, 323, 361, 404 QFile 113, 318, 320–322 aligning text 105 setting read positiontobegin- QFileDialog 113, 175–178 assigning shortcuts90 ning 365 flags 175 displaying pixmap 197, 278 QButtonBox83 qFill()420 displaying strings 38 QByteArray114, 139, 318 qFind() 414 markup 102 QCheckBox QFont182 properties85 in theQtinheritance hierarchy QFontDialog 182–183 in theQtinheritance hierarchy 42 QFontMetrics288 41 QClipboard201–205 QFrame41, 42 setAlignment() 105 QColor 40 QFtp 333 setNum() (slot) 38 in theQtinheritance hierarchy QGridLayout 33–34, 148–149, 303, setPixmap() 197, 277 42 362 setText()38, 56, 124 QColorDialog 275–276 addWidget()34 setting image197, 278 QComboBox see combobox column numbers34 setting newvalue 38 qCopy() 415 coordinate system 34 setting text 56, 85, 124 qCopyBackward()415 with Designer 84 text (property) 56 QCoreApplication44, 320, 338 linenumbers34 text() 56 qCount() 418 in theQtinheritance hierarchy QLayout 42, 143–144 qCritical() 386–388 42 addLayout() 67 QDataStream 243, 322–327 QHash 283, 409–411 addStretch()68 definingversion of theformat qHash() 409 in theQtDesigner84 323, 326 QHBoxLayout33 QLCDNumber 186 readinginlinebyline331 in Designer 84 QLinearGradient 288, 310 QDate179, 189 in theQtinheritance hierarchy QLineEdit qDebug() 152, 386–388, 390 42 changinginput 73 displaying database errors 261 QHeaderView 211 EchoMode enumerator 181 qDeleteAll()399, 418 QHostAddress333 restricting input70 QDesktopWidget304 QHostInfo 333 setValidator() 70 QDialog161–184 QHttp333, 350, 361, 363 textChanged()(Signal) 71 avoiding bloating164 QIcon 127 textChanged()(signal)234 classes inherited from ˜asmain QImage40, 297–300 QLinkedList 398, 401 window 62 compositionmodes310–315 access viaindex operator 401 extensions 164 converting in QPixmap 297 QList398–402 QDir 176 QInputDialog 179–182 as base class forQQueue404 QDirModel212–214 qInstallMsgHandler() 389 mutable iterator 396 overloaded index()method213 qint16 422 QListView 210 QDomCDATASection 370 qint32 422 QListWidget157, 251 QDomComment 370 qint64 422 QListWidgetItem157, 330, 332 QDomDocument366, 367, 370 qint8422 QLocale378 QDomDocumentFragment 371 QIntValidator 70 qlonglong423 QDomDocumentType369 QIODevice 317–320, 322 qLowerBound() 417–418 QDomElement366, 369, 370 networksubclasses 333 QMainWindow 101–140, 362 QDomNode 367 opening 319 setCentralWidget()105 QDomText 370 QItemDelegate210, 228, 234, 245 qmake27–29, 51–56, 392

434 Index

choosingQtlibrariestobe deleting ˜-basedobjects336 42 linked43, 257 derivingfrom104 setDefault() 69 FORMS variable49, 91 inherited classes 31 size policy144 generating applicationwith28 inheritingfrom39, 63–65 QQueue 342, 395, 404 generating librarywith28 andmoc 56 QRadioButton generating Makefilewith28 property()56 in theQtinheritance hierarchy HEADERS directive106 setProperty() 56, 85 42 help when inheritingfromQOb- tr() see tr() qrcfile 57 ject 64 qobject cast 247 qreal 423 includinganSQL module 257 QPaintDevice 276, 302 QRect278–279 integratingDesignerfiles49, QPainter 276–278, 280, 282–290 floating-pointvariants see 91 QPainterPath see Painterpaths QRectF librariestobelinkedbydefault QPen 277 moving 288 43 definingcolor 277 shrinking 280 andmoc 56 definingthickness 277 QRectF 279 QT variable43 drawingdotted line295 QRegExpValidator 70 RESOURCES directive57 QPixmap QRgb 272, 275 SOURCESdirective 28, 106 converting in QImage297 QSet 411–412 specialfeatures on MacOSX extracting fromdatastream QSettings136, 151 53 200 QSize278 specialfeatures on Windows fillingwithcolor 277 floating-pointvariants see 28 making screenshots 304 QSizeF TEMPLATE directive28, 106 vs.QImage297 QSizeF 279 TRANSLATIONSdirective 376 QPoint 278 QSizeHint209 QMAKEFLAGS(environment vari- floating-pointvariants see QSlider 38 able)28 QPointF setvalue 38 QMap 404–407 QPointF279 setValue() (slot) 38 QMapIterator 405 QPolygon 279, 308 valuechange see value- QMatrix 290–295 definingsmallest possible rect- Changed()(Signal) qMax() 290, 421 angle279 valueChanged() see value- QMenu127 floating-pointvariants see Changed()(Signal) QMessageBox 116, 166–173, 390 QPolygonF qSort()413 modal118, 168 QPolygonF279 QSortFilterProxyModel231, 232 QMimeData194 qPrintable() 328, 387 QSpinBox 38, 180 qMin() 290, 421 QPrintDialog302, 306 setvalue 38 QModelIndex 208, 218, 219 QPrinter276, 302, 305–307 setValue() (slot) 38 QMouseEvent186 QProcess319, 328–332 valuechange see value- QMultiHash 411 asynchronoususage330–332 Changed()(Signal) QMultiMap407–408 synchronoususage329 valueChanged() see value- QMutableListIterator 396 QProxyModel212, 231 Changed()(Signal) QMutex 342 QPushButton 35 QSplitter see splitter QMutexLocker344 clicked()signal36 QSplitterHandle153 QNetworkProxy333 clicking on 36 QSqlDatabase 260 QObject 39–40 converting to atogglebutton QSqlError261 andautomatic memory man- 166 QSqlQuery261 agement68 defaultbutton 87 QSqlRecord262 connect()36, 38 in theQtinheritance hierarchy QSqlRelationalDelegate245, 267

435 Index

QSqlRelationalTableModel266, 268 332, 354 QTimerEvent 188, 189 problems in Qt 4.1270 Qt 4.042 QtNetwork 42, 45, 332 QSqlTableModel265–266, 268 Qt 4.143 QtOpenGL42, 45 qStableSort() 413 Qt 4.243 QTranslator 378, 379 QStack 403–404 Qt Assistant see Assistant QTreeView210 QStackedLayout see stacked, lay- Qt Designer see Designer QTreeWidget251, 252 out Qt Linguist see Linguist QtSql 43, 45, 257–270 QStackedWidget see stacked, wid- Qt Solutions47 displaying database errors 261 get Qt-3 classes 46 establishing connection260 QStandardItemModel212, 247, Qt3Support43, 46 making queries261 249–250, 356, 357, 365 QT FATAL WARNINGS (environment modelfor Interview265–270 QStatusBar 118 variable) 388 temporarydatabase 265 addPermanentWidget()121 QT NO DEBUG OUTPUT(environ- QtSvg 43, 46 addWidget()120 ment variable) 386 QtXml 43, 45, 353–373 clearMessage()(slot) 120 QT NO WARNING OUTPUT(envi- QUdpSocket318, 333 showMessage()(slot) 120 ronment variable) 388 question dialog 169–171 QString40 QT TR NOOP (macro) 381 vs.informationdialog171 arg()123 QTableView 211 questions checking if it is empty 182 QTableWidget251, 253 asking169 converting 8-bit text to/from QtCore42, 44, 332 queued connections347, 348 see fromLocal8Bit() debugvariations28 queues see QQueue converting to number values QTcpServer 333, 338 quint16 423 72 QTcpSocket40, 318, 333, 334, 340 quint32 423 internal encoding 114 QtDBus43, 47 quint64 423 outputtingvia cout 328 QtDebug 387 quint8 423 in theQtinheritance hierarchy QTemporaryFile 318 quit() 26, 36, 112 42 QTestLib43, 46 qulonglong 423 QStringList 395 QTextBrowser 349, 361 qUpperBound() 417–418 comparingwithQVec- QTextCodec 114 QUrl 195, 364 tor< QString > 419 QTextDocument 123 converting to filepath200 QStringListModel212, 221 redo 117 QValidator 70 QStyle 330 undo117 QVariant 135, 200 QSvgRenderer301 QTextEdit106, 115, 142 converting into native data QSvgWidget301 opennew document 116 types138 qSwap()420 preventing linewrap106 converting to astring366 Qt setDocument() 116 QVBoxLayout30, 144–145 contents of individual libraries setting font type 106 addWidget() see addWidget() 42 size policy144 creatinginDesigner84 German translationsources text format 114 in theQtinheritance hierarchy 380 undo see QTextDocument 42 informationbox about173 QtGlobal 386 QVector 401–402 installing23 QtGui 42, 44–45 filling420 opensource editionfor Win- QThread see threads QVector< QPoint> 279 dows24 QThreadStorage345–347 QWaitCondition 342 size 42 QTime 188 qWarning() 341, 388 source code 23 QTimer334, 350 QWidget304 QT (qmake variable) 43, 257, 301, singleshot timer304 as abasisfor allcontrol ele-

436 Index

ments40 restricting theviewtospecific andstretch factors153 andlayout141, 143 datasets see filtering screenreader 209 resize() 105 retranslateUi() 92 screenshot 302–305 setWindowTitle()69, 105 Returnkey scroll wheel 293, 296 QXmlContentHandler 355, 356 assigning168 search QXmlDefaultHandler 355–361 RGB in sorted containers 416–418 QXmlDTDHandler 355 vs.CMYK274 forspecific datasets see filter- QXmlEntityResolver355 colorselection dialog see ing QXmlErrorHandler 355, 356 QColorDialog in unsortedcontainers414– QXmlInputSource 365 right alignment 145 415 QXmlLexicalHandler 355 roles135 SELECT queries(SQL) 262 QXmlSimpleReader 365 AccessibleDescriptionRole 209 displaying results267 AccessibleTextRole 209 selectable entries see checkbox R CheckStateRole 209, 234 selectionmodel217 rcc 57–58 DecorationRole 209 selectionMode (property) 216 reading DisplayRole209, 224 semi-modal dialogs see dialog from right to left see Bidi EditRole 209, 225 separator rectangle see QRect FontRole209, 251 in menus 108 redo 117 SizeHintRole209 sequential connections318 reference system StatusTipRole209 serialization 322, 387 fixed280, 281 TextAlignmentRole 209 serialization operators387 reflection97 TextColorRole209 defining325 Registry ToolTipRole209, 225, 251 setdifference 411 createpath136 UserRole209 setmethod56 savingapplicationdata136 WhatsThisRole209 setobject see QSet regularexpressions root element setoperations411 allowing duringfiltering234 determininginXML files 369 setGeometry()141 in Qt 70 rotation 293 setupUi()92, 111 in validators70 of thecoordinate system 295 sharing reject() (slot) 71,162 RSSparser 355–361 implicit 40, 412 relational databases see QtSql RTTI 248 shearing293 module shortcuts109 release version S assigning with Designer 90 generating 28 saveFile() 115 forlabels see Buddy repaint280 saveFileAs() 115 window-wide107 resize() 143 saving show() 163, 164 resizeEvent()143 afile see saveFile() side-effects Resource Editor see Designer splitterpositions 151 in debuginstructions 392 resource file109 under newname see save- signal resources FileAs() binding to slots by name con- choosingicons foractions from SAX45, 353–366 vention97 109 defaulthandler see QXmlDe- declaring77 compiler see rcc faultHandler sending78 external 57–58, 99 scaling292 SIGNAL() 36 file57 screen signalsand slots 35 localizationofimages58 redrawing280 connecting with Designer 88 RESOURCES (qmake directive) 57 screen size number of arguments39

437 Index

sequence of functioncall38 removing frombeginning and stretch67, 83, 152 with thread usage347–350 endofstring123 stretchfactor 146–148 simplified() 123 spin box see QSpinBox in theQGridLayout 148 singleshot timer304 splitter34, 130, 150–157 andscreen size 153 size definingsizeof˜widgets 151 in thesplitterlayout152 changing87 savingpositionof151 string definingfixed281 SQLdatabases converting to numbers72, 73 fixing 142 supportinQt see QtSql with dynamicelements124 savingawindow’s 139 SQLite 45 list see QStringList storing see QSize compilingsupport259 objects see QString size grip database driver 259 reading181 formainwindows118 integratingdrivers260 string-oriented connections318 size hint using264–265 strings providinga see sizeHint() stack selecting181 size policy144, 146 generating objectson33 style88, 330 sizeHint() 143, 289 stacked styleguide forSVG files 301 layout 34, 157–160 on headersinquestiondialogs SizeHintRole widget157 169 in Interview209 stacks see QStack Microsoft 169 Skype19 standardconnection(database) sub-tree slider see QSlider 260 insertingXML into 372 slot 36 standardlocale SVG43, 46, 300–302 binding to signalsbynamecon- forcing 329, 331 Basic301 vention97 standardoutput fileloading 301 declaring71–72 directingoutputto321 supportedprofiles 46 SLOT() 36 Standard Template Library see STL Tiny 301 socket standardwidgets swapping communicationvia 318, 334, manipulating 99 values 420 338 standard-error output see stderr Sybase Adaptive Server Socks 5333 static cast 189, 193 database driver 259 sorting212, 413–414 status bar101, 110, 114, 118–124 synchronousprocesses329 data in models 231 data to be displayedinInter- system scope itemsinviewwidgets 255 view 209 forpreferences136, 137 with QMap 405 normal message119, 120 system time see time sortingindicator permanentmessage120, 121 in tree views233 temporarymessage119, 120 T source mode 311 status line see status bar taborder 192 SourceAtop mode 313 status tip tabsequence 48, 87, 89 SourceIn mode 312 setting 127 tab-completion 245 SourceOutmode 312 StatusTipRole table layout see QGridLayout SourceOver mode 311 in Interview209 table view SOURCES(qmakedirective)28, 106 stderr 386 of data 211 spacer 82 STL of SQLtables265–266 in Designer 82 compatibilitywithQt393 withoutInterview see QTable- spaces iterators395–396 Widget distributingwithstretches67, stored procedures 263 tabulator see tabsequence 83 andSQLite264 tags 366

438 Index

tararchive toolbar101, 125–126 ui files listing contents 328 adding in theDesigner109 format 49 TEMPLATE (qmake directive) 28, tooltip110, 209, 251, 360 integratinginprojects91–97 106 setting foractions 129 uic49, 92 temporarydata ToolTipRole uint 423 storing318 in Interview209, 225, 251, 360 ulong423 temporarydatabase 265 top-levelwidget86, 162 undo117 temporarydirectory tr() 50, 105, 376 stack117 determining176, 319 dynamictextparts 123 undocumented features temporaryfile 318 transactions(database)264 eventfilter 247 temporarymemory404 transformation matrix 290 Unicode 40, 114 test cases43, 46 translate()50 converting local8-bit encoded Testing translation288, 291 data to 330, 331 inverted Layouts157 of applications to other lan- union 135 text guages 49, 375–378 of sets 412 colorfor DisplayRoledata209 context see context unitmatrix 291–292 document see QTextDocument of thecoordinate system 295 unittests 43,46 dynamically generating 124 sources377 Unix editor 101, see QTextEdit TRANSLATIONS(qmakedirective) behavior of qFatal()388 files forUnixand Windows114 376 unsignedchar window formessages see transparency shortform423 QMessageBox as color272 unsignedint TextAlignmentRole of pixels 297 shortform423 in Interview209 traversing 395 unsignedlong textChanged()(Signal) 73 tree structure shortform423 textChanged()(signal)234 of Qt classes 31 unsignedshort TextColorRole tree view 210 shortform423 in Interview209 of SQLtables265 UPDATE statement(SQL) 262 threads40, 337–352 withoutInterview see URL see QUrl synchronizing341–345 QTreeWidget usability workingwithQSettings136 tree-basedalgorithms 404 accelerators107 tiledpattern 295 Tulip 393–423 arrangingmenubar entries time type conversion 126 current system 188 with signal/slotconnections 38 avoiding bloated dialogs 164 determining333 types definingthe defaultbutton 87 time object see QTime PODs325, 422 with drag anddrop197 timer188, 189, 295 Qt-specific 422 headersinquestiondialogs signal call aftertimeout 335 typewriter font see monospaced 169 timeserver 336 font localizingshortcuts110 titlebar see window title non-modaldialogs 163 toggling U push buttonsfor information actions126, 133 Ubuntu dialogs 171 dockwindows133 installingSQL supportlater on status messagevs. dialog 124 viaQPushButton 166 259 stretchfactor andscreen size toInt() 72, 73 problems with qmake27 153 toLocal8Bit() 328 uchar423 tabsequence 89 tool button 214 UDP318 when usingQMessageBox 116

439 Index

yes-no questions170 file27 opensource edition24 usecase platform-specific extensions consumer/producer342–345 W 47 user input see input WA QuitOnClose62 Registry see Registry user interfacecompiler see uic wait condition342–344 specialfeatures of qmake28 user scope warningdialog172 text encoding 114 forpreferences136, 137 warnings 388 Windows-XP style88 UserRole webserver words in Interview209, 360, 366 asynchronouscommunication counting 123 ushort 423 with see QHttp worker threads347 UTF-8 see Unicode What’s This?help110, 129, 209 workingdirectory WhatsThisRoleinInterview 209 changingto332 V whitespace 123 determining176 validator 70 wholenumber see integertypes writing valueranges widget29 allowing in amodel227 of floating-pointtypes 423 adding to alayout see addWid- WYSIWYGGUI editor see Designer of integertypes 422 get() value() changingpropertiesinthe De- X vs.index operator 405, 409 signer 48 X11 valueChanged() (Signal) 38 changingsize105 andQImage297 values collapsible 150 Xcode 53 limiting421 creatingwithDesigner82 XHTML354, 367 vector see QVector drawingon280, 283 XML42, 43, 45, 301, 353–373 graphics with SVG see SVG removing in theDesigner48 determiningdocument type vendor details your own42 369 definingapplication-wide 137 width determiningroot element369 versioncontrol 28 readingout ˜ofsplitterwidgets integratingsupportfor 43 vertically arrangingalayout see 151 parser 45 QVBoxLayout setting see setGeometry() readingfiles367 view 208 wildcard readingout attributes369 backgroundcolor 209 usingwhenfiltering234 translationsource format 377 editingentries246 window XOR410 element-based withoutmodel changingsize87, 105, 118 mode 314 251 dockable see dockwindow Xt 47 restricting to specificdatasets formessages see QMessage- 234 Box Y selectable entries see checkbox savingsize139 yellownote see tooltip view widgets see element-based window title yes-no questions170 views setting 69, 86, 105 viewMode (property) 216 Windows(Microsoft) Z viewport211 behavior of qFatal()388 zero VisualStudio51, 52 developmentenvironment for division by 388 compilingaQt applicationwith opensource projects 52 object 200 29 forking337 zero string VisualStudioproject GPLversion 52 checking for see isNull() compiling29 lineend of text files 114 generating fromqmake project messagehandler for389

440