Qt 高级编程


ptg Advanced Qt Programming ptg This page intentionally left blank ptg Advanced Qt Programming Creating Great Software with C+ + and Qt 4 Mark Summerfield Upper Saddle River, NJ ·Boston ·Indianapolis ·San Francisco p New York ·Toronto ·Montreal ·London ·Munich ·Paris ·Madrid p Capetown ·Sydney ·Tokyo ·Singapore ·Mexico City ptg Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact: U.S. Corporate and Government Sales (800) 382-3419 corpsales@pearsontechgroup.com For sales outside the United States, please contact: International Sales international@pearsoned.com Visit us on the Web: informit.com/aw Library of Congress Cataloging-in-Publication Data Summerfield, Mark. Advanced Qt programming : creating great software with C++ and Qt 4 / Mark Summerfield. p.mcm. Includes bibliographical references and index. ISBN 978-0-321-63590-7 (hardcover : alk. paper) 1. Qt (Electronic resource) 2. Graphical user interfaces (Computer systems) 3. C++ (Computer program language) I. Title. QA76.9.U83S88 2010 005.1’13—dc22 2010019289 Copyright © 2011 Qtrac Ltd. All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, write to: Pearson Education, Inc. Rights and Contracts Department 501 Boylston Street, Suite 900 Boston, MA 02116 Fax: (617) 671-3447 ISBN-13: 978-0-321-63590-7 ISBN-10: 0-321-63590-6 Text printed in the United States on recycled paper at Courier in Westford, Massachusetts. First printing, July 2010 ptg This book is dedicated to Anna Rebecca Paterson ptg This page intentionally left blank ptg Contents at a Glance List of Tables . ........................................................................................... xiii Foreword ................................................................................................... xv Introduction . ............................................................................................. 1 Chapter 1. Hybrid Desktop/Internet Applications ............................... 5 Chapter 2. Audio and Video ................................................................. 53 Chapter 3. Model/View Table Models ................................................. 87 Chapter 4. Model/View Tree Models .................................................... 129 Chapter 5. Model/View Delegates . ...................................................... 185 Chapter 6. Model/View Views .............................................................. 207 Chapter 7. Threading with QtConcurrent . ............................................ 245 Chapter 8. Threading with QThread . .................................................... 287 Chapter 9. Creating Rich Text Editors .................................................... 317 Chapter 10. Creating Rich Text Documents . ....................................... 359 Chapter 11. Creating Graphics/View Windows .................................... 389 Chapter 12. Creating Graphics/View Scenes . .................................... 409 Chapter 13. The Animation and State Machine Frameworks ............. 469 Epilogue ................................................................................................... 491 Selected Bibliography . ........................................................................... 495 Index . ........................................................................................................ 499 www.qtrac.eu/aqpbook.html vii ptg This page intentionally left blank ptg Contents List of Tables . ........................................................................................... xiii Foreword ................................................................................................... xv Introduction . ............................................................................................. 1 Acknowledgements . 3 Chapter 1. Hybrid Desktop/Internet Applications . 5 Internet-Aware Widgets . 6 Using WebKit ...................................................................................... 21 A Generic Web Browser Window Component ..................... 22 Creating Web Site-Specific Applications . ............................... 30 Embedding Qt Widgets in Web Pages .................................... 44 Chapter 2. Audio and Video ................................................................. 53 Using QSound and QMovie .............................................................. 54 The Phonon Multimedia Framework ............................................... 60 Playing Music . ........................................................................... 64 Playing Videos ........................................................................... 80 Chapter 3. Model/View Table Models . ................................................. 87 Qt’s Model/View Architecture . ...................................................... 88 Using QStandardItemModels for Tables . ....................................... 90 Changing a Table Model through the User Interface . .......... 91 A QStandardItemModel Subclass for Tables .......................... 102 A QSortFilterProxyModel to Filter Out Duplicate Rows . .......... 107 A QSortFilterProxyModel to Filter In Wanted Rows .................. 109 Creating Custom Table Models . 113 Changing a Table Model through the User Interface . 113 A Custom QAbstractTableModel Subclass for Tables . 116 The QAbstractItemModel API Methods for Tables . 117 Methods to Support Saving and Loading Table Items . . . 126 ix ptg Chapter 4. Model/View Tree Models .................................................... 129 Using QStandardItemModels for Trees ............................................ 130 Changing a Tree Model through the User Interface . ............. 131 A QStandardItem Subclass for Tree Items ............................... 141 A QStandardItemModel Subclass for Trees . .......................... 143 Creating Custom Tree Models ......................................................... 151 Changing a Tree Model through the User Interface . 152 A Custom Item Class for Tree Items . 155 A Custom QAbstractItemModel Subclass for Trees . 158 The QAbstractItemModel API for Trees . 160 The QAbstractItemModel API for Drag and Drop . 168 Methods for Saving and Loading Tree Items . 180 Chapter 5. Model/View Delegates . 185 Datatype-Specific Editors . 186 Datatype-Specific Delegates . 188 A Read–Only Column or Row Delegate . 188 An Editable Column or Row Delegate . 193 Model-Specific Delegates . .............................................................. 201 Chapter 6. Model/View Views . .............................................................. 207 QAbstractItemView Subclasses . .................................................... 208 Model-Specific Visualizing Views .................................................... 224 The Visualizer Widget . .............................................................. 225 The Visualizer’s Aggregated Header Widget . ....................... 232 The Visualizer’s Aggregated View Widget . ............................ 235 Chapter 7. Threading with QtConcurrent . ............................................ 245 Executing Functions in Threads . 248 Using QtConcurrent::run() . 252 Using QRunnable . 257 Filtering and Mapping in Threads .................................................... 261 Using QtConcurrent to Filter . .................................................... 270 Using QtConcurrent to Filter and Reduce ............................... 277 Using QtConcurrent to Map .................................................... 281 Chapter 8. Threading with QThread ............................... Processing Independent Items . ...................................................... 287 Processing Shared Items ................................................................... 302 x ptg Chapter 9. Creating Rich Text Editors . ................................................... 317 Introducing QTextDocument . ......................................................... 318 Creating Custom Text Editors . ......................................................... 320 Completion for Line Edits and Comboboxes . 320 Completion and Syntax Highlighting for Text Editors . 322 Completion for Multi-line Editors . 323 Syntax Highlighting . 336 A Rich Text Single Line Editor . 342 Multi-line Rich Text Editing . 353 Chapter 10. Creating Rich Text Documents . 359 Exported QTextDocument File Quality . ......................................... 361 Creating QTextDocuments .............................................................. 364 Creating QTextDocuments with HTML . .................................... 364 Creating QTextDocuments with QTextCursor . ....................... 367 Exporting and Printing Documents ................................................. 371 Exporting QTextDocuments ...................................................... 372 Exporting in PDF and PostScript Format . .......................... 372 Exporting in Open Document Format . ............................ 373 Exporting in HTML Format . ................................................. 374 Exporting in SVG Format .................................................... 375 Exporting in Pixmap Formats .............................................. 375 Printing and Previewing QTextDocuments . ............................ 376 Painting Pages . ................................................................................ 379 Painting PDF or PostScript ......................................................... 387 Painting SVG .............................................................................. 387 Painting Pixmaps . ...................................................................... 388 Chapter 11. Creating Graphics/View Windows . 389 The Graphics/View Architecture . 390 Graphics/View Widgets and Layouts . 392 Introducing Graphics Items . 399 Chapter 12. Creating Graphics/View Scenes . 409 Scenes, Items, and Actions .............................................................. 411 Creating the Main Window . .................................................... 412 Saving, Loading, Printing, and Exporting Scenes .................. 415 Saving Scenes . ................................................................... 415 Loading Scenes ................................................................. 417 xi ptg Printing and Exporting Scenes . ......................................... 420 Manipulating Graphics Items . ................................................. 423 Adding Items ...................................................................... 425 Copying, Cutting, and Pasting Items ............................... 427 Manipulating Selected Items ............................................ 430 Showing and Hiding the Guideline Grid .......................... 435 Keeping the User Interface Up to Date . .......................... 436 Enhancing QGraphicsView ............................................................ 439 Creating a Dock Widget Toolbox . ................................................. 440 Creating Custom Graphics Items .................................................... 447 Enhancing QGraphicsTextItem . .............................................. 447 Graphics Item Transformations . ....................................... 453 Enhancing an Existing Graphics Item . .................................... 455 Creating a Custom Graphics Item from Scratch . .................. 459 Chapter 13. The Animation and State Machine Frameworks ............. 469 Introducing the Animation Framework . ......................................... 469 Introducing the State Machine Framework .................................... 474 Combining Animations and State Machines . ............................... 481 Epilogue ................................................................................................... 491 Selected Bibliography . ........................................................................... 495 Index . ........................................................................................................ 499 xii ptg List of Tables 1.1. The Main WebKit Classes . .............................................................. 21 1.2. Qt’s Global Utility Functions . ......................................................... 46 2.1. The Main Phonon Classes .............................................................. 63 3.1. The QAbstractItemModel API . 118 3.2. The Qt::ItemDataRole enum . 119 3.3. The Qt::ItemFlag enum . 119 4.1. The QAbstractItemModel’s Drag and Drop API .......................... 159 5.1. The QStyledItemDelegate API . ...................................................... 194 6.1. The QAbstractItemView API . ......................................................... 210 9.1. The QTextCursor API #1 . 328 9.2. The QTextCursor API #2 . 329 9.3. The QTextCursor API #3 . 330 9.4. The QTextCursor::MoveOperation enum . 334 11.1. The QGraphicsItem API (Selected Methods) #1. 403 11. 2 . The QGraphicsItem API (Selected Methods) #2 . 404 11. 3 . The QGraphicsItem API (Selected Methods) #3 . 405 11. 4 . The QGraphicsItem API (Selected Methods) #4 . 406 11. 5 . The Qt::ItemSelectionMode enum . 406 11. 6 . The QGraphicsItem::GraphicsItemFlag enum #1 . 406 11. 7. The QGraphicsItem::GraphicsItemFlag enum #2 . ....................... 407 xiii ptg This page intentionally left blank ptg Foreword Way back in 1991, I sat on a park bench in Trondheim, Norway, together with Haavard Nord. We were doing our non-military service together at the regional hospital there, and needed to develop software for the storage and analysis of ultrasound images. The hospital used all sorts of computers and wanted the system to work on Unix, Mac, and Windows. This was a huge challenge and we scanned the market for available class libraries that could help us. We were appalled by the quality of what we found. On that park bench we decided to come up with our own solution to the challenge. We were young, ambitious, and naïve. Sick and tired of wasting our time find- ing out how to use non-intuitive tools and libraries, we set our sights on improv- ing the situation. We wanted to change the world of software development ever so slightly. Our goal was to make life easier for software developers. To make it possible to focus on what we all know is the fun side of developing software: being creative and turning out well-written code. So, we created the first crude versions of Qt, and incorporated Trolltech a few years later. I think we achieved at least part of our goal. Qt has had tremendous success since it was first released in 1995. In 2008 Trolltech was acquired by Nokia and in April 2009 it was time for me to move on. After 15 years and 27 days in the company I was no longer on the inside. The product is in good hands, and the passion and hard work of the team are the same as ever. The Trolls at Nokia are making sure that Qt continues to be the rock solid framework you expect. Lars Knoll (of kHTML—WebKit—fame) today leads almost 150 dedicated Qt engineers. Nokia has also added the LGPL as a licensing option, making Qt accessible to even more developers. This fall I was invited by Nokia as a guest of honor at the Qt Developer Days in Munich, Germany. This user conference—which also takes place in the U.S.—is a fantastic venue for Qt enthusiasts and has been increasing in size year by year. It was great feeling the buzz and talking to Qt users from all over Europe. I spoke to many developers who told me that Qt makes a real difference in their software work. That makes an old hacker feel good. Qt as a good tool and class library is only half the story behind its success. You also need good documentation, tutorials, and books. After all, the goal was to make life easier for developers. xv ptg That is why I was never in doubt, back in 2003. I was President of Trolltech and Mark Summerfield, the head of documentation, came into my office. He wanted to write a book about Qt together with Jasmin Blanchette. A really good book, written by someone with intimate knowledge of the product and with a passion for explaining things clearly and intuitively. Who was better fit for the task than the head of Qt documentation, together with one of the best Qt developers? The end result was a great book about Qt, which has since been updated and expanded. Mark has now completed another important project. A good book on advanced Qt programming has been missing in the arsenal of Qt programmers. I’m very happy that Mark has written one. He is a fantastic technical writer with all the necessary background to write authoritatively about Qt programming. His focus on detail and ability to express himself clearly and intuitively have always impressed me. In other words: You are in for a treat! You are holding in your hands (or reading on-screen) an excellent opportunity to expand on your knowledge of all the cool stuff you can do with Qt. Happy programming! Eirik Chambe-Eng The southern Alps, France December 24, 2009 xvi ptg Introduction For some time I have wanted to write a Qt book that covered topics that were too advanced for C++ GUI Programming with Qt 4,★ even though that book itself has proved quite challenging for some readers. There is also some specialized material—not all of it difficult—that I wanted to cover that simply does not belong in a first book on Qt programming. Furthermore, in view of the sheer size of Qt, no one book can possibly do justice to all that it offers, so there was clearly room for the presentation of new material. What I’ve done in this book is to take a selection of modules and classes from a variety of areas and shown how to make good use of them. The topics chosen reflect both my own interests and also those that seem to result in the most discussion on the qt-interest mailing list. Some of the topics are not covered in any other book, while other topics cover more familiar ground—for example, model/view programming. In all cases, I have tried to provide more comprehensive coverage than is available elsewhere. So the purposes of this book are to help Qt programmers deepen and broaden their Qt knowledge and to increase the repertoire of what they can achieve using Qt. The “advanced” aspect often refers more to what you will be able to achieve than to the means of achieving it. This is because—as always—Qt insulates us as far as possible from irrelevant detail and underlying complexity to provide easy-to-use APIs that we can use simply and directly to great effect. For example, we will see how to create a music player without having to know anything about how things work under the hood; we will need to know only the high-level API that Qt provides. On the other hand, even using the high-level QtConcurrent module, the coverage of threading is necessarily challenging. This book assumes that readers have a basic competence in C++programming, and at least know how to create basic Qt applications—for example, having read a good Qt 4 book, and having had some practical experience. Readers are also assumed to be familiar with Qt’s reference documentation, at least as far as being able to navigate it to look up the APIs of classes of interest. In addi- tion, some chapters assume some basic topic-specific knowledge—for example, Chapter 1 assumes some knowledge of JavaScript and web programming, and the threading chapters assume a basic understanding of threading and Qt’s threading classes. All these assumptions mean that this book can avoid ex- ★ C++ GUI Programming with Qt 4, Second Edition, by Jasmin Blanchette and this author, ISBN 0132354160. 1 ptg 2 Introduction plaining many details and classes that are already familiar to Qt programmers, such as using layouts, creating actions, connecting signals and slots, and so on, leaving the book free to focus on the less familiar material. Of course, no single volume book can realistically do justice to Qt’s more than 700 public classes—almost 800 in Qt 4.6—and its much more than one million words of documentation, so no attempt is made to do so here. Instead this book provides explanations and examples of how to use some of Qt’s most powerful features, complementing the reference documentation rather than duplicating it. The book’s chapters have been designed to be as self-contained as possible, so it is not necessary to read the book from beginning to end in chapter order. To support this, where particular techniques are used in more than one chapter, the explanation is given in just one place and cross-references are given else- where. Nonetheless, if you plan to read odd chapters out of order,I recommend that you at least do an initial skim read of the entire book, since chapters and sections devoted to one particular topic may of necessity have material relating to other topics. Also, I have tried to include lots of small details from Qt’s API throughout, to make the book’s content richer,and to show as many features as possible in context, so useful information appears throughout. As with all my previous books, the quoted code snippets are of “live code”, that is, the code was automatically extracted from the examples’ source files and directly embedded in the PDF that went to the publisher—so there are no cut and paste errors, and the code works. The examples are available from www.qtrac.eu/aqpbook.html and are licensed under the GPL (GNU General Public License version 3). The book presents more than twenty-five examples spread over more than 150 .hpp and .cpp files, and amounting to well over 20 000 lines of code. Although all of the most important pieces of code are quot- ed and explained in the book, there are numerous small details that there isn’t space to cover in the book itself, so I recommend downloading the examples and at least reading the source code of those examples that are in your areas of particular interest. In addition to the examples, some modules containing commonly used functionality are also provided. These all use the AQP names- pace to make them easy to reuse, and they are all introduced in the first couple of chapters, and then used throughout the book. All the examples—except for those in the last chapter which use Qt 4.6-specific features—have been tested with Qt 4.5 and Qt 4.6 on Linux, Mac OS X, and Windows. Applications built using Qt 4.5 will run unchanged with Qt 4.6, and later Qt 4.x versions, because Qt maintains backward compatibility between minor releases. However, where there are differences between the two Qt versions, the book shows and explains the Qt 4.6-specific approach, while the source code uses #if QT_VERSION so that the code compiles with either version with the best practices used for each. A few examples may work with earlier Qt 4.x versions, particularly Qt 4.4, and some examples could be backported to ptg Introduction 3 an earlier Qt version—however, the focus of this book is purely on Qt 4.5 and Qt 4.6, so there is no explicit coverage of backporting. The book shows best Qt 4.6 practices, and despite Qt 4.6’s numerous new fea- tures compared with Qt 4.5, this makes few differences to the code. One trivial difference is that Qt 4.6 has a shortcut for the “quit” action and Qt 4.5 hasn’t; the source code uses the shortcut for Qt 4.6 and has equivalent code for Qt 4.5 by using #if QT_VERSION. A much more important difference is that Qt 4.6 in- troduced the QGraphicsObject class and also changed the behavior of graphics items when it comes to communicating geometry changes. We explain the dif- ferences in a sidebar and show the Qt 4.6 approach in the book’s code snippets, but in the source code, #if QT_VERSION is used to show how to do the same things using Qt 4.6 and Qt 4.5 or earlier, and using the best approach for both. In the book’s last chapter,Qt 4.6-specific features are shown, with two out of the three examples covered being conversions of examples presented earlier, and that make use of the Qt 4.6 animation and state machine frameworks. Modifying earlier examples makes it easier to see how to go from the traditional Qt ap- proach to using the new frameworks. The next version of Qt—Qt 4.7—will focus on stability, speed, and apart from the new Qt Quick technology (which provides a means of creating GUIs declar- atively using a JavaScript-like language), will introduce fewer new features than in previous releases. Nonetheless, despite the huge ongoing development effort that is being put into Qt, and its ever increasing scope, this book should serve as a useful resource for learning about and using important Qt technolo- gies in the Qt 4.x series, especially for Qt 4.5, Qt 4.6, and later versions,for some years to come. Acknowledgements My first acknowledgement is of my friend Trenton Schulz, an ex-senior soft- ware engineer at Nokia’s Qt Development Frameworks (formerly Trolltech) who is now a research scientist at the Norwegian Computing Center. Trenton has proved to be a reliable, insightful, and challenging reviewer, whose careful reading, high standards, and numerous suggestions have considerably helped to improve this book. My next acknowledgement is of another friend, Jasmin Blanchette, also an ex-senior software engineer at Qt Development Frameworks, coauthor with me of the C++ GUI Programming with Qt 4 book, and now researching for a PhD at the Technische Universität München. We both came up with the idea for this book some time ago, and it is only due to pressure of work that he has been an excellent—and demanding—reviewer, rather than coauthor. I would also like to thank many people who work for (or worked for) Qt Develop- ment Frameworks who read portions of the book and provided useful feedback, ptg 4 Introduction or who answered technical questions, or both. These include Andreas Aardal Hanssen (who gave particularly excellent feedback and suggestions regarding the graphics/view chapters, and who drafted the off-screen rendering sidebar for me), Andy Shaw,Bjørn Erik Nilsen, David Boddie, Henrik Hartz, Kavindra Devi Palaraja, Rainer Schmid (now at froglogic), Simon Hausmann, Thierry Bastian, and Volker Hilsheimer. The Italian software company www.develer.com was kind enough to provide me with free repository hosting to aid my peace of mind over the long process of writing the book. And several of their developers gave me useful feedback, particularly on some of the examples in the early chapters. I’m especially thankful to Gianni Valdambrini, Giovanni Bajo, Lorenzo Mancini (who set up the repository for me), and Tommaso Massimi. A special thank you to rough-cut reader Alexey Smirnov who spotted some errors and encouraged me to add support for network proxies to some of the networking examples. I also want to thank froglogic’s founders, Reginald Stadlbauer and Harri Porten—the part-time consultancy work I do for them has helped fund the time to write this book, as well as introducing me to some programming technologies and ideas that were new to me. They’ve also turned me into a big fan of their GUI application testing tool, Squish. My friend Ben Thompson also deserves thanks—for reminding me of certain mathematical concepts that I’d forgotten, and especially for his patience in explaining them to me until I understood them again. This book (and some of my others) would not have been possible without Qt. So I’m very grateful to Eirik Chambe-Eng and Haavard Nord for creating Qt—and especially to Eirik for allowing me to write my first book as part of my daily work at Trolltech, and for taking the time and care to write the foreword to this book. Special thanks to my editor, Debra Williams Cauley, both for quite indepen- dently suggesting that I write this book in the first place, and for her support and practical help as the work progressed. Also thanks to Jennifer Lindner who gave useful input on the book’s structure as well as other feedback that I incorporated. Thanks also to Anna Popick, who managed the production pro- cess so well, and to the proofreader, Barbara Wood, who did such fine work. I also want to thank my wife, Andrea, who experiences all the ups and downs of writing along with me, for her enduring love and support. ptg Hybrid Desktop/Internet Applications ||||| 1 ● Internet-Aware Widgets ● Using WebKit The apparent ubiquity of the “computing cloud”, the ready availability of web- enabled mobile phones and small form-factor netbook and smartbook comput- ers—not to mention the Google Doc’s file store—and the zero-deployment costs of web-based applications might lead us to believe that desktop applications are dinosaurs that don’t yet know they’re extinct. But before we abandon C++ and Qt and switch to web programming and the subtle pleasures of JavaScript and HTML, it is worth reflecting on just some of the advantages that desktop applications can provide. • Availability—outside of specialist mission-critical areas we can be sure that on rare (and always inconvenient) occasions the Internet will be un- available—due to network failures, ISP errors, etc.—and web applications will be useless.★ • Resource Access—a desktop application has full access to the user’s computer with none of the necessary security restrictions that limit the capabilities of web-based applications. • Look and Feel—a desktop application doesn’t have a redundant (and con- fusing) browser menu bar and toolbar in addition to its own menu bar and toolbars; it has its own keyboard shortcuts with no risk of conflict with those used by a browser; and it has exactly the look and feel it was pro- grammed to have rather than one that varies from browser to browser. • Custom Widgets—a desktop application can present the user with custom widgets specifically dedicated to the task at hand, and can provide a level of usability that web applications cannot match. ★See, for example, opencloudcomputing.info/trends/cloud-computing-downtime or the Cloud Comput- ing Incidents Database. 5 ptg 6 Chapter 1. Hybrid Desktop/Internet Applications Ideally we would like to have all the benefits of desktop applications, and at the same time enjoy all the advantages of Internet access when it is available. Thanks to Qt’s QtWebKit module, introduced with Qt 4.4, this can be achieved, since QtWebKit allows us to create hybrid desktop/Internet applications that can work both offline and online. The main disadvantage compared with web-based applications is in the area of deployment—the Qt application must be available on the user’s computer. In cases where the deployment effort or bandwidth utilization must be min- imized there are several approaches that can be taken. For example, we can put a lot of application functionality into relatively small plugins that can be updated independently. Or we could use application scripting to provide much of the application’s functionality using the QtScript module for JavaScript (ECMAScript)—or a third-party module if we want to use a different scripting language—and just update or add individual scripts as necessary. Or we could put as much functionality as possible into the server and into web page scripts, thereby greatly reducing the number of times we need to update the client. In this chapter we will focus on key aspects of Qt’s support for hybrid appli- cations. In the first section we will use the convenient QNetworkAccessManager class introduced in Qt 4.4, to create Internet-aware widgets. In the second section we will make use of the QtWebKit module, starting by developing a generic web browser component—a surprisingly easy task thanks to the func- tionality provided by the QtWebKit module. We will then make use of the generic web browser component—for example, to create a web site-specific application—and use the QtWebKit module to access the DOM (Document Object Model) of web pages downloaded behind the scenes so that we can ex- tract information from them for further processing. And then we will see how to embed Qt widgets—including our own custom widgets—into web pages, to provide functionality that is not available using the standard HTML widgets. Internet-Aware Widgets |||| Our definition of an Internet-aware widget is a widget that automatically re- trieves data from the Internet, either as a one-off event when it is constructed, or at regular intervals. The easiest way to create an Internet-aware widget is to create a widget sub- class that makes use of a QNetworkAccessManager object. These objects are ca- pable of performing HTTP (and HTTPS) HEAD, POST, GET, and PUT requests, and also of handling cookies (using QNetworkCookieJar) and authentication (using QAuthenticator). In this section we will look at an example that uses one QNetworkAccessManager to read data from the Internet at timed intervals, and another QNetworkAccess- Manager that is used to download images on demand. This should be sufficient ptg Internet-Aware Widgets 7 QNetworkAccessManager QNetworkRequest QNetworkReply Web Server Figure 1.1 A QNetworkAccessManager in communication with a web site to give a flavor of how QNetworkAccessManagers are used. Figure 1.1 illustrates the relationship between a QNetworkAccessManager and an external web site. Note that since QNetworkAccessManager is part of Qt’s QtNetwork module, any application that uses it must include the line QT += network in its .pro file. This section’s example is a taskbar tray icon application. Such applications are typically used for frequently used controls such as volume controls, or to provide status information such as memory usage or the current date and time. In this section we will develop the Weather Tray Icon application (weathertrayicon). This application shows an icon corresponding to the current weather conditions at a specified U.S. airport with both the icon and the data retrieved from the U.S. National Weather Service (www.weather.gov). Figure 1.2 The Weather Tray Icon application and its context menu Figure 1.2’s left-hand screenshot shows the Weather Tray Icon application and a tooltip—the icon is under the bottom-right corner of the tooltip. The figure’s right-hand screenshot shows the application’s context menu. Once an hour ptg 8 Chapter 1. Hybrid Desktop/Internet Applications the application downloads the weather data and the corresponding icon for the chosen airport’s weather conditions and updates itself accordingly. Taskbar tray icon applications like this work on all of Qt’s supported desktop platforms. For example, the screenshots in Figure 1.2 were taken on Linux running Fedora with the GNOME desktop. On Windows and Mac OS X the tooltip would be plain text since Qt tooltips on those platforms don’t support Qt rich text (HTML); and of course on Mac OS X the icon appears in the menu bar as we would expect. When reviewing most of the book’s examples we won’t usually show the main() functions because they are almost all simple and standard. But in this case there are a couple of important deviations from the norm, so we will show the Weather Tray Icon application’s main() function. int main(int argc, char *argv[]) { QApplication app(argc, argv); app.setApplicationName(app.translate("main", "Weather Tray Icon")); app.setOrganizationName("Qtrac Ltd."); app.setOrganizationDomain("qtrac.eu"); app.setQuitOnLastWindowClosed(false); if (int error = enableNetworkProxying()) return error; WeatherTrayIcon weatherTrayIcon; weatherTrayIcon.show(); return app.exec(); } The function starts in the standard Qt way with the creation of a QApplication object. We set the application’s name, which we can later use—for example, for dialog titles—accessing it with QApplication::applicationName(), and we also set the organization’s name and domain which means that we can create QSettings objects whenever we want without having to bother giving them any arguments. There are two unusual aspects to this function. The first is that we have told Qt not to close the application when the last window is closed. This is because normally a tray icon application has no window (it just has a tray icon), and any windows it does use are normally transient (e.g., a tooltip or a context menu), and their closure should not implicitly cause application termination. The second unusual aspect is the call to a custom enableNetworkProxying() func- tion. This function is discussed in the “Supporting Network Proxying” sidebar (➤ 9). If the function returns a nonzero error code it signifies that an error oc- curred in which case we return the error code and terminate the application. ptg Internet-Aware Widgets 9 Supporting Network Proxying For machines that have direct connections to the Internet (e.g., using a broadband modem or router), the networking examples presented in this chapter should work as is. However, for machines that are on firewalled networks—typically corporate networks—the examples may fail to reach the Internet. Most firewalled networks provide some kind of proxy server through which Internet connections can be made. Qt provides support for such proxies, so we have added support for proxying to the browserwindow, nyrbviewer, rsspanel, and weathertrayicon examples, by having them call a custom enableNetworkProxying() function inside their main() function. The enableNetworkProxying() function uses the AQP::OptionParser (supplied with the book’s examples in directory option_parser and using the AQP name- space) to parse the command line arguments used to set up proxying. The command line options supported by the proxying-enabled applications are: -h --help show this information and terminate -H --host=STRING hostname, e.g., www.example.com -P --password=STRING password -p --port=INTEGER port number, e.g., 1080 -t --type=STRING (http, socks5; default socks5) proxy type -u --username=STRING username The proxying is set up in the enableNetworkProxying() function only if a host- name is specified. Here is the code; the parser is of type AQP::OptionParser. if (parser.hasValue("host")) { QNetworkProxy proxy; proxy.setType(parser.string("type") == "socks5" ? QNetworkProxy::Socks5Proxy : QNetworkProxy::HttpProxy); proxy.setHostName(parser.string("host")); if (parser.hasValue("port")) proxy.setPort(parser.integer("port")); if (parser.hasValue("username")) proxy.setUser(parser.string("username")); if (parser.hasValue("password")) proxy.setPassword(parser.string("password")); QNetworkProxy::setApplicationProxy(proxy); } If a host is specified, proxying is set up using the given host, the default or given proxy type, and any other options the user has given. This sets up global proxying for the whole application. It is also possible to set up per-socket proxying using QAbstractSocket::setProxy(). ptg 10 Chapter 1. Hybrid Desktop/Internet Applications The weather data is provided in various formats, but we have chosen to access it in XML format. The format itself is very simple, consisting essentially of a list of key–value pairs where the key is a tag name and the value is the text between the opening and closing tags. For example: Fair 49 F (9 C) 49 9 From the Northeast at 5 MPH 9.00 http://weather.gov/weather/images/fcicons/ nskc.jpg When the application first starts it sets its airport to the one that was last set by the user, or to a default airport the first time it is run. It then uses a QNetwork- AccessManager to retrieve the weather data. Two elements of the data are a URL and a filename for an icon that corresponds to the prevailing weather conditions at the airport. The application uses a second QNetworkAccessManager to retrieve the icon and sets this as the icon shown in the taskbar tray. In fact, the application caches icons to economize on bandwidth, as we will see shortly. class WeatherTrayIcon : public QSystemTrayIcon { Q_OBJECT public: explicit WeatherTrayIcon(); private slots: void requestXml(); void readXml(QNetworkReply *reply); void readIcon(QNetworkReply *reply); void setAirport(QAction *action); private: ··· QMenu menu; QNetworkAccessManager *networkXmlAccess; QNetworkAccessManager *networkIconAccess; QString airport; QCache iconCache; int retryDelaySec; }; We will show and explain all the methods in a moment—including the private ones not shown—but now we’ll comment on some of the private member data. ptg Internet-Aware Widgets 11 The airport string holds the current airport, for example, “Chicago/Ohare (KORD)”. The iconCache has QUrl keys and pointers to QIcon values. We will cover the others when we discuss the methods they are used in. The QCache class caches items using a “cost” scheme. The cache’s maximum cost defaults to 100—the sum of item costs is always less than or equal to the max- imum. By default each item has a cost of 1, so unless we change the maximum or set our own item costs the cache will hold up to 100 items. When a new item is added, if the item’s cost makes the sum of costs exceed the maximum cost, one or more of the least recently accessed items are removed until the sum of costs is less than or equal to the maximum. Behind the scenes QCache uses a QHash to provide very fast lookup by key. However, out of the box, QHash cannot store QUrls as keys because Qt does not provide a qHash(QUrl) function.★ This is easy to remedy with a one-liner: inline uint qHash(const QUrl &url) { return qHash(url.toString()); } Here we’ve simply passed the work on to the built-in qHash(QString) function. We are now ready to review the methods, starting with the constructor. WeatherTrayIcon::WeatherTrayIcon() : QSystemTrayIcon(), retryDelaySec(1) { setIcon(QIcon(":/rss.png")); createContextMenu(); networkXmlAccess = new QNetworkAccessManager(this); networkIconAccess = new QNetworkAccessManager(this); connect(networkXmlAccess, SIGNAL(finished(QNetworkReply*)), this, SLOT(readXml(QNetworkReply*))); connect(networkIconAccess, SIGNAL(finished(QNetworkReply*)), this, SLOT(readIcon(QNetworkReply*))); QTimer::singleShot(0, this, SLOT(requestXml())); } We give the application an initial icon to use while waiting for the first weather icon to be downloaded. Then we create a context menu with actions for changing the airport and for terminating the application. Most of the constructor is devoted to setting up the Internet access by creating two QNetworkAccessManagers. One is used to fetch the weather data and the other to fetch the icon associated with the current weather conditions. We use separate network access managers so that they can work independently of ★Qt 4.7 is scheduled to provide a qHash(QUrl) function. ptg 12 Chapter 1. Hybrid Desktop/Internet Applications each other, and in both cases we create a single signal–slot connection since all we are interested in is when each download is finished. Finally, we call the requestXml() slot using a single shot timer. This method makes use of the networkXmlAccess network access manager to fetch the weather data for the current airport. We could have simply called requestXml() directly, but as a matter of style we prefer to restrict ourselves to calling “create” methods that contribute to the construction of an object in constructors, and to call any post-construction initializing method using a single shot timer. This ensures that by the time the initializing method is called, the object is fully constructed. This means that the initializing method can access any member variable or method— something that is not guaranteed to be safe during construction. Before we look at the requestXml() slot, we’ll briefly look at how the context menu is created, to see how the airport whose details must be downloaded can be set by the user. void WeatherTrayIcon::createContextMenu() { QStringList airports; airports << "Austin-Bergstrom International Airport (KAUS)" ··· << "San Jose International Airport (KSJC)"; QSettings settings; airport = settings.value("airport", QVariant(airports.at(0))) .toString(); QActionGroup *group = new QActionGroup(this); foreach (const QString &anAirport, airports) { QAction *action = menu.addAction(anAirport); group->addAction(action); action->setCheckable(true); action->setChecked(anAirport == airport); action->setData(anAirport); } connect(group, SIGNAL(triggered(QAction*)), this, SLOT(setAirport(QAction*))); menu.addSeparator(); menu.addAction(QIcon(":/exit.png"), tr("E&xit"), qApp, SLOT(quit())); AQP::accelerateMenu(&menu); setContextMenu(&menu); } Here we have used a hard-coded list of airport names, but we could just as easily have read them from a file or resource. (If we wanted to list all the ptg Internet-Aware Widgets 13 U.S. airports we could easily group them—for example, by having states as top-level menu items, and airports as submenu items.) We use QSettings to set the current airport, defaulting to the first one in the list the first time the application is run. For each airport we create a QAction and check the one that matches the current airport. Each airport action is added to a QActionGroup. By default a QActionGroup has its exclusive property set to true; this ensures that its actions have radio button rather than checkbox check marks and that only one airport is ever checked at any one time. We also add an exit action and give it a specific keyboard accelerator. Then we call AQP::accelerateMenu() to provide keyboard accelerators for as many air- ports as possible, and then set the menu we have created as the application’s context menu. If the application is being built on Mac OS X the call to AQP::ac- celerateMenu() normally has no effect, since Mac OS X doesn’t support acceler- ators. See the “Keyboard Accelerators” sidebar for more about automatically setting keyboard accelerators (➤ 15). We need to connect each action with the setAirport() slot and to parameterize each slot invocation in some way so that the slot knows which airport has been chosen. An easy way to do this is to call QObject::sender() inside the slot to see which action called it, and then to extract the action’s text to determine the chosen airport. An alternative is to use a QSignalMapper.Butin this case there is an even easier solution—instead of connecting each of the airport QActionswe connect the QActionGroup instead. The QActionGroup::triggered() signal carries the relevant QAction as its parameter. void WeatherTrayIcon::requestXml() { QString airportId = airport.right(6); if (airportId.startsWith("(") && airportId.endsWith(")")) { QString url = QString("http://www.weather.gov/xml/" "current_obs/%1.xml").arg(airportId.mid(1, 4)); networkXmlAccess->get(QNetworkRequest(QUrl(url))); } } The XML weather data for a given airport is in a file whose name matches the airport’s four letter code. We have included this code in parentheses at the end of each airport’s name, and use basic QString methods to extract it. Once we have the URL we need we use the XML network access manager to do a GET request on it. When a request is made a pointer to the QNetworkReply object that will receive the results is returned. These objects emit signals indicating progress, for example, downloadProgress() and uploadProgress(), and when the request finishes, the reply object issues a finished() signal. ptg 14 Chapter 1. Hybrid Desktop/Internet Applications The network manager that initiated the request also emits a finished() signal, and since we are not concerned with monitoring progress this is the only signal we connected to—and the reason why we ignored the QNetworkAccessManager:: get() method’s return value. Once the download is finished (successfully or not), the signal–slot connection shown earlier ensures that the readXml() method is called with a pointer to the reply object as its sole argument. void WeatherTrayIcon::readXml(QNetworkReply *reply) { if (reply->error() != QNetworkReply::NoError) { setToolTip(tr("Failed to retrieve weather data:\n%1") .arg(reply->errorString())); QTimer::singleShot(retryDelaySec * 1000, this, SLOT(requestXml())); retryDelaySec <<= 1; if (retryDelaySec > 60 * 60) retryDelaySec = 1; return; } retryDelaySec = 1; QDomDocument document; if (document.setContent(reply)) populateToolTip(&document); QTimer::singleShot(60 * 60 * 1000, this, SLOT(requestXml())); } If the request failed we put the error message in the tray icon’s tooltip and try again later after a delay. The delay is held in the private retryDelaySec variable and is initialized to 1. (We must multiply by 1000because QTimer::singleShot() takes a timeout in milliseconds.) On each successive failure we double the delay interval—left shifting an integer by one bit doubles its value. So after a second failure we try again after two seconds, then after four seconds, and so on. At this rate, if we have a dozen failures the interval will be over an hour, in which case we reset it to start again at one second. We vary the retry interval each time to avoid being mistaken for a denial of service attack and to avoid getting into an “unlucky” request/failure cycle. As soon as the request succeeds and we have reset retryDelaySec back to 1, we parse the XML data. The QNetworkReply is a QIODevice subclass, so in addition to being able to emit network progress signals, it can also emit, for example, the readyRead() signal. And like any other QIODevice such as a file, we can read data from it, which by default is returned in a QByteArray. The QDomDocument:: setContent() method can read XML from a QByteArray,aQString, or from a QIODevice, so we are able to directly pass it the QNetworkReply for parsing. ptg Internet-Aware Widgets 15 Keyboard Accelerators (for non-Mac OS X platforms) Keyboard accelerators are important to users who are fast typists and to those who cannot—or don’t want to—use the mouse. They are Alt+x key sequences (where x is usually a letter or digit) that the user can press to pull down a menu from the menu bar (e.g., Alt+F for the File menu). Once a menu appears, one of its menu options can be chosen by pressing its underlined letter or digit alone, so to create a new file the user would press Alt+F, N. In dialogs accelerators are used to switch the keyboard focus to particular widgets—for example, a label that appears as Total: has a keyboard accelera- tor of Alt+T, and pressing this would be expected to move the keyboard focus to the label’s buddy widget. Similarly, checkboxes and radio buttons often have accelerators and when pressed the accelerator toggles the checkbox’s or radio button’s state. It is common to write accelerators using uppercase letters (e.g., Alt+E) both for display and in code. (Keyboard accelerators are distinct from keyboard shortcuts which are arbitrary key sequences associated with particular actions, for example, Ctrl+N to create a new file.) For short menus and for dialogs that only contain a few widgets, setting accelerators manually (by including an & in the texts) is quite easy. But once we get to more than about fifteen menu items or widgets it becomes increasingly difficult to work out what the optimal accelerators should be. The ideal solution would be to get the computer to work out the accelerators for us, and that is the approach taken in this book. In the aqp directory the alt_key.{hpp,cpp} module provides an API, and the kuhn_munkres.{hpp,cpp} module provides an algorithm that instantly computes optimal results, easily outperforming all the naïve algorithms. For example, to provide accelerators for a main window’s menus we can write this line near the end of the main window’s constructor: AQP::accelerateMenu(menuBar()); That’s all that is required. The Kuhn-Munkres algorithm is used to cal- culate optimal accelerators and respects any items that already have accelerators—for example, if we want certain menu options or labels to have particular accelerators no matter what. For dialogs we can use an equally simple approach, writing this line at the end of a dialog’s constructor: AQP::accelerateWidget(this); The accelerator functions don’t account for hidden widgets such as those on a tab page that isn’t being shown. We can still handle such cases by using one or more calls to the AQP::accelerateWidgets() function, giving it a specific list of widgets to work on each time. ptg 16 Chapter 1. Hybrid Desktop/Internet Applications If the QDomDocument::setContent() method returns true, the parse succeeded, so we populate the tooltip with the new data—a process that might involve downloading a new icon too. If the parse failed, we leave the existing tooltip as is. And at the end we set a single shot timer to repeat the process one hour later. The QDomDocument class is part of Qt’s QtXml module, so to be able to use it we must add the line QT += xml to the application’s .pro file. The application’s flow of control, from the initial GET request on the XML net- work access manager, to downloading the XML and icons, and then repeating the download every hour, is illustrated by Figure 1.3. Start requestXml() networkXmlAccess readXml() Wait one hour www.weather.gov Icon in cache? Use cached icon networkIconAccess readIcon() networkXmlAccess::get() networkXmlAccess::finished() QTimer::singleShot() No networkIconAccess::get() Y Yes networkIconAccess::finished() Figure 1.3 The Weather Tray Icon application’s flow of control For completeness, we will show the private populateToolTip() method, which we will review in three parts, followed by its two private helper methods. void WeatherTrayIcon::populateToolTip(QDomDocument *document) { QString toolTipText = tr("%1
") .arg(airport); QString weather = textForTag("weather", document); if (!weather.isEmpty()) toolTipText += toolTipField("Weather", "green", weather); ··· ptg Internet-Aware Widgets 17 Here we extract the text elements that we want to include in the tooltip, although we have omitted most of the code since each element follows the same (or very similar) pattern as the code shown for the “weather” element. The private textForTag() helper method is used to retrieve the text for any given tag. This works because we know that for the weather data every tag is unique and does not contain any nested tags. QString iconUrl = textForTag("icon_url_base", document); if (!iconUrl.isEmpty()) { QString name = textForTag("icon_url_name", document); if (!name.isEmpty()) { iconUrl += name; QUrl url(iconUrl); QIcon *icon = iconCache.object(url); if (icon && !icon->isNull()) setIcon(*icon); else networkIconAccess->get(QNetworkRequest(url)); } } The icon that corresponds to the prevailing weather conditions is identified by two XML elements, the “icon_url_base” and the “icon_url_name”. We attempt to extract both these elements’ texts and to create a URL out of them. We then try to retrieve the icon from the cache using the URL as the key. The QCache::object() method returns 0 if there is no item in the cache with the corresponding key. If we retrieved a QIcon pointer from the cache we use it—in fact we get a copy (which is cheap because Qt uses copy-on-write, and useful since QCache can delete items at any time). Otherwise we use the icon network access manager to download the icon. And if we initiate downloading an icon, the signal–slot connection set up earlier will ensure that the readIcon() slot (covered shortly) is called when the download has finished. #ifndef Q_WS_X11 toolTipText = QTextDocumentFragment::fromHtml(toolTipText) .toPlainText(); #endif setToolTip(toolTipText); } Unfortunately, Qt rich text (HTML) tray icon tooltips are only supported on X11, so for Windows and Mac OS X systems we convert the HTML tooltip text to plain text. We do this by using the static QTextDocumentFragment::fromHtml() method to get a QTextDocumentFragment, and then using QTextDocumentFragment:: ptg 18 Chapter 1. Hybrid Desktop/Internet Applications toPlainText(), to produce plain text. Using a QTextDocumentFragment is more convenient than doing the conversion by hand since not only does it convert HTML entities to the appropriate Unicode characters and strip out HTML tags, but it is also smart enough to convert
s into newlines. QString WeatherTrayIcon::textForTag(const QString &tag, QDomDocument *document) { QDomNodeList nodes = document->elementsByTagName(tag); if (!nodes.isEmpty()) { const QDomNode &node = nodes.item(0); if (!node.isNull() && node.hasChildNodes()) return node.firstChild().nodeValue(); } return QString(); } Using QDomDocument is ideal for small XML files since it parses the entire file, holds all the data in memory, and provides a variety of convenient access methods. Here, we begin by getting a list of all the QDomNodes that use the specified tag. If the list is nonempty, we retrieve the first node. In the weather data, every tag is unique, so there should only ever be one node for a given tag. In the DOM API, the text between tags is held in a child node, so it can be retrieved from a node by retrieving the node’s first child, converting the child to a text node, and then retrieving its text data—for example, node.firstChild().toText().data(). Fortunately, Qt offers a shortcut—the QDomNode::nodeValue() method—which returns a node type-specific string, which in the case of text nodes is the text itself. QString WeatherTrayIcon::toolTipField(const QString &name, const QString &htmlColor, const QString &value, bool appendBr) { return QString("%1: %3%4") .arg(name).arg(htmlColor).arg(value) .arg(appendBr ? "
" : ""); } The private toolTipField() helper method allows us to factor out the formatting of each line of tooltip text. This slightly shortens and simplifies populateTool- Tip()’s code and also makes it easier to modify the formatting later on. If an icon must be downloaded, once the QNetworkReply is ready,the signal–slot connection set up earlier ensures that the readIcon() slot is called. We will look at this slot in two parts. ptg Internet-Aware Widgets 19 void WeatherTrayIcon::readIcon(QNetworkReply *reply) { QUrl redirect = reply->attribute( QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (redirect.isValid()) networkIconAccess->get(QNetworkRequest(redirect)); This method is called when a request to download an icon has finished. We begin by checking to see if instead of the reply we are expecting we have re- ceived a redirect of some kind. If this is the case we initiate a new GET request to retrieve the icon data using the redirect’s target URL. The QNetworkAccess- Manager doesn’t perform redirects automatically for security reasons, but here we have chosen to trust the site. If security is a concern, we should really check the redirected URL—for example, that it is from the same domain and that it doesn’t include some malicious JavaScript. In the common case, when there is no redirect in force, we will get an invalid redirect QUrl, and can proceed to read the reply’s data. else { QByteArray ba(reply->readAll()); QPixmap pixmap; if (pixmap.loadFromData(ba)) { QIcon *icon = new QIcon(pixmap); setIcon(*icon); iconCache.insert(reply->request().url(), icon); } } } If the reply is not a redirect then either we have the icon data or an error oc- curred. We read all the data that is available into a QByteArray and then feed the data to a QPixmap. If the QPixmap::loadFromData() method returns false then either the icon data was incomplete, corrupt, or in an unrecognized format, or there was a network error and no data was retrieved. In any of these cas- es we abandon the attempt to retrieve the icon, and the current icon remains unchanged. If the download was successful, we convert the QPixmap into a QIcon and set the icon as the tray icon. Then we add the icon keyed by its URL to the icon cache safe in the knowledge that we will never cache more than 100 icons and that we will never needlessly download an icon that is already in the cache. void WeatherTrayIcon::setAirport(QAction *action) { airport = action->data().toString(); QSettings settings; settings.setValue("airport", airport); ptg 20 Chapter 1. Hybrid Desktop/Internet Applications requestXml(); } This slot is called whenever the user chooses a new airport from the context menu. We retrieve the name of the airport, set this airport as the new default, and then call requestXml() to force the application to retrieve fresh weather data for the newly chosen airport. Notice that we don’t save any settings when the application terminates; instead we save the settings (in this case there is only one) whenever they are changed. This approach has the advantage that the settings are always up to date, even when the application is running or in the face of an unexpected crash, but the disadvantage that the code for saving settings could be spread all over the place, making maintenance more error-prone. We have now finished reviewing a small application that uses the high-level and easy-to-use QNetworkAccessManager class to do basic Internet downloading. In addition to the Weather Tray Icon application, the book’s examples also include the RssPanel application (rsspanel) shown in Figure 1.4. Figure 1.4 The RssPanel application The RssPanel application features an Internet-aware RssComboBox that popu- lates itself automatically from an RSS (Really Simple Syndication) feed—an XML file—given a suitable URL, and updates itself periodically. We won’t review the code for this example because it is structurally very similar to the Weather Tray Icon example. However,it might provide an easier starting point for creating your own Internet-aware widgets. It also uses a QXmlStreamReader subclass to parse the RSS data it downloads, rather than the QDomDocument used by the weathertrayicon example.★ The rest of the chapter will continue the theme of retrieving data from the Internet, but using WebKit to display data (i.e., HTML pages), and to perform operations on downloaded data. ★Eventually,the QtXml module which provides Qt’s DOM and SAX parsers may be phased out in favor of the much faster QXmlStreamReader and QXmlStreamWriter classes built into QtCore. ptg Using WebKit 21 Using WebKit |||| WebKit is an open source web content rendering and editing engine that was originally created by KDE (‘K’ Desktop Environment) developers. WebKit is now used as the basis for many web browsers, including Google’s Chrome, KDE’s Konqueror,and Mac OS X’s Safari, and is also used by most web-enabled mobile devices. WebKit aims to be standards compliant, and supports all the standard web technologies,including HTML5,SVG (Scalable Vector Graphics), CSS (Cascading Style Sheets—including CSS 3 Web Fonts), and JavaScript. Qt’s QtWebKit module provides a Qt-style interface for WebKit and makesWeb- Kit’s functionality available to Qt programmers,and also provides considerable additional functionality of its own. To be able to use the module, it is essential to add the line QT += webkit to the application’s .pro file. Table 1.1 The Main WebKit Classes Class Description QWebElement A class for accessing and editing a QWebFrame’s DOM elements with a jQuery-like API (Qt 4.6) QWebFrame A data object that represents a frame in a web page QWebHistory The history of visited links associated with a given QWebPage QWebHistoryItem An object that represents one visited link in a QWebHistory QWebPage A data object that represents a web page QWebSettings A data object that holds the settings used by a given QWebFrame or QWebPage QWebView A widget that visualizes a QWebPage The most important QtWebKit classes are shown in Table 1.1, and the relation- ships between some of them are shown in Figure 1.5. QWebView QWebPage QWebFrame QWebFrame QWebFrame ... QWebFrame QWebPage:: mainFrame()QWebFrame:: childFrames() Figure 1.5 Some QtWebKit classes in context ptg 22 Chapter 1. Hybrid Desktop/Internet Applications Note that the only widget in QtWebKit is QWebView. For example, both QWebPage and the (one or more) QWebFrames it contains are data classes. Using QWebPage it is possible to download web content behind the scenes, process that content, and then reflect the results into the user interface in whatever way we choose —something we will see in this section’s second subsection. Now that we have an initial impression of what the QtWebKit module provides, we will look at some examples of its use. In the first subsection we will create a web browser window component. In the second subsection we will create a web site-specific application that makes use of the browser component and that reads and processes web pages behind the scenes. And in the third subsection we will show how to embed Qt widgets and custom widgets into a web page. A Generic Web Browser Window Component ||| In the two following subsections we are going to use WebKit to help us develop two example hybrid desktop/Internet applications. In this subsection we will create the browser window component (browserwindow) shown in Figure 1.6 that the following examples make use of. Figure 1.6 The browser window component The browser window supports the standard browser features: forward, back, reload, cancel loading, zooming, open a given page, and the ability to return to a specific page in the browser’s history. It also has a context menu and a toolbar (which can be hidden). In addition, when a custom DEBUG symbol is defined (for example, by adding DEFINES += DEBUG in the .pro file), the browser window’s context menu shows an additional option, Inspect, that when invoked launches the WebKit Web Inspector shown in Figure 1.7. This is a useful debugging tool that can provide a wide variety of information about a web page, including the page’s DOM ptg Using WebKit 23 Figure 1.7 The Web Inspector (Document Object Model), and the resources it uses (for example, style sheets, images, and JavaScript scripts), including their sizes and load times, and a lot more besides. From Qt 4.6, the Web Inspector can be invoked in a more conventional way by creating a QWebInspector object, giving it a QWebPage, and then calling its show() method. For the Web Inspector to be available (and for the Qt 4.6 QWebInspector class to work) we must switch on the QWebSettings::DeveloperExtrasEnabled web setting. We do this, and set various other settings, in the application’s main() function, applying the changes to the global QWebSettings object, as this extract from main() shows. QWebSettings *webSettings = QWebSettings::globalSettings(); webSettings->setAttribute(QWebSettings::AutoLoadImages, true); webSettings->setAttribute(QWebSettings::JavascriptEnabled, true); webSettings->setAttribute(QWebSettings::PluginsEnabled, true); webSettings->setAttribute(QWebSettings::ZoomTextOnly, true); #ifdef DEBUG webSettings->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); #endif The global QWebSettings object’s settings are inherited by all of the application’s QWebPage and QWebView objects, although we can override them individually for every QWebPage and QWebView if we want to. The QWebSettings::ZoomTextOnly attribute was introduced in Qt 4.5, and affects the zoom factor. By setting the attribute to true we ensure that images are not zoomed (and therefore not distorted if they are pixmaps), so only the text is shrunk or enlarged when the user zooms. ptg 24 Chapter 1. Hybrid Desktop/Internet Applications At the time of this writing, the Qt documentation does not specify the web set- tings’ defaults, so they may vary across platforms or minor Qt 4.x versions. We can always check what a particular setting’s value is using QWebSettings::test- Attribute() which takes an attribute enum value and returns a bool. To get an overview of the browser window’s API we will look at the public and protected parts of the class definition in the header file. class BrowserWindow : public QFrame { Q_OBJECT public: explicit BrowserWindow(const QString &url=QString(), QWebPage *webPage=0, QWidget *parent=0, Qt::WindowFlags flags=0); QString toHtml() const { return webView->page()->mainFrame()->toHtml(); } QString toPlainText() const { return webView->page()->mainFrame()->toPlainText(); } signals: void loadFinished(bool ok); void urlChanged(const QUrl &url); public slots: void load(const QString &url); void setHtml(const QString &html) { webView->setHtml(html); } void showToolBar(bool on) { toolBar->setVisible(on); } void enableActions(bool enable); protected: void focusInEvent(QFocusEvent*) { webView->setFocus(); } Most of the functionality, particularly the toolbar and context menu actions, is provided by private slots (not shown) which we will discuss as we encounter them in the following code snippets. The reimplementation of the QWidget:: focusInEvent() is used to ensure that if the browser window is given the focus programmatically (by calling QWidget::setFocus() on it), the focus is passed on to the web view. The class also has some private variables (not shown) that provide access to its widgets. Thanks to the considerable out of the box functionality provided by QWebView, the BrowserWindow class is quite small, with most of the code in the constructor and the create methods it calls. Here’s the constructor. BrowserWindow::BrowserWindow(const QString &url, QWebPage *webPage, QWidget *parent, Qt::WindowFlags flags) ptg Using WebKit 25 : QFrame(parent, flags) { setFrameStyle(QFrame::Box|QFrame::Raised); webView = new QWebView; if (webPage) webView->setPage(webPage); load(url); createActions(); createToolBar(); createLayout(); createConnections(); } We made the browser window a QFrame subclass so that we can give it a frame. This is helpful to users since web pages often contain widgets of their own (buttons, line editors, and so on), so by framing the browser window we make clear the boundary between the web page and the application in which it is embedded. We allow the class’s clients to pass in their own QWebPage if they prefer; other- wise QWebView creates a QWebPage for itself. This is useful if we want to use a QWebPage subclass—something we will see in the “Embedding Qt Widgets in Web Pages” subsection (➤ 44). The createActions() method is slightly unusual because we only have to create a few of the actions ourselves. Here is an extract from the method which omits the creation of the zoomInAction, setUrlAction, and historyAction, since they are all created in the same way as the zoomOutAction that is shown. void BrowserWindow::createActions() { zoomOutAction = new QAction(QIcon(":/zoomout.png"), tr("Zoom Out"), this); zoomOutAction->setShortcuts(QKeySequence::ZoomOut); ··· QList actions; actions << webView->pageAction(QWebPage::Back) << webView->pageAction(QWebPage::Forward) << webView->pageAction(QWebPage::Reload) << webView->pageAction(QWebPage::Stop) << zoomOutAction << zoomInAction << setUrlAction << historyAction; #ifdef DEBUG actions << webView->pageAction(QWebPage::InspectElement); #endif AQP::accelerateActions(actions); ptg 26 Chapter 1. Hybrid Desktop/Internet Applications webView->addActions(actions); webView->setContextMenuPolicy(Qt::ActionsContextMenu); } We create a list of the actions we want the browser window to have, including the inspect action if DEBUG is set, and using QWebView’s predefined actions where possible. We then use AQP::accelerateActions() to provide keyboard accelerators (i.e., underlined letters, not keyboard shortcuts such as Ctrl+X), on non-Mac OS X platforms (15 ➤ ). Then we simply add the actions to the QWebView and tell it to provide a context menu using these actions. Note that we do not need to create signal–slot connections or provide slots for the actions provided by QWebView, since these are all built in. Figure 1.8 The browser window component on Mac OS X We have not shown the createToolBar() method since it is mostly standard for C++/Qt applications. However,as Figure 1.8 shows—compared with Figure 1.6 (22 ➤ )—the toolbar is laid out differently on Mac OS X than on other platforms. This is done because on Mac OS X it is common to have tool buttons display an icon with text beneath. And to make the entire toolbar layout consistent we put the labels for the zoom spinbox and for the progress bar beneath them rather than to their left as we did on other platforms. To achieve these differences we used #ifdefs in the code in browserwindow/browserwindow.cpp, making use of the fact that Q_WS_MAC is only defined on Mac OS X.★ The createLayout() method is small and standard, so we will skip it and move on to createConnections(). ★As noted in the Introduction, all the source code is available from www.qtrac.eu/aqpbook.html. ptg Using WebKit 27 void BrowserWindow::createConnections() { connect(webView, SIGNAL(loadProgress(int)), progressBar, SLOT(setValue(int))); connect(webView, SIGNAL(urlChanged(const QUrl&)), this, SLOT(urlChange(const QUrl&))); connect(webView, SIGNAL(loadFinished(bool)), this, SLOT(loadFinish(bool))); connect(setUrlAction, SIGNAL(triggered()), this, SLOT(setUrl())); connect(historyAction, SIGNAL(triggered()), this, SLOT(popUpHistoryMenu())); connect(zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut())); connect(zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn())); connect(zoomSpinBox, SIGNAL(valueChanged(int)), this, SLOT(setZoomFactor(int))); } We set up the first three signal–slot connections to allow us to keep track of loading progress and page changes so that we can keep the progress bar and progress label up to date. The remaining connections are for handling the actions we created ourselves, and to respond to the user interacting with the zoom spinbox. Now that we have seen enough to have an overview of the browser window,we can review the methods used to provide its behavior. void BrowserWindow::load(const QString &url) { if (url.isEmpty()) return; QString theUrl = url; if (!theUrl.contains("://")) theUrl.prepend("http://"); webView->load(theUrl); } If the given URL string is empty we do nothing; otherwise we prepend “http://” as a courtesy if no scheme is specified, and tell the web view to perform the load. (We use QString rather than QUrl since it is easier to correctly add the scheme to a string than to a QUrl.) void BrowserWindow::setUrl() { load(QInputDialog::getText(this, tr("Set URL"), tr("&URL:"))); } ptg 28 Chapter 1. Hybrid Desktop/Internet Applications If the user invokes the set URL action, this method is called. If they cancel, an empty URL will be passed to the load() method which will then harmlessly do nothing. void BrowserWindow::urlChange(const QUrl &url) { emit urlChanged(url); progressLabel->setText(tr("Loading")); } Whenever the web page’s URL changes (whether the user clicked a link, or used the set URL action, or used the history), this slot is called. We emit our own urlChanged() signal as a convenience to BrowserWindow users, and update the progress label to indicate that loading has commenced. void BrowserWindow::loadFinish(bool ok) { emit loadFinished(ok); progressLabel->setText(ok ? tr("Loaded") : tr("Canceled")); } When loading finishes, this slot is called with the bool ok signifying whether the load was successful. Again we emit a signal for the convenience of Browser- Window users, and again we update the progress label to reflect the current sit- uation. We don’t have to worry about keeping the progress bar up to date since we connected the web view’s loadProgress() signal to the progress bar’s setValue() slot at the end of the constructor. void BrowserWindow::setZoomFactor(int zoom) { webView->setZoomFactor(zoom / 100.0); } If the user manipulates the zoom spinbox this slot is called and the web view’s text is scaled accordingly. (If we want images to scale we must set the QWebSettings::ZoomTextOnly attribute to false.) const int ZoomStepSize = 5; void BrowserWindow::zoomOut() { zoomSpinBox->setValue(zoomSpinBox->value() - ZoomStepSize); } The zoomOutAction is connected to this slot. A similar zoom-in action and slot are also present, although not shown. When these slots are invoked the ptg Using WebKit 29 setValue() calls result in the spinbox emitting a valueChanged() signal and that leads to the setZoomFactor() slot being invoked because of the signal–slot connection we saw earlier. void BrowserWindow::enableActions(bool enable) { foreach (QAction *action, webView->actions()) action->setEnabled(enable); toolBar->setEnabled(enable); webView->setContextMenuPolicy(enable ? Qt::ActionsContextMenu : Qt::NoContextMenu); } In some use cases the application embedding the browser window does not want the user to be able to make use of the browser functionality beyond simply viewing and interacting with the page that is presented. This method makes it possible to disable or enable the browser window’s actions. const int MaxHistoryMenuItems = 20; const int MaxMenuWidth = 300; void BrowserWindow::popUpHistoryMenu() { QFontMetrics fontMetrics(font()); QMenu menu; QSet uniqueUrls; QListIterator i(webView->history()->items()); i.toBack(); while (i.hasPrevious() && uniqueUrls.count() < MaxHistoryMenuItems) { const QWebHistoryItem &item = i.previous(); if (uniqueUrls.contains(item.url())) continue; uniqueUrls << item.url(); QString title = fontMetrics.elidedText(item.title(), Qt::ElideRight, MaxMenuWidth); QAction *action = new QAction(item.icon(), title, &menu); action->setData(item.url()); menu.addAction(action); } AQP::accelerateMenu(&menu); if (QAction *action = menu.exec(QCursor::pos())) webView->load(action->data().toUrl()); } When this method is invoked it pops up a menu whose items correspond to the web pages the user has visited. The link data is retrieved from the QWebView’s ptg 30 Chapter 1. Hybrid Desktop/Internet Applications QWebHistory. This holds a list of QWebHistoryItems, each of which has the web page’s title, the page’s URL, the page’s icon—Qt provides a default icon if one isn’t available from the web page’s server—and a few other pieces of infor- mation. The menu presents the links in reverse order,that is, from most recently visited at the top to least recently visited at the bottom. It also imposes a limit on the number of items shown, and eliminates duplicates—which means that the or- der of visiting is not strictly preserved. Some pages have very long titles, and in such cases we elide the title at the right-hand end (i.e., chop off the excess text and replace it with an ellipsis, “…”), using QFontMetrics::elidedText().It is also possible to elide on the left or in the middle by passing Qt::ElideLeft or Qt::ElideMiddle as the second argument. As we mentioned earlier,prior to Qt 4.7, Qt does not provide a qHash(QUrl) func- tion, so we cannot store QUrlsinaQSet out of the box. Since QSet is implemented in terms of a QHash, the solution is to add exactly the same qHash(QUrl) one-liner that we used for the Weather Tray Icon application we saw earlier (11 ➤ ). If the user cancels the menu (e.g., by pressing Esc or by clicking elsewhere), QMenu::exec() will return 0; otherwise it will return the QAction corresponding to the menu option the user chose. If a QAction is returned we extract the URL that is held in its data. And once we have the URL, we ask the web view to load the corresponding page. We have now completed our review of the browser window component. There are other standard browser features that we could add, some of which are easily done since WebKit already provides the necessary functionality. For ex- ample, we could add a search text function based on the QWebView::findText() method, and a print page function based on the QWebFrame::print() or QWeb- Frame::render() method. In the following two subsections we will use the browser window as a funda- mental part of two hybrid desktop/Internet applications. We will also learn how to download web content invisibly behind the scenes and how to inject JavaScript into web pages so that we can extract information from them. And we will learn how to enhance the browser window so that it can seamlessly dis- play standard and custom Qt widgets that users can interact with. Creating Web Site-Specific Applications ||| If people are using one particular web site a lot of the time then it should be possible to provide them with more convenience and functionality by creating a web site-specific application geared to their needs. The danger of such ap- plications is that they can be vulnerable to changes in the web site, but this may be outweighed by the time savings achieved by the greater convenience of use—particularly if the site has a large number of users. Also, we might ptg Using WebKit 31 Figure 1.9 The New York Review of Books Viewer be able to contain the effects of such changes so that they only affect the JavaScript we use behind the scenes without requiring source code changes at all. Creating a custom site-specific application can also ensure that users can only access the site using a custom client application. Perhaps the best known example of this is Apple’s iTunes Music Store, which (at the time of this writing) cannot be used with a standard web browser. In this subsection we will look at the New York Review of Books Viewer appli- cation (nyrbviewer) shown in Figure 1.9. This application shows pages from the NYRB (New York Review of Books) using the browser window component from the previous subsection. What makes this application stand out as more conve- nient to use than viewing the site in a web browser is that it offers comboboxes listing the issues and the articles within a selected issue. This gives the user an easy way of seeing what issues and articles are available and a fast way of choosing an article to read. Most of the functionality is already present in the browser window component, so we only need to concentrate on how we are going to populate the comboboxes with the correct data and how we are going to make them work. We will start by looking at the constructor. const QString NYRBUrl("http://www.nybooks.com"); ptg 32 Chapter 1. Hybrid Desktop/Internet Applications MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { createWidgets(); createLayout(); issueLinkFetcher = new LinkFetcher(NYRBUrl, scriptPathAndName("fetch_issue_links.js"), this); articleLinkFetcher = new LinkFetcher(NYRBUrl, scriptPathAndName("fetch_article_links.js"), this); createConnections(); AQP::accelerateWidget(this); issueComboBox->setFocus(); issueLinkFetcher->load(NYRBUrl); setWindowTitle(QApplication::applicationName()); QTimer::singleShot(1000 * 60, this, SLOT(networkTimeout())); } We create the widgets and layouts in the usual way. The AQP::accelerate- Widget() function uses QObject::findChildren() to find all the QLabels and QAbstractButtons (and their subclasses, apart from QToolButtons) that are chil- dren of the window and sets keyboard accelerators for them (15 ➤ ). One novel aspect of the constructor is the creation of the two LinkFetcher objects. These objects create QWebPage objects behind the scenes—recall that QWebPages are data objects—and have their QWebPages download a specified page. They then inject the JavaScript they are given when they are constructed into the page, and this is used to extract relevant links. The web site URL they are given is used to convert any relative links such as articles/22273 into absolute links such as http://www.nybooks.com/articles/22273. Once the link fetchers are created and connected, we start things off by telling the issueLinkFetcher to download the NYRB web site’s main page and to use the fetch_issue_links.js script to extract the links to all the issues; these can then be used to populate the issues combobox. And whenever an issue is chosen, the articleLinkFetcher is told to download the current issue’s page and uses the fetch_article_links.js script to extract the links to all the issue’s articles; these can be used to populate the articles combobox. We will look at the LinkFetcher class toward the end of this subsection. We created the scriptPathAndName() method because we want to have some flexibility regarding the scripts, as we will discuss in a moment. One final aspect is that we have set up a single shot timer. This will time out after a minute and call the networkTimeout() slot, so that we can provide an error message if it transpires that the Internet is unreachable. QString MainWindow::scriptPathAndName(const QString &filename) { ptg Using WebKit 33 QString name = filename; QString path = AQP::applicationPathOf() + "/"; if (QFile::exists(path + name)) return path + name; return QString(":/%1").arg(name); } This method looks for the script in the directory containing the application’s executable and if it is found returns the full path and name for reading by the link checker. But if the script isn’t in the filesystem then we fall back to using a script that has been embedded in the executable as a resource. This means that by default the application uses its own embedded scripts, but if we need to change the scripts—for example, if the web site undergoes changes—we can simply put updated scripts in the excecutable’s directory and they will automatically be found and used in preference to the application’s embedded scripts. (Of course those concerned with security might prefer to use a binary file format with a checksum or other measure to ensure that a locally installed script is legitimate, but such issues are beyond the scope of this book.) The aqp.{hpp,cpp} module’s AQP::applicationPathOf() function returns the path of the application (as a QString), or the path of the subdirectory inside the ex- ecutable’s directory, if a subdirectory is given as an argument. We cannot use QApplication::applicationDirPath() directly,since that does not account for the fact that the executable may be in a different directory depending on whether it is in its released form or is under development. (For example, during devel- opment on Windows the executable is normally in the debug or release subdi- rectory.) const QString InitialMessage( QObject::tr("Attempting to connect to the network...")); const QString FailMessage( QObject::tr("No issues or articles available")); void MainWindow::networkTimeout() { const QString text = browser->toPlainText().trimmed(); if (text == InitialMessage || text == FailMessage) browser->setHtml("

Failed to connect " "to the network

Perhaps the proxy " "settings are wrong, or maybe a proxy is needed. " "Try:
nyrbviewer --help"); } One minute after the application starts up this slot is called. If no Internet connection has been established, the browser’s text will be the same as the InitialMessage or the FailMessage, so we try to provide some help to the user. (See the “Supporting Network Proxying” sidebar; 9 ➤ .) ptg 34 Chapter 1. Hybrid Desktop/Internet Applications In the header file we have a private variable, namesForUrlsForIssueCache: QHash > namesForUrlsForIssueCache; This cache’s keys are the index positions of items in the issue combobox, and its values are maps whose keys are URLs and whose values are article names. We will see how it is used when we look at the main window’s methods, but note for now that link fetchers can return a URL–name map for the links they have retrieved from the page they have downloaded. (We don’t use a QCache because we don’t ever want to get rid of the cached data.) We won’t show the createWidgets() and createLayout() methods since they are standard C++/Qt, but we will look at createConnections(). void MainWindow::createConnections() { connect(issueLinkFetcher, SIGNAL(finished(bool)), this, SLOT(populateIssueComboBox(bool))); connect(articleLinkFetcher, SIGNAL(finished(bool)), this, SLOT(populateCache(bool))); connect(issueComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIssueIndexChanged(int))); connect(articleComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(currentArticleIndexChanged(int))); connect(quitButton, SIGNAL(clicked()), this, SLOT(close())); } Whenever a link fetcher finishes downloading a page and extracting the rele- vant links, it emits a finished(bool) signal with the Boolean indicating success or failure. For the issue link fetcher we connect directly to the populateIssue- ComboBox() slot, but for the article link fetcher we connect to the populateCache() slot, which in turn calls populateArticleComboBox() once the cache has been pop- ulated. If the user chooses a different issue we must repopulate the article combobox, and if the user chooses an article we must load it into the browser window. These behaviors are set up by the third and fourth signal–slot connections. The application’s overall flow of control is illustrated in Figure 1.10. Now we will review the main window’s methods, starting with those concern- ing the issue combobox and then looking at those that concern the article combobox. void MainWindow::populateIssueComboBox(bool ok) { if (ok) populateAComboBox(tr("- no issue selected -"), issueLinkFetcher->namesForUrls(), issueComboBox); ptg Using WebKit 35 else { issueComboBox->clear(); issueComboBox->addItem(tr("- no issues available -")); } } This slot is called only once, when the LinkFetcher::load() call made on the issueLinkFetcher in the main window’s constructor eventually results in a Link- Fetcher::finished() signal being emitted. The issue link fetcher retrieves the names and URLs of all the issues (since 2000). Once the retrieval is finished, the results are made available as a QMap where the URLs are keys and the issue names are values. We send this map to the populate- AComboBox() method and ask it to populate the issues combobox using the map’s data. void MainWindow::populateAComboBox(const QString &statusText, const QMap &namesForUrls, QComboBox *comboBox) { comboBox->clear(); comboBox->addItem(statusText); QMapIterator i(namesForUrls); i.toBack(); while (i.hasPrevious()) { i.previous(); comboBox->addItem(i.value(), i.key()); } if (comboBox->count() > 1) comboBox->setCurrentIndex(1); } This method is called from both the populateIssueComboBox() slot and from the populateArticleComboBox() method. We start by clearing the combobox’s original contents and adding the status text as the first item. Then we iterate in reverse order over the URL–issue name map (or the URL–article name map, if it is being used to populate the articles combobox) that is passed in, and that was returned by a link fetcher. For the NYRB web site’s issues and articles, the URLs encode the date, for example, /contents/20090115, so we are iterating in date order from most to least recent. For issues, the names are simply dates in U.S. format (e.g., “Jan 15, 2009”), and these are added as combobox item texts, with the URLs added as item data. For articles, the names are the actual article titles. If there is more than one item, that is, there is at least one issue or article, we make the first (most recent) issue or article the current one, and this in turn ptg 36 Chapter 1. Hybrid Desktop/Internet Applications Start issueLinkFetcher::load() populateIssueComboBox() www.nybook.com User chooses an issue Issue’s articles in the cache? articleLinkFetcher::load() populateArticleComboBox() populateCache() No Y Yes Figure 1.10 The New York Review of Books Viewer’s flow of control will cause the currentIssueIndexChanged() slot or the currentArticleIndex- Changed() slot to be called. void MainWindow::currentIssueIndexChanged(int index) { articleComboBox->clear(); if (index == 0) { articleComboBox->addItem(tr("- no issue selected -")); return; } if (namesForUrlsForIssueCache.contains(index)) populateArticleComboBox(); else { articleComboBox->addItem( tr("+ fetching the list of articles +")); browser->setHtml(tr("

" "Fetching the list of articles...

")); QString url = issueComboBox->itemData(index).toString(); articleLinkFetcher->load(url); } } If a new issue is chosen we start by clearing the article combobox. If an issue is selected we check to see if we have already downloaded its URL–article name map; if we have, we can simply call populateArticleComboBox() which always retrieves its data from the cache. If there is no cache entry for the newly chosen issue, we add status-indicating text to the article combobox and to the browser window. Then we retrieve the issue’s URL (which is held in the issue combobox item’s data), and tell the article link fetcher to download the issue’s page and extract the article links ptg Using WebKit 37 from it. Once the download finishes the populateCache() slot is called thanks to the signal–slot connection we created earlier. void MainWindow::populateCache(bool ok) { if (!ok || issueComboBox->count() == 1) { articleComboBox->setItemText(0, tr("- no articles available -")); browser->setHtml(tr("

%1

") .arg(FailMessage)); return; } QTextDocument document; QMap namesForUrls = articleLinkFetcher->namesForUrls(); QMutableMapIterator i(namesForUrls); while (i.hasNext()) { i.next(); document.setHtml(i.value()); i.setValue(document.toPlainText()); } namesForUrlsForIssueCache[issueComboBox->currentIndex()] = namesForUrls; populateArticleComboBox(); } If the download of URL–article names failed we inform the user via both the article combobox and by putting some text in the browser window. In the case of success we retrieve the URL–article name map from the article link fetcher. Then we add the now modified map to the cache and call populateArticleCombo- Box(). We need the article names as plain text since they are going into a QCombo- Box (which cannot display rich text). Rather than creating and destroying a QTextDocumentFragment each time (i.e., i.setValue(QTextDocumentFragment::from- Html(i.value()).toPlainText());), we create a single QTextDocument and at each iteration set its HTML text and then call its toPlainText() method. void MainWindow::populateArticleComboBox() { int index = issueComboBox->currentIndex(); if (index > 0) populateAComboBox(tr("- no article selected -"), namesForUrlsForIssueCache[index], articleComboBox); } ptg 38 Chapter 1. Hybrid Desktop/Internet Applications This method is only ever called if the cache has the required data, so we know that if an issue has been chosen then its links are in the cache. We populate the combobox using the populateAComboBox() method shown earlier (35 ➤ ), passing it a status text to add as the first item, the current issue’s URL–article name map from the cache, and the combobox we want populated. If the user has chosen the first item in the issue combobox (the “no issue selected” item), we do nothing. void MainWindow::currentArticleIndexChanged(int index) { if (index == 0) return; QString url = articleComboBox->itemData(index).toString(); browser->load(url); browser->setFocus(); } When the user chooses a new item in the article combobox (apart from the first “no article selected” item), we retrieve the URL that is associated with the ar- ticle title from the combobox item’s data, and tell the browser window to load the corresponding page. We also give the browser window focus (which actually goes to the browser window’s QWebView), so that the user can immediately begin scrolling with the keyboard up and down arrow keys. We have now seen the complete implementation of the New York Review of Books Viewer application (except for the header, and the createWidgets() and createLayout() methods). We will now review the LinkFetcher class to see how that works behind the scenes to download pages and extract links. Here is its header: class LinkFetcher : public QObject { Q_OBJECT public: explicit LinkFetcher(const QString &site_, const QString &scriptOrScriptName_, QObject *parent=0); void load(const QString &url); QMap namesForUrls() const { return m_namesForUrls; } void clear() { m_namesForUrls.clear(); } signals: void finished(bool); public slots: void addUrlAndName(const QString &url, const QString &name); ptg Using WebKit 39 private slots: void injectJavaScriptIntoWindowObject(); void fetchLinks(bool ok); private: QWebPage page; QMap m_namesForUrls; const QString site; const QString scriptOrScriptName; }; This class uses the QWebPage data class to load the URL it is given. It then uses the JavaScript script it was given to extract the relevant links from the page and populate the m_namesForUrls map with each link’s URL and name. We can make any QObject accessible to JavaScript. This means that the JavaScript that is injected into the pages that are downloaded can not only access the pages’ elements (using the HTML Document Object Model), but can also access any QObjects we have made available. In particular,the JavaScript can do method calls on a QObject’s public slots and can access a QObject’s proper- ties if it has any. In this case we will be passing a reference to the link fetcher instance, so the injected JavaScript script can communicate with our C++ code by calling the link fetcher’s public slots. LinkFetcher::LinkFetcher(const QString &site_, const QString &scriptOrScriptName_, QObject *parent) : QObject(parent), site(site_), scriptOrScriptName(scriptOrScriptName_) { QWebSettings *webSettings = page.settings(); webSettings->setAttribute(QWebSettings::AutoLoadImages, false); webSettings->setAttribute(QWebSettings::PluginsEnabled, false); webSettings->setAttribute(QWebSettings::JavaEnabled, false); webSettings->setAttribute(QWebSettings::JavascriptEnabled, true); webSettings->setAttribute(QWebSettings::PrivateBrowsingEnabled, true); connect(page.mainFrame(), SIGNAL(javaScriptWindowObjectCleared()), this, SLOT(injectJavaScriptIntoWindowObject())); connect(&page, SIGNAL(loadFinished(bool)), this, SLOT(fetchLinks(bool))); } Since we are downloading the page behind the scenes and are only interest- ed in its links, we change the QWebPage’s page settings. We switch off image downloading, plugins, and Java, and switch on JavaScript, and also QWeb- Settings::PrivateBrowsingEnabled, which prevents the QWebPage from recording ptg 40 Chapter 1. Hybrid Desktop/Internet Applications any history or storing any web page icons since neither is of any use to us and would otherwise consume some memory and CPU time. Whenever a page load is initiated, the JavaScript window objects for its QWebFrames are cleared, ready for any new JavaScript that the new page’s frame or frames may contain. Since we need to inject our own JavaScript into the main frame’s window object for each page that is downloaded, we must ensure that our JavaScript is re-injected whenever the JavaScript window object is cleared. This is achieved by the first signal–slot connection shown here. The second signal–slot connection ensures that as soon as the page has been downloaded we try to extract its links. void LinkFetcher::injectJavaScriptIntoWindowObject() { page.mainFrame()->addToJavaScriptWindowObject("linkFetcher", this); } The QWebFrame::addToJavaScriptWindowObject() method can add any QObject to a QWebFrame’s JavaScript window objects. The string first argument is the name that the object will be accessible as in JavaScript (in this example, linkFetcher), and the QObject second argument is a reference to the actual object—in this case an instance of the LinkFetcher class itself. void LinkFetcher::load(const QString &url) { clear(); page.mainFrame()->load(QUrl(url)); } When the link fetcher is given a URL to load it starts by clearing the m_names- ForUrls map and then tells the QWebPage’s main frame to load the page. Once loading is finished the fetchLinks() slot is called because of the signal–slot con- nection set up earlier. void LinkFetcher::fetchLinks(bool ok) { if (!ok) { emit finished(false); return; } QString javaScript = scriptOrScriptName; if (scriptOrScriptName.endsWith(".js")) { QFile file(scriptOrScriptName); if (!file.open(QIODevice::ReadOnly)) { emit finished(false); return; ptg Using WebKit 41 } javaScript = QString::fromUtf8(file.readAll()); } QWebFrame *frame = page.mainFrame(); frame->evaluateJavaScript(javaScript); emit finished(true); } If the load failed we notify any connected objects. Otherwise we check to see if the QString scriptOrScriptName private member variable holds an actual script or only the name of a script; and in the latter case we attempt to read in the script’s text. Instead of creating a QTextStream, we open the file in binary mode and convert the QByteArray returned by the QFile::readAll() method into Unicode using the static QString::fromUtf8() method. Once the script is ready we tell the QWebPage’s main frame to evaluate it, and then we notify any connected objects that we have successfully finished. QWebPage QObject QWebFrame JavaScript t addToJavaScriptWindowObject() a evaluateJavaScript() Access the object’s properties and call its methods r mainFrame() Figure 1.11 Injecting JavaScript to access HTML elements and an application’s QObjects The QWebFrame::evaluateJavaScript() method returns a QVariant that holds the value of the last JavaScript expression executed; we have ignored it because we have chosen a more versatile approach to JavaScript⇔C++communication. Since we have set a reference to the link checker itself as an object accessible to the JavaScript script, that script can call any of the link checker’s public slots and access its properties. In this case we have provided the addUrlAndName() slot expressly for the use of JavaScript scripts. void LinkFetcher::addUrlAndName(const QString &url, const QString &name) { if (url.startsWith("http://")) m_namesForUrls[url] = name; else m_namesForUrls[site + url] = name; } ptg 42 Chapter 1. Hybrid Desktop/Internet Applications Whenever the JavaScript script obtains the details of a link it calls this slot with the link’s URL and name, and as a result the m_namesForUrls map is populated. Since the URLs might be relative or absolute, we prefix the relative ones with the site that was given to the link fetcher when it was constructed. Figure 1.11 illustrates how by adding an application QObject to a web page, JavaScript executed in the context of the web page can access both the page’s HTML elements (via the Document Object Model), and any application QObjects we give the page access to. We have now completed our review of the LinkFetcher class, but to make sure we understand how things work we will show the fetch_article_links.js script (since it is by far the shorter of the two), just to see how it works and how it interacts with the link fetcher. Java- Script var links = document.getElementsByTagName("a"); for (var i = 0; i < links.length; ++i) { if (links[i].href.search("/articles/") != -1) { linkFetcher.addUrlAndName(links[i].href, links[i].innerHTML); } } The JavaScript getElementsByTagName() method is used to retrieve all the “a” (anchor) tags—these are the ones that hold links, with the link itself held in the href property and the text between the and tags as the innerHTML property. The key point to notice is the last statement, where we call LinkFetcher::add- UrlAndName() using the linkFetcher reference to the C++ link fetcher object. The original fetch_issue_links.js script is very similar to the fetch_article_ links.js script, but a bit longer (around 20 lines), since we read the issue links out of a combobox that appeared on the web site’s home page. About nine months into the writing of this book, the NYRB web site made con- siderable changes to its home page, and the issues combobox that the original fetch_issue_links.js script read the list of issues from has now gone. Nor do any other pages appear to provide such a combobox, or the list of issues in any other form. However, their web master pointed out that they have one page for each year that lists that year’s issues. Armed with this information, we were easily able to write a new fetch_issue_links.js script—around 40 lines long—and simply put the new script in the same directory as the executable. And since we designed the application to use scripts it finds in the same direc- tory as the executable in preference to the scripts embedded in its resources, without even needing to be recompiled, the program automatically uses the new fetch_issue_links.js script. Since the new fetch_issue_links.js script must read one page per year of issues—we have set it to read the past five years—it runs slower than the ptg Using WebKit 43 original because it does a synchronous GET request for each page.★ Nonetheless, the solution was easy to implement and works well. However,in the long term, we would want to avoid doing synchronous downloads in JavaScript, since they block the GUI application’s event loop. So in this case the ideal solution would be to redesign the program so that it uses Qt’s networking classes to do the GET requests to produce the list of issues, while keeping the fetch_article_links.js script for the articles. We leave this as an exercise, since it should be quite straightforward to do. Before finishing this subsection’s coverage of using JavaScript, it is worth dis- cussing how to debug applications that use injected JavaScript like this—since it isn’t easy! One simple way of dealing with the problem is to make sure that every script returns a value when evaluated, and to retrieve and test the value. In this example, we have taken a more versatile approach, and added a new public slot to the link fetcher’s header file: void debug(const QString &value) { qDebug("%s", qPrintable(value)); } During development we called this slot from inside the JavaScript scripts (the debug() calls are still there in the source code, but commented out). For example, we have the following line inside the fetch_article_links.js script’s for loop (although we didn’t show it when we quoted the script earlier): Java- Script linkFetcher.debug(links[i].href + " * " + links[i].innerHTML); Adding debug() statements like this can be very useful. (Windows users must of course add CONFIG += console to their .pro file so that the debugging output will be visible.) Unfortunately,using debug() statements doesn’t work if the script has a syntax error, since the script won’t run at all. One way to check a script for syntax errors is to pass its filename as the command line argument to Qt’s qscript program (in Qt’s examples directory). If the syntax is okay, qscript will attempt to run the script; otherwise it will provide an error message with the line number where the first error was found. The New York Review of Books Viewer application covered in this subsection makes use of fairly basic JavaScript scripts (although the new fetch_issue_ links.js script is more complex). Much more sophisticated scripts can be writ- ten, especially since scripts can access a downloaded page’s DOM (Document Object Model). We can also use Qt 4.6’s QWebElement class to access (and even modify) web pages via their DOM, which is very convenient for web sites that we know will remain stable or that we have control over. So, in each case, we must make the trade-off between the ease and convenience with which we can ★ The new script uses the XMLHttpRequest object and is based on ideas from JavaScript: The Definitive Guide by David Flanagan, ISBN 9780596101992. ptg 44 Chapter 1. Hybrid Desktop/Internet Applications change our JavaScript scripts to keep up with web site changes, versus the power and asynchronicity of Qt’s networking and web classes which make it easy to present a responsive user interface and allow us to do all our web pro- gramming in pure C++. Embedding Qt Widgets in Web Pages ||| The range of widgets available in HTML is rather limited. Various solutions are possible, such as using proprietary content formats like Flash or using proprietary browser extensions such as those available for Internet Explorer, or by embedding a Java application. All these approaches require that the user’s browser support them and this may not be possible in all cross-platform contexts. Another disadvantage of using proprietary formats or extensions is that we are limited to whatever functionality they provide. Another solution is to embed Qt widgets. This has the advantages that we get complete control over the behavior and appearance of the widgets we embed, and it allows us complete freedom to build in whatever functionality we need. The disadvantage of this approach is that the browser must be able to support embedded Qt widgets. Figure 1.12 The Matrix Quiz web page In this subsection we will review the Matrix Quiz web page shown in Fig- ure 1.12. This web page is embedded in a browser window component and used in the Matrix Quiz application (matrixquiz). The web page shows two randomly generated 3 × 3 matrices and invites the user to add them up by entering the appropriate values in the third matrix whose initial values are all zeros. If the user presses the New button a new pair of matrices is generated. If the user presses the Submit button the third ma- trix’s values are compared to what they ought to be, and any incorrect values are highlighted in red—for example, cells containing the values 181 and 187 in ptg Using WebKit 45 Figure 1.12. The cursor cell is shown in reverse video (e.g., white on black), and with a focus rectangle—here, for example, the cell containing value 116. The web page is made up of a mixture of HTML elements and standard and custom Qt widgets. The title text, the “Name:” label, and the large “+” and “=” signs are all standard HTML elements. The name line edit, the buttons, and the result label could also have been HTML elements, but for the sake of the example we have used standard Qt widgets. The matrix widgets are custom widgets (a simple QTableWidget subclass)—something not possible in pure HTML. As implemented, the browser window component we created earlier does not support embedded Qt widgets. We have added support simply by creating a custom QWebPage subclass that has the necessary support, and passing that in to the browser window rather than letting the browser window create its own standard QWebPage. We will start reviewing the code by looking at a group of three tiny extracts from the Matrix Quiz application’s main() function. qsrand(static_cast(time(0))); QWebSettings *webSettings = QWebSettings::globalSettings(); webSettings->setAttribute(QWebSettings::AutoLoadImages, true); webSettings->setAttribute(QWebSettings::JavascriptEnabled, true); webSettings->setAttribute(QWebSettings::PluginsEnabled, true); QString url = QUrl::fromLocalFile(AQP::applicationPathOf() + "/matrixquiz.html").toString(); BrowserWindow *browser = new BrowserWindow(url, new WebPage); browser->showToolBar(false); browser->enableActions(false); Qt’s global qsrand() function is used to seed Qt’s random number generator. Without this call, calls to qrand() would always return the same sequence of random numbers (since the seed defaults to 1 unless we explicitly set it). We prefer to use Qt’s random functions since not all platforms (e.g.,some embedded systems) support them, and although not relevant here, they are also thread- safe. (Qt’s global functions, including qsrand(), are shown in Table 1.2.) To make use of JavaScript we must enable it, and similarly to make use of embedded widgets we must enable plugins. The QUrl class is normally used to create URLs for web pages on the Internet, but here we have used it to create a URL for a web page in the local filesystem using the file:// scheme. When we create the BrowserWindow instance we not only pass it the URL of the web page we want it to use (as a string), but an instance of our custom WebPage class. We also hide the browser window’s toolbar and switch off all its actions— ptg 46 Chapter 1. Hybrid Desktop/Internet Applications Table 1.2 Qt’s Global Utility Functions Function/Example Description u = qAbs(n); Returns the absolute (positive) value of n x = qBound(min, n, max); Returns n if min <= n <= max; otherwise returns min if n < min; otherwise returns max qDebug("%d: %s", integer, qPrintable(string)); Prints C++ POD types to the console using printf() syntax; doesn’t understand Qt types (Windows .pro files need CONFIG += console) qDebug() << number << string << hash << stringlist << map << variant << object; Prints C++POD types and any Qt QObjects to the console, including collections such as QHash and QMap; requires #include (Windows .pro files need CONFIG += console) b = qFuzzyCompare(f, g); Returns true if floating-point numbers (or QTransforms in Qt 4.6) f and g can be considered to be equal x = qMax(n, m); Returns the larger of n and m x = qMin(n, m); Returns the smaller of n and m const char *s = qPrintable(qstring); Returns a char* (using the local 8-bit encoding) from a QString suitable for printf() or qDebug() x = qRound(f); Returns f rounded to the nearest integer, as an int x = qRound64(f); Returns f rounded to the nearest integer, as a qint64 x = qrand(); Returns a pseudo-random number between 0 and RAND_MAX (as defined in ). This thread-safe function uses a default seed of 1; call qsrand() to set a different seed qsrand(u); Seeds the pseudo-random number generator with uint u s = qVersion(); Returns a const char* that specifies the version of Qt the application is using (e.g., "4.6.2") now it cannot be used as a general browser but only to view and interact with the page we have specified. For the rest of the code we will begin by reviewing the small WebPage class that provides support for embedded Qt widgets. Then we will look at the custom MatrixWidget class, and finally we will look at the matrixquiz.html page to see how the widgets are embedded and to see the JavaScript that is used to provide some of the page’s functionality. ptg Using WebKit 47 HTML document QWebPage createPlugin() Called for each of type x-qt-plugin Returns a QObject Access the object’s properties and call its methods Figure 1.13 Accessing QObjects embedded in an HTML document using JavaScript The WebPage class is a QWebPage subclass. The constructor (not shown) simply passes its optional parent argument to the base class; its body is empty. The only method that the subclass reimplements is the protected createPlugin() method, but before looking at that, let’s look at an extract from matrixquiz.html showing how one of the buttons is created. Can't load QPushButton plugin! Whenever an HTML tag with a type attribute of application/x-qt- plugin is encountered in a page held by a QWebPage, the createPlugin() method is called. This is illustrated in Figure 1.13. QObject* WebPage::createPlugin(const QString &classId, const QUrl&, const QStringList ¶meterNames, const QStringList ¶meterValues) { QWidget *widget = 0; if (classId == "MatrixWidget") { widget = new MatrixWidget(view()); int index = parameterNames.indexOf("readonly"); if (index > -1) static_cast(widget)->setReadOnly( static_cast(parameterValues[index].toInt())); } else { QUiLoader loader; widget = loader.createWidget(classId, view()); } if (widget) { int index = parameterNames.indexOf("width"); if (index > -1) ptg 48 Chapter 1. Hybrid Desktop/Internet Applications widget->setMinimumWidth(parameterValues[index].toInt()); index = parameterNames.indexOf("height"); if (index > -1) widget->setMinimumHeight(parameterValues[index].toInt()); } return widget; } The default implementation returns 0, and in such cases the tag’s text (if any) is rendered rather than the intended object. The classId argument is given the name of the object’s class specified in the tag as the classid attribute. Any other tag attributes are passed in two parallel string lists, the first holding the attribute names and the second the corresponding attribute values. So, for the button we saw earlier, the parameterNames list is ["id", "height", "width"], and the parameterValues list is ["newButton", "40", "100"]. The method starts by creating the requested widget as a child of the WebPage’s QWebView. Any not-null widget returned by createPlugin() is rendered inside the HTML page by the QWebPage’s associated QWebView. If the requested widget class is MatrixWidget, we create a suitable instance and call MatrixWidget::readOnly() with a Boolean value if the parameterNames list has a "readonly" item. (In the HTML, we have made the first two matrices read-only, and the third one read-write.) For any other kind of widget that is requested, that is, for any standard Qt widget, we could use the same technique as we use to create MatrixWidgets. But this would lead to a very long sequence of if … else statements checking for classIds and would be tedious to maintain. Fortunately, Qt already has a class that can create instances of standard Qt widgets based on their class name:QUiLoader.Thisclasswas originally designed to support the dynamic loading and rendering of Qt Designer .ui files, but here we have used its QUiLoader::createWidget() method to instantiate and return a pointer to the requested widget. (Note that to use the QUiLoader class we must include the line CONFIG += uitools in the application’s .pro file.) Once the widget is created, we set its minimum width and height if these are given as attributes. We don’t use the id attribute in our C++ code, but we do use it in the JavaScript that provides the web page’s behavior beyond that provided by the MatrixWidget and the other Qt classes. We want the custom MatrixWidget class to be programmable using JavaScript in a web page. Just as with QtScript, a class’s Q_PROPERTYs are available in JavaScript as JavaScript properties and a class’s public slots are available as JavaScript methods. Here’s the MatrixWidget’s definition from its header file: ptg Using WebKit 49 class MatrixWidget : public QTableWidget { Q_OBJECT public: explicit MatrixWidget(QWidget *parent=0); public slots: void clearMatrix(); void repopulateMatrix(); QString valueAt(int row, int column) const { return item(row, column)->text(); } void setValueAt(int row, int column, const QString &value) { item(row, column)->setText(value); } void setHighlighted(int row, int column, bool highlight=true) { item(row, column)->setBackground(highlight ? Qt::red : Qt::white); } void setReadOnly(bool read_only); }; We defined some public slots—these will be accessible from JavaScript; but we did not need to create any custom properties since the base class’s rowCount and columnCount properties are accessible to JavaScript and those are the only ones needed. The setHighlighted() method sets a table item’s background—this is used to highlight cells that have incorrect values. const int ColumnWidth = 40; MatrixWidget::MatrixWidget(QWidget *parent) : QTableWidget(3, 3, parent) { verticalHeader()->hide(); horizontalHeader()->hide(); for (int row = 0; row < rowCount(); ++row) { for (int column = 0; column < columnCount(); ++column) { QTableWidgetItem *item = new QTableWidgetItem("0"); item->setTextAlignment(Qt::AlignCenter); setItem(row, column, item); if (row == 0) setColumnWidth(column, ColumnWidth); } } } We use the constructor to create a QTableWidget with a fixed number of rows and columns and with a fixed column width. Every item is initialized to contain the text “0” and to have centered alignment. Both the vertical and ptg 50 Chapter 1. Hybrid Desktop/Internet Applications horizontal headers are hidden, which is why the widget looks rather different from a standard QTableWidget. void MatrixWidget::setReadOnly(bool read_only) { setEditTriggers(read_only ? QAbstractItemView::NoEditTriggers : QAbstractItemView::AllEditTriggers); setFocusPolicy(read_only ? Qt::NoFocus : Qt::WheelFocus); } If the widget is set to read-only we turn off all the edit triggers. We also change its focus policy so that it cannot accept keyboard focus; this means that when a keyboard user presses Tab in a widget preceding a read-only matrix widget the focus will bypass the read-only widget and go to the next focus-accepting widget. In this example the focus starts in the “name” line edit; if the user presses Tab the focus will then go to the read-write matrix widget they must enter their answer in, skipping the two read-only matrix widgets in between. void MatrixWidget::repopulateMatrix() { for (int row = 0; row < rowCount(); ++row) { for (int column = 0; column < columnCount(); ++column) item(row, column)->setText( QString::number(qrand() % 100)); } } This method is used to populate the widget with random integers (as strings) between 0 and 99. The clearMatrix() method (not shown) is structurally very similar, only it sets every cell item’s text to “0” and its background to white. We have now seen all the relevant C++ code. The matrixquiz.html file contains the HTML for specifying the HTML elements and also an tag for each of the Qt widgets and for each of the MatrixWidgets. These all follow the same pattern as we saw earlier, but just to demonstrate that there is no difference between using a standard Qt widget and a custom one, here is the tag for the first of the MatrixWidgets: Can't load MatrixWidget plugin! The matrixquiz.html file provides the web page’s behavior using JavaScript. The