Académique Documents
Professionnel Documents
Culture Documents
Qt 4
et
++
C
Programmation dinterfaces GUI
Rseaux
et tlcom
Programmation
Gnie logiciel
Scurit
Systme
dexploitation
Qt4 et C++
Programmation dinterfaces GUI
Jasmin Blanchette
et Mark Summerfield
CampusPress a apport le plus grand soin la ralisation de ce livre afin de vous fournir une information complte et fiable. Cependant, CampusPress nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient
rsulter de cette utilisation.
Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions
thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle.
CampusPress ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de
quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes.
Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs
propritaires respectifs.
Publi par CampusPress
47 bis, rue des Vinaigriers
75010 PARIS
Tl. : 01 72 74 90 00
ISBN : 978-2-7440-4092-4
Copyright 2009 Pearson Education France
Tous droits rservs
All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system,
without permission from Pearson Education, Inc.
Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a)
du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France
ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code.
VII
57
Avant-propos ..........................................
IX
64
Prface ....................................................
XI
70
Remerciements ......................................
XIII
72
75
XV
3
4
6
7
10
78
79
85
88
92
96
105
106
108
118
122
141
143
45
46
51
56
144
150
Sparateurs .........................................
152
IV
155
309
157
310
159
169
317
321
170
329
175
330
178
339
183
184
342
188
353
197
359
Impression ................................................
199
360
207
365
213
370
214
373
219
374
224
376
379
383
385
386
390
229
236
241
256
263
264
273
276
396
278
402
287
288
408
294
411
300
418
302
303
423
407
425
426
435
439
443
444
448
461
469
470
477
478
478
479
479
Index .............................................................
529
Jasmin Blanchette
Avant-propos
ces numros dans les champs appropris avec la date, lendroit, une description et le montant.
La numrotation et la copie sont conues pour faciliter la tche, mais ce sont des oprations
redondantes, sachant que la date, lendroit, la description et le montant identifient sans aucun doute
un reu. On pourrait penser quil sagit dun bien petit travail pour obtenir un remboursement.
Le tarif journalier qui dpend du lieu de sjour est quelque peu embarrassant. Il existe des
documents quelque part qui rpertorient les tarifs normaliss pour les divers lieux de sjour.
Vous ne pouvez pas simplement choisir "Chicago" ; vous devez rechercher le tarif correspondant Chicago vous-mme. Il en va de mme pour le champ correspondant au taux de change.
La personne doit trouver le taux de change en vigueur peut-tre laide de Google puis
saisir le taux dans chaque champ. En clair, vous devez attendre que votre banque vous fasse
parvenir une lettre dans laquelle ils vous informent du taux de change appliqu. Mme si
lopration ne savre pas trs complique, il nest pas commode de rechercher toutes ces
informations dans diffrentes sources, puis de les recopier plusieurs endroits dans le formulaire.
La programmation peut normment ressembler ces notes de frais, mais en pire. Et cest l
que Qt vient votre rescousse. Qt est diffrent. Dun ct, Qt est logique. Dun autre, Qt est
amusant. Qt vous permet de vous concentrer sur vos tches. Quand les architectes de Qt
rencontraient un problme, ils ne cherchaient pas uniquement une solution convenable ou la
solution la plus simple. Ils recherchaient la bonne solution, puis lexpliquaient. Cest vrai
quils faisaient des erreurs et que certaines de leurs dcisions de conception ne rsistaient pas
au temps, mais ils proposaient quand mme beaucoup de bonnes choses et les mauvais cts
pouvaient toujours tre amliors. Imaginez quun systme conu lorigine pour mettre en
relation Windows 95 et Unix/Motif unifie dsormais des systmes de bureau modernes aussi
divers que Windows XP, Mac OS X et GNU/Linux, et pose les fondements de la plate-forme
applicative Qtopia pour Linux Embarqu.
Bien avant que Qt ne devienne ce produit si populaire et si largement utilis, la dvotion avec
laquelle les dveloppeurs de ce framework recherchaient les bonnes solutions rendait Qt vraiment
particulier. Ce dvouement est toujours aussi fort aujourdhui et affecte quiconque dveloppe
et assure la maintenance de Qt. Pour nous, travailler avec Qt constitue une grande responsabilit et un privilge. Nous sommes fiers de contribuer rendre vos existences professionnelles
et open source plus simples et plus agrables.
Matthias Ettrich
Oslo, Norvge
Juin 2006
Prface
Qt est un framework C++ permettant de dvelopper des applications GUI multiplatesformes en se basant sur lapproche suivante : "Ecrire une fois, compiler nimporte o."
Qt permet aux programmeurs demployer une seule arborescence source pour des applications qui sexcuteront sous Windows 98 XP, Mac OS X, Linux, Solaris, HP-UX, et
de nombreuses autres versions dUnix avec X11. Les bibliothques et outils Qt font
galement partie de Qtopia Core, un produit qui propose son propre systme de fentrage au-dessus de Linux Embarqu.
Le but de ce livre est de vous apprendre crire des programmes GUI avec Qt 4. Le
livre commence par "Hello Qt" et progresse rapidement vers des sujets plus avancs,
tels que la cration de widgets personnaliss et le glisser-dposer. Le code source des
exemples de programmes est tlchargeable sur le site Pearson, www.pearson.fr, la
page ddie cet ouvrage. LAnnexe A vous explique comment installer le logiciel.
Ce manuel se divise en trois parties. La Partie I traite de toutes les notions et pratiques
ncessaires pour programmer des applications GUI avec Qt. Si vous ntudiez que cette
partie, vous serez dj en mesure dcrire des applications GUI utiles. La Partie II aborde
des thmes importants de Qt trs en dtail et la Partie III propose des sujets plus spcialiss et plus avancs. Vous pouvez lire les chapitres des Parties II et III dans nimporte
quel ordre, mais cela suppose que vous connaissez dj le contenu de la Partie I.
Les lecteurs de ldition Qt 3 de ce livre trouveront cette nouvelle dition familire tant
au niveau du contenu que du style. Cette dition a t mise jour pour profiter des
nouvelles fonctionnalits de Qt 4 (y compris certaines qui sont apparues avec Qt 4.1) et
pour prsenter du code qui illustre les techniques de programmation Qt 4. Dans la
plupart des cas, nous avons utilis des exemples similaires ceux de ldition Qt 3. Les
nouveaux lecteurs nen seront pas affects, mais ceux qui ont lu la version prcdente
pourront mieux se reprer dans le style plus clair, plus propre et plus expressif de Qt 4.
XII
Remerciements
XIV
Nous tenons aussi remercier tout particulirement les quipes de Trolltech en charge de la
documentation et du support pour avoir gr tous les problmes lis la documentation lorsque
le livre nous prenait la majorit de notre temps, de mme que les administrateurs systme de
Trolltech pour avoir laisser nos machines en excution et nos rseaux en communication tout
au long du projet.
Du ct de la production, Trenton Schulz a cr le CD compagnon de l'dition imprime de
cet ouvrage et Cathrine Bore de Trolltech a gr les contrats et les lgalits pour notre compte.
Un grand merci galement Nathan Clement pour les illustrations. Et enfin, nous remercions
Lara Wysong de Pearson pour avoir aussi bien gr les dtails pratiques de la production.
Bref historique de Qt
XVI
La lettre "Q" a t choisie comme prfixe de classe parce que ce caractre tait joli dans lcriture Emacs de Haavard. Le "t" a t ajout pour reprsenter "toolkit" inspir par Xt, the X
Toolkit. La socit a t cre le 4 mars 1994, tout dabord sous le nom Quasar Technologies,
puis Troll Tech et enfin Trolltech.
En avril 1995, grce lun des professeurs de Haavard, lentreprise norvgienne Metis a sign
un contrat avec eux pour dvelopper un logiciel bas sur Qt. A cette priode, Trolltech a
embauch Arnt Gulbrandsen, qui, pendant six ans, a imagin et implment un systme de
documentation ingnieux et a contribu llaboration du code de Qt.
Le 20 mai 1995, Qt 0.90 a t tlcharg sur sunsite.unc.edu. Six jours plus tard, la publication a t annonce sur comp.os.linux.announce. Ce fut la premire version publique de Qt.
Qt pouvait tre utilis pour un dveloppement sous Windows et Unix, proposant la mme API
sur les deux plates-formes. Qt tait disponible sous deux licences ds le dbut : une licence
commerciale tait ncessaire pour un dveloppement commercial et une dition gratuite tait
disponible pour un dveloppement open source. Le contrat avec Metis a permis Trolltech de
rester flot, alors que personne na achet de licence commerciale Qt pendant dix longs mois.
En mars 1996, lAgence spatiale europenne est devenue le deuxime client Qt, en achetant
dix licences commerciales. Eirik et Haavard y croyaient toujours aussi fortement et ont embauch un autre dveloppeur. Qt 0.97 a t publi fin mai et le 24 septembre 1996, Qt 1.0 a fait son
apparition. A la fin de la mme anne, Qt atteignait la version 1.1 : huit clients, chacun venant
dun pays diffrent, ont achet 18 licences au total. Cette anne a galement vu la naissance du
projet KDE, men par Matthias Ettrich.
Qt 1.2 a t publi en avril 1997. Matthias Ettrich a dcid dutiliser Qt pour concevoir un
environnement KDE, ce qui a favoris llvation de Qt au rang de standard de facto pour le
dveloppement GUI C++ sous Linux. Qt 1.3 a t publi en septembre 1997.
Matthias a rejoint Trolltech en 1998 et la dernire version principale de Qt 1, 1.40, a t conue
en septembre de la mme anne. Qt 2.0 a t publi en juin 1999. Qt 2 avait une nouvelle
licence open source, QPL (Q Public License), conforme la dfinition OSD (Open Source
De?nition). En aot 1999, Qt a reu le prix LinuxWorld en tant que meilleure bibliothque/
outil. A cette priode-l, Trolltech Pty Ltd (Australie) a t fonde.
Trolltech a publi Qtopia Core (appel ensuite Qt/Embedded) en 2000. Il tait conu pour
sexcuter sur les priphriques Linux Embarqu et proposait son propre systme de fentrage
en remplacement de X11. Qt/X11 et Qtopia Core taient dsormais proposs sous la licence
GNU GPL (General Public License) fortement utilise, de mme que sous des licences
commerciales. A la fin de lanne 2000, Trolltech a fond Trolltech Inc. (USA) et a publi la
premire version de Qtopia, une plate-forme applicative pour tlphones mobiles et PDA.
Qtopia Core a remport le prix LinuxWorld "Best Embedded Linux Solution" en 2001 et 2002,
et Qtopia Phone a obtenu la mme rcompense en 2004.
Qt 3.0 a t publi en 2001. Qt est dsormais disponible sous Windows, Mac OS X, Unix et
Linux (Desktop et Embarqu). Qt 3 proposait 42 nouvelles classes et son code slevait
500 000 lignes. Qt 3 a constitu une avance incroyable par rapport Qt 2 : un support local et
Bref historique de Qt
XVII
I
Qt : notions de base
1
Pour dbuter
1
Pour dbuter
Au sommaire de ce chapitre
Utiliser Hello Qt
Etablir des connexions
Disposer des widgets
Utiliser la documentation
de rfrence
Hello Qt
Commenons par un programme Qt trs simple. Nous ltudierons dabord ligne par ligne,
puis nous verrons comment le compiler et lexcuter.
1
2
#include <QApplication>
#include <QLabel>
3
4
5
6
7
8
9
Les lignes 1 et 2 contiennent les dfinitions des classes QApplication et QLabel. Pour
chaque classe Qt, il y a un fichier den-tte qui porte le mme nom (en respectant la casse) que
la classe qui renferme la dfinition de classe.
La ligne 5 cre un objet QApplication pour grer les ressources au niveau de lapplication.
Le constructeur QApplication exige argc et argv parce que Qt prend personnellement en
charge quelques arguments de ligne de commande.
La ligne 6 cre un widget QLabel qui affiche "Hello Qt !". Dans la terminologie Qt et Unix, un
widget est un lment visuel dans une interface utilisateur. Ce terme provient de "gadget pour
fentres" et correspond "contrle" et "conteneur" dans la terminologie Windows. Les boutons,
les menus et les barres de dfilement sont des exemples de widgets. Les widgets peuvent
contenir dautres widgets ; par exemple, une fentre dapplication est habituellement un widget
qui comporte un QMenuBar, quelques QToolBar, un QStatusBar et dautres widgets. La
plupart des applications utilisent un QMainWindow ou un QDialog comme fentre dapplication,
mais Qt est si flexible que nimporte quel widget peut tre une fentre. Dans cet exemple, le
widget QLabel correspond la fentre dapplication.
La ligne 7 permet dafficher ltiquette. Lors de leur cration, les widgets sont toujours masqus,
de manire pouvoir les personnaliser avant de les afficher et donc dviter le phnomne du
scintillement.
La ligne 8 transmet le contrle de lapplication Qt. A ce stade, le programme entre dans la
boucle dvnement. Cest une sorte de mode dattente o le programme attend que lutilisateur
agisse, en cliquant sur la souris ou en appuyant sur une touche par exemple. Les actions de
lutilisateur dclenchent des vnements (aussi appels "messages") auquel le programme peut
rpondre, gnralement en excutant une ou plusieurs fonctions. Par exemple, quand lutilisateur clique sur un widget, les vnements "bouton souris enfonc" et "bouton souris relch" sont
dclenchs. A cet gard, les applications GUI diffrent normment des programmes par lots
traditionnels, qui traitent habituellement lentre, produisent des rsultats et sachvent sans
intervention de quiconque.
Chapitre 1
Pour dbuter
Pour des questions de simplicit, nous nappelons pas delete sur lobjet QLabel la fin de la
fonction main(). Cette petite fuite de mmoire est insignifiante dans un programme de cette
taille, puisque la mmoire est rcupre de toute faon par le systme dexploitation ds quil
se termine.
Figure 1.1
Hello sur Linux
Vous pouvez dsormais tester le programme sur votre ordinateur. Vous devrez dabord installer
Qt 4.1.1 (ou une version ultrieure de Qt 4), un processus expliqu en Annexe A. A partir de
maintenant, nous supposons que vous avez correctement install une copie de Qt 4 et que le
rpertoire bin de Qt se trouve dans votre variable denvironnement PATH. (Sous Windows,
cest effectu automatiquement par le programme dinstallation de Qt.) Vous aurez galement
besoin du code source du programme dans un fichier appel hello.cpp situ dans un rpertoire nomm hello. Vous pouvez saisir le code de ce programme vous-mme ou le copier
depuis le CD fourni avec ce livre, partir du fichier /examples/chap01/hello/hello.cpp.
Depuis une invite de commande, placez-vous dans le rpertoire hello, puis saisissez
qmake -project
1. Si vous obtenez une erreur du compilateur sur <QApplication>, cela signifie certainement que vous
utilisez une version plus ancienne de Qt. Assurez-vous dutiliser Qt 4.1.1 ou une version ultrieure de Qt 4.
Si vous vous servez de Microsoft Visual C++, vous devrez excuter nmake au lieu de make.
Vous pouvez aussi crer un fichier de projet Visual Studio depuis hello.pro en tapant
qmake -tp vc hello.pro
puis concevoir le programme dans Visual Studio. Si vous travaillez avec Xcode sous Mac OS
X, vous avez la possibilit de gnrer un projet Xcode laide de la commande
qmake -spec macx-xcode
Figure 1.2
Une tiquette avec une
mise en forme HTML
basique
par
QLabel *label = new QLabel("<h2><i>Hello</i> "
"<font color=red>Qt!</font></h2>");
et gnrez nouveau lapplication. Comme lillustre cet exemple, il est facile dgayer linterface utilisateur dune application Qt en utilisant une mise en forme simple de style HTML
(voir Figure 1.2).
#include <QApplication>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton *button = new QPushButton("Quit");
QObject::connect(button, SIGNAL(clicked()),
&app, SLOT(quit()));
Chapitre 1
9
10
11
Pour dbuter
button->show();
return app.exec();
}
Les widgets de Qt mettent des signaux pour indiquer quune action utilisateur ou un changement dtat a eu lieu.1 Par exemple, QPushButton met un signal clicked() quand lutilisateur clique sur le bouton. Un signal peut tre connect une fonction (appel un slot dans ce
cas), de sorte quau moment o le signal est mis, le slot soit excut automatiquement. Dans
notre exemple, nous relions le signal clicked() du bouton au slot quit() de lobjet QApplication. Les macros SIGNAL() et SLOT() font partie de la syntaxe ; elles sont expliques plus
en dtail dans le prochain chapitre.
Figure 1.3
Lapplication Quit
Nous allons dsormais gnrer lapplication. Nous supposons que vous avez cr un rpertoire
appel quit contenant quit.cpp (voir Figure 1.3). Excutez qmake dans le rpertoire quit
pour gnrer le fichier de projet, puis excutez-le nouveau pour gnrer un fichier Makefile,
comme suit :
qmake -project
qmake quit.pro
A prsent, gnrez lapplication et excutez-la. Si vous cliquez sur Quit ou que vous appuyez
sur la barre despace (ce qui enfonce le bouton), lapplication se termine.
ce sont des enfants de QWidget. Nous pouvons aussi dire que QWidget est le parent de
QSpinBox et QSlider. QWidget na pas de parent puisque cest une fentre de niveau le plus
haut. Les constructeurs de QWidget et toutes ses sous-classes reoivent un paramtre QWidget
* qui spcifie le widget parent.
Voici le code source :
1
2
3
4
#include
#include
#include
#include
<QApplication>
<QHBoxLayout>
<QSlider>
<QSpinBox>
5
6
7
8
9
10
11
12
13
14
15
16
17
18
QObject::connect(spinBox, SIGNAL(valueChanged(int)),
slider, SLOT(setValue(int)));
QObject::connect(slider, SIGNAL(valueChanged(int)),
spinBox, SLOT(setValue(int)));
spinBox->setValue(35);
19
20
21
22
23
24
25
return app.exec();
}
Les lignes 8 et 9 configurent QWidget qui fera office de fentre principale de lapplication.
Nous appelons setWindowTitle() pour dfinir le texte affich dans la barre de titre de la
fentre.
Les lignes 10 et 11 crent QSpinBox et QSlider, et les lignes 12 et 13 dterminent leurs
plages valides. Nous pouvons affirmer que lutilisateur est g au maximum de 130 ans. Nous
pourrions transmettre window aux constructeurs de QSpinBox et QSlider, en spcifiant que le
parent de ces widgets doit tre window, mais ce nest pas ncessaire ici, parce que le systme
de positionnement le dduira lui-mme et dfinira automatiquement le parent du pointeur
toupie (spin box) et du curseur, comme nous allons le voir.
Chapitre 1
Pour dbuter
Les deux appels QObject::connect() prsents aux lignes 14 17 garantissent que le pointeur toupie et le curseur sont synchroniss, de sorte quils affichent toujours la mme valeur.
Ds que la valeur dun widget change, son signal valueChanged(int) est mis et le slot
setValue(int) de lautre widget est appel avec la nouvelle valeur.
La ligne 18 dfinit la valeur du pointeur toupie en 35. QSpinBox met donc le signal valueChanged(int) avec un argument int de 35. Cet argument est transmis au slot setValue(int) de QSlider, qui dfinit la valeur du curseur en 35. Le curseur met ensuite le signal
valueChanged(int) puisque sa propre valeur a chang, dclenchant le slot setValue(int)
du pointeur toupie. Cependant, ce stade, setValue(int) nmet aucun signal, parce que la
valeur du pointeur toupie est dj de 35. Vous vitez ainsi une rcursivit infinie. La Figure 1.5
rcapitule la situation.
Figure 1.5
Changer la valeur dun
widget entrane la modification des deux widgets
1.
setValue(35)
2.
35
valueChanged(35)
setValue(35)
3.
35
valueChanged(35)
setValue(35)
4.
35
Dans les lignes 19 22, nous positionnons le pointeur toupie et le curseur laide dun
gestionnaire de disposition (layout manager). Ce gestionnaire est un objet qui dfinit la taille
et la position des widgets qui se trouvent sous sa responsabilit. Qt propose trois principaux
gestionnaires de disposition :
QHBoxLayout dispose les widgets horizontalement de gauche droite (de droite gauche
dans certaines cultures).
QVBoxLayout dispose les widgets verticalement de haut en bas.
QGridLayout dispose les widgets dans une grille.
Lorsque vous appelez QWidget::setLayout() la ligne 22, le gestionnaire de disposition
est install dans la fentre. En arrire-plan, QSpinBox et QSlider sont "reparents" pour devenir des enfants du widget sur lequel la disposition sapplique, et cest pour cette raison que
10
nous navons pas besoin de spcifier un parent explicite quand nous construisons un widget qui
sera insr dans une disposition.
Figure 1.6
Les widgets
de lapplication Age
Window Title
QWidget
QSpinBox
QSlider
QHBoxLayout
Mme si nous navons pas dfini explicitement la position ou la taille dun widget, QSpinBox
et QSlider apparaissent convenablement cte cte. Cest parce que QHBoxLayout assigne
automatiquement des positions et des tailles raisonnables aux widgets dont il est responsable
en fonction de leurs besoins. Grce aux gestionnaires de disposition, vous vitez la corve de
coder les positions lcran dans vos applications et vous tes sr que les fentres seront redimensionnes correctement.
Lapproche de Qt pour ce qui concerne la conception des interfaces utilisateurs est facile
comprendre et savre trs flexible. Habituellement, les programmeurs Qt instancient les
widgets ncessaires puis dfinissent leurs proprits de manire adquate. Les programmeurs
ajoutent les widgets aux dispositions, qui se chargent automatiquement de leur taille et de leur
position. Le comportement de linterface utilisateur est gr en connectant les widgets ensembles
grce aux signaux et aux slots de Qt.
Chapitre 1
Pour dbuter
11
En guise dentranement, vous rechercherez les classes et les fonctions dont nous avons parles
dans ce chapitre.
Figure 1.7
La documentation de Qt dans lAssistant sous Mac OS X
Notez que les fonctions hrites sont dtailles dans la classe de base ; par exemple, QPushButton ne possde pas de fonction show(), mais il en hrite une de son anctre QWidget.
La Figure 1.8 vous montre comment les classes que nous avons tudies jusque l sont lies les
unes aux autres.
Figure 1.8
Arbre dhritage des
classes Qt tudies QCoreApplication
jusqu prsent
QObject
QWidget
QLayout
QApplication
QBoxLayout
QAbstractButton
QAbstractButton
QAbstractSlider
QFrame
QPushButton
QSpinBox
QSlider
QLabel
QHBoxLayout
12
La documentation de rfrence pour la version actuelle de Qt et pour certaines versions antrieures est disponible en ligne ladresse suivante, http://doc.trolltech.com/. Ce site propose
galement des articles slectionns dans Qt Quarterly, la lettre dinformation des programmeurs
Qt envoye tous les dtenteurs de licences.
Figure 1.9
Styles disponibles
partout
Windows
Plastique
CDE
Motif
Dans Qt/X11 et Qtopia Core, le style par dfaut est Plastique. Il utilise des dgrads et lanticrnelage pour proposer un aspect et une apparence modernes. Les utilisateurs de lapplication
Qt peuvent passer outre ce style par dfaut grce loption de ligne de commande -style.
Par exemple, pour lancer lapplication Age avec le style Motif sur X11, tapez simplement
./age -style motif
Figure 1.10
Styles spcifiques la
plate-forme
Windows XP
Mac
Contrairement aux autres styles, les styles Windows XP et Mac ne sont disponibles que sur leurs
plate-formes natives, puisquils se basent sur les gnrateurs de thme de ces plate-formes.
Chapitre 1
Pour dbuter
13
Ce chapitre vous a prsent les concepts essentiels des connexions signal slot et des dispositions. Il a galement commenc dvoiler lapproche totalement oriente objet et cohrente de
Qt concernant la construction et lutilisation des widgets. Si vous parcourez la documentation
de Qt, vous dcouvrirez que lapproche savre homogne. Vous comprendrez donc beaucoup
plus facilement comment utiliser de nouveaux widgets et vous verrez aussi que les noms choisis
minutieusement pour les fonctions, les paramtres, les numrations, etc. rendent la programmation dans Qt tonnamment plaisante et aise.
Les chapitres suivants de la Partie I se basent sur les notions fondamentales tudies ici et vous
expliquent comment crer des applications GUI compltes avec des menus, des barres doutils,
des fentres de document, une barre dtat et des botes de dialogue, en plus des fonctionnalits
sous-jacentes permettant de lire, traiter et crire des fichiers.
2
Crer des botes de dialogue
Au sommaire de ce chapitre
Drivation de QDialog
Description dtaille des signaux et slots
Conception rapide dune bote de dialogue
Botes de dialogue multiformes
Botes de dialogue dynamiques
Classes de widgets et de botes de dialogue
intgres
Dans ce chapitre, vous allez apprendre crer des botes de dialogue laide de Qt.
Celles-ci prsentent diverses options et possibilits aux utilisateurs et leur permettent de
dfinir les valeurs des options et de faire des choix. On les appelle des botes de dialogue,
puisquelles donnent la possibilit aux utilisateurs et aux applications de "discuter".
La majorit des applications GUI consiste en une fentre principale quipe dune barre
de menus et doutils, laquelle on ajoute des douzaines de botes de dialogue. Il est
galement possible de crer des applications "bote de dialogue" qui rpondent directement aux choix de lutilisateur en accomplissant les actions appropries (par exemple,
une application de calculatrice).
16
Nous allons crer notre premire bote de dialogue en crivant compltement le code pour vous en
expliquer le fonctionnement. Puis nous verrons comment concevoir des botes de dialogue grce
au Qt Designer, un outil de conception de Qt. Avec le Qt Designer, vous codez beaucoup plus
rapidement et il est plus facile de tester les diffrentes conceptions et de les modifier par la suite.
Drivation de QDialog
Notre premier exemple est une bote de dialogue Find crite totalement en langage C++ (voir
Figure 2.1). Nous limplmenterons comme une classe part entire. Ainsi, elle deviendra un
composant indpendant et autonome, comportant ses propres signaux et slots.
Figure 2.1
La bote de dialogue Find
Le code source est rparti entre deux fichiers : finddialog.h et finddialog.cpp. Nous
commencerons par finddialog.h.
1
2
#ifndef FINDDIALOG_H
#define FINDDIALOG_H
#include <QDialog>
4
5
6
7
class
class
class
class
QCheckBox;
QLabel;
QLineEdit;
QPushButton;
Les lignes 1 et 2 (et 27) protgent le fichier den-tte contre les inclusions multiples.
La ligne 3 contient la dfinition de QDialog, la classe de base pour les botes de dialogue dans
Qt. QDialog hrite de QWidget.
Les lignes 4 7 sont des dclarations pralables des classes Qt que nous utiliserons pour
implmenter la bote de dialogue. Une dclaration pralable informe le compilateur C++
quune classe existe, sans donner tous les dtails dune dfinition de classe (gnralement
situe dans un fichier den-tte). Nous en parlerons davantage dans un instant.
Nous dfinissons ensuite FindDialog comme une sous-classe de QDialog:
8
9
10
Chapitre 2
11
12
17
public:
FindDialog(QWidget *parent = 0);
La macro Q_OBJECT au dbut de la dfinition de classe est ncessaire pour toutes les classes qui
dfinissent des signaux ou des slots.
Le constructeur de FindDialog est typique des classes Qt de widgets. Le paramtre parent
spcifie le widget parent. Par dfaut, cest un pointeur nul, ce qui signifie que la bote de dialogue
na pas de parent.
13
14
15
signals:
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);
La section des signaux dclare deux signaux que la bote de dialogue met quand lutilisateur
clique sur le bouton Find. Si loption Search backward est active, la bote de dialogue met
findPrevious(); sinon elle met findNext().
Le mot-cl signals est en fait une macro. Le prprocesseur C++ la convertit en langage C++
standard avant que le compilateur ne la voie. Qt::CaseSensitivity est un type numration
qui peut prendre les valeurs Qt::CaseSensitive et Qt::CaseInsensitive.
16
17
18
private slots:
void findClicked();
void enableFindButton(const QString &text);
26
private:
QLabel *label;
QLineEdit *lineEdit;
QCheckBox *caseCheckBox;
QCheckBox *backwardCheckBox;
QPushButton *findButton;
QPushButton *closeButton;
};
27
#endif
19
20
21
22
23
24
25
Dans la section prive de la classe, nous dclarons deux slots. Pour implmenter les slots, vous
devez avoir accs la plupart des widgets enfants de la bote de dialogue, nous conservons
donc galement des pointeurs vers eux. Le mot-cl slots, comme signals, est une macro qui
se dveloppe pour produire du code que le compilateur C++ peut digrer.
Sagissant des variables prives, nous avons utilis les dclarations pralables de leurs classes.
Ctait possible puisque ce sont toutes des pointeurs et que nous ny accdons pas dans le
fichier den-tte, le compilateur na donc pas besoin des dfinitions de classe compltes. Nous
aurions pu inclure les fichiers den-tte importants (<QCheckBox>, <QLabel>, etc.), mais la
compilation se rvle plus rapide si vous utilisez les dclarations pralables ds que possible.
18
Nous allons dsormais nous pencher sur finddialog.cpp qui contient limplmentation de la
classe FindDialog.
1
#include <QtGui>
#include "finddialog.h"
Nous incluons dabord <QtGui>, un fichier den-tte qui contient la dfinition des classes GUI
de Qt. Qt est constitu de plusieurs modules, chacun deux se trouvant dans sa propre bibliothque. Les modules les plus importants sont QtCore, QtGui, QtNetwork, QtOpenGL, QtSql,
QtSvg et QtXml. Le fichier den-tte <QtGui> renferme la dfinition de toutes les classes qui
font partie des mo dules QtCore et QtGui. En incluant cet en-tte, vous vitez la tche fastidieuse dinclure chaque classe sparment.
Dans filedialog.h, au lieu dinclure <QDialog> et dutiliser des dclarations pralables
pour QCheckBox, QLabel, QLineEdit et QPushButton, nous aurions simplement pu spcifier
<QtGui>. Toutefois, il est gnralement malvenu dinclure un fichier den-tte si grand depuis
un autre fichier den-tte, notamment dans des applications plus importantes.
3
4
5
6
7
8
FindDialog::FindDialog(QWidget *parent)
: QDialog(parent)
{
label = new QLabel(tr("Find &what:"));
lineEdit = new QLineEdit;
label->setBuddy(lineEdit);
9
10
11
12
13
14
Chapitre 2
19
qui reoit le focus quand vous appuyez sur le raccourci clavier de ltiquette. Donc, quand
lutilisateur appuie sur Alt+W (le raccourci de ltiquette), lditeur de lignes reoit le contrle.
A la ligne 12, nous faisons du bouton Find le bouton par dfaut de la bote de dialogue en
appelant setDefault(true). Le bouton par dfaut est le bouton qui est press quand lutilisateur appuie sur Entre. A la ligne 13, nous dsactivons le bouton Find. Quand un widget est
dsactiv, il apparat gnralement gris et ne rpondra pas en cas dinteraction de lutilisateur.
15
16
17
18
19
20
Le slot priv enableFindButton(const QString &) est appel ds que le texte change
dans lditeur de lignes. Le slot priv findClicked() est invoqu lorsque lutilisateur clique
sur le bouton Find. La bote de dialogue se ferme si lutilisateur clique sur Close. Le slot
close() est hrit de QWidget et son comportement par dfaut consiste masquer le widget
(sans le supprimer). Nous allons tudier le code des slots enableFindButton() et findClicked() ultrieurement.
Etant donn que QObject est lun des anctres de FindDialog, nous pouvons omettre le
prfixe QObject:: avant les appels de connect().
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Nous disposons ensuite les widgets enfants laide des gestionnaires de disposition. Les dispositions peuvent contenir des widgets et dautres dispositions. En imbriquant QHBoxLayout,
QVBoxLayout et QGridLayout dans diverses combinaisons, il est possible de concevoir des
botes de dialogue trs sophistiques.
20
Figure 2.2
Les dispositions de la
bote de dialogue Find
Titre de la fentre
leftLayout
topLeftLayout
QLabel
QLineEdit
QPushButton
QPushButton
QCheckBox
QCheckBox
rightLayout
rightLayout
Elment
d'espacement
Pour la bote de dialogue Find, nous employons deux QHBoxLayout et deux QVBoxLayout,
comme illustr en Figure 2.2. La disposition externe correspond la disposition principale ;
elle est installe sur FindDialog la ligne 35 et est responsable de toute la zone de la bote de
dialogue. Les trois autres dispositions sont des sous-dispositions. Le petit "ressort" en bas
droite de la Figure 2.2 est un lment despacement (ou "tirement"). Il comble lespace vide sous
les boutons Find et Close, ces boutons sont donc srs de se trouver en haut de leur disposition.
Les gestionnaires de disposition prsentent une subtilit : ce ne sont pas des widgets. Ils hritent de QLayout, qui hrite son tour de QObject. Dans la figure, les widgets sont reprsents
par des cadres aux traits pleins et les dispositions sont illustres par des cadres en pointills
pour mettre en avant la diffrence qui existe entre eux. Dans une application en excution, les
dispositions sont invisibles.
Quand les sous-dispositions sont ajoutes la disposition parent (lignes 25, 33 et 34), les sousdispositions sont automatiquement reparentes. Puis, lorsque la disposition principale est
installe dans la bote de dialogue (ligne 35), elle devient un enfant de cette dernire et tous les
widgets dans les dispositions sont reparents pour devenir des enfants de la bote de dialogue.
La hirarchie parent-enfant ainsi obtenue est prsente en Figure 2.3.
36
37
38
setWindowTitle(tr("Find"));
setFixedHeight(sizeHint().height());
}
Figure 2.3
Les relations parentenfant de la bote de
dialogue Find
FindDialog
QLabel (label)
QLineEdit (lineEdit)
QCheckBox (caseCheckBox)
QCheckBox (backwardCheckBox)
QPushButton (ndButton)
QPushButton (closeButton)
QHBoxLayout (mainLayout)
QVBoxLayout (leftLayout)
QHBoxLayout (topLeftLayout)
QVBoxLayout (rightLayout)
Enfin, nous dfinissons le titre afficher dans la barre de titre de la bote de dialogue et nous
configurons la fentre pour quelle prsente une hauteur fixe, tant donn quelle ne contient
Chapitre 2
21
aucun widget qui peut occuper de lespace supplmentaire verticalement. La fonction QWidget::sizeHint() retourne la taille "idale" dun widget.
Ceci termine lanalyse du constructeur de FindDialog. Vu que nous avons cr les widgets et
les dispositions de la bote de dialogue avec new, il semblerait logique dcrire un destructeur
qui appelle delete sur chaque widget et disposition que nous avons crs. Toutefois, ce nest
pas ncessaire, puisque Qt supprime automatiquement les objets enfants quand le parent est
dtruit, et les dispositions et widgets enfants sont tous des descendants de FindDialog.
Nous allons prsent analyser les slots de la bote de dialogue :
39
40
41
42
43
44
45
46
47
48
49
50
void FindDialog::findClicked()
{
QString text = lineEdit->text();
Qt::CaseSensitivity cs =
caseCheckBox->isChecked()? Qt::CaseSensitive
: Qt::CaseInsensitive;
if (backwardCheckBox->isChecked()) {
emit findPrevious(text, cs);
} else {
emit findNext(text, cs);
}
}
51
52
53
54
Le slot findClicked() est appel lorsque lutilisateur clique sur le bouton Find. Il met le
signal findPrevious() ou findNext(), en fonction de loption Search backward. Le motcl emit est spcifique Qt ; comme les autres extensions Qt, il est converti en langage C++
standard par le prprocesseur C++.
Le slot enableFindButton() est invoqu ds que lutilisateur modifie le texte dans lditeur
de lignes. Il active le bouton sil y a du texte dans cet diteur, sinon il le dsactive.
Ces deux slots compltent la bote de dialogue. Nous avons dsormais la possibilit de crer un
fichier main.cpp pour tester notre widget FindDialog:
1
#include <QApplication>
#include "finddialog.h"
3
4
5
6
7
8
9
22
contiendra des rgles particulires pour excuter moc, le compilateur de mta-objets de Qt.
(Le systme de mta-objets de Qt est abord dans la prochaine section.)
Pour que moc fonctionne correctement, vous devez placer la dfinition de classe dans un fichier
den-tte, spar du fichier dimplmentation. Le code gnr par moc inclut ce fichier dentte et ajoute une certaine "magie" C++.
moc doit tre excut sur les classes qui se servent de la macro Q_OBJECT. Ce nest pas un
problme parce que qmake ajoute automatiquement les rgles ncessaires dans le fichier Makefile. Toutefois, si vous oubliez de gnrer nouveau votre fichier Makefile laide de qmake et
que moc nest pas excut, lditeur de liens indiquera que certaines fonctions sont dclares
mais pas implmentes. Les messages peuvent tre plutt obscurs. GCC produit des avertissements
comme celui-ci :
finddialog.o: In function FindDialog::tr(char const*, char const*):
/usr/lib/qt/src/corelib/global/qglobal.h:1430: undefined reference to
FindDialog::staticMetaObject
Si vous vous trouvez dans ce cas, excutez nouveau qmake pour mettre jour le fichier
Makefile, puis gnrez nouveau lapplication.
Excutez maintenant le programme. Si des raccourcis clavier apparaissent sur votre plateforme, vrifiez que les raccourcis Alt+W, Alt+C, Alt+B et Alt+F dclenchent le bon comportement.
Appuyez sur la touche de tabulation pour parcourir les widgets en utilisant le clavier. Lordre
de tabulation par dfaut est lordre dans lequel les widgets ont t crs. Vous pouvez le modifier
grce QWidget::setTabOrder().
Proposer un ordre de tabulation et des raccourcis clavier cohrents permet aux utilisateurs qui
ne veulent pas (ou ne peuvent pas) utiliser une souris de profiter pleinement de lapplication.
Les dactylos apprcieront galement de pouvoir tout contrler depuis le clavier.
Dans le Chapitre 3, nous utiliserons la bote de dialogue Find dans une application relle, et
nous connecterons les signaux findPrevious() et findNext() certains slots.
Chapitre 2
23
Les slots sont presque identiques aux fonctions membres ordinaires de C++. Ils peuvent tre
virtuels, surchargs, publics, protgs ou privs, tre invoqus directement comme toute autre
fonction membre C++, et leurs paramtres peuvent tre de nimporte quel type. La diffrence
est quun slot peut aussi tre connect un signal, auquel cas il est automatiquement appel
chaque fois que le signal est mis.
Voici la syntaxe de linstruction connect():
connect(sender, SIGNAL(signal), receiver, SLOT(slot));
o sender et receiver sont des pointeurs vers QObject et o signal et slot sont des signatures de fonction sans les noms de paramtre. Les macros SIGNAL() et SLOT() convertissent
leur argument en chane.
Dans les exemples tudis jusque l, nous avons toujours connect les signaux aux divers slots.
Il existe dautres possibilits envisager.
Quand le signal est mis, les slots sont appels les uns aprs les autres, dans un ordre non
spcifi.
Quand le premier signal est mis, le second est galement mis. En dehors de cette caractristique, les connexions signal-signal sont en tout point identiques aux connexions
signal-slot.
Vous nen aurez que trs rarement besoin, parce que Qt supprime automatiquement toutes
les connexions concernant un objet quand celui-ci est supprim.
24
Pour bien connecter un signal un slot (ou un autre signal), ils doivent avoir les mmes types
de paramtre dans le mme ordre :
connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),
this, SLOT(processReply(int, const QString &)));
Si les types de paramtre sont incompatibles, ou si le signal ou le slot nexiste pas, Qt mettra
un avertissement au moment de lexcution si lapplication est gnre en mode dbogage. De
mme, Qt enverra un avertissement si les noms de paramtre se trouvent dans les signatures du
signal ou du slot.
Jusqu prsent, nous navons utilis que des signaux et des slots avec des widgets. Cependant, le
mcanisme en soi est implment dans QObject et ne se limite pas la programmation dinterfaces
graphiques utilisateurs. Il peut tre employ par nimporte quelle sous-classe de QObject:
class Employee: public QObject
{
Q_OBJECT
public:
Employee() { mySalary = 0; }
int salary() const { return mySalary; }
public slots:
void setSalary(int newSalary);
signals:
void salaryChanged(int newSalary);
private:
int mySalary;
};
void Employee::setSalary(int newSalary)
{
if (newSalary!= mySalary) {
mySalary = newSalary;
emit salaryChanged(mySalary);
}
}
Vous remarquerez la manire dont le slot setSalary() est implment. Nous nmettons
le signal salaryChanged() que si newSalary!= mySalary. Vous tes donc sr que les
connexions cycliques ne dbouchent pas sur des boucles infinies.
Chapitre 2
25
Systme de mta-objets de Qt
Lune des amliorations majeures de Qt a t lintroduction dans le langage C++ dun mcanisme permettant de crer des composants logiciels indpendants qui peuvent tre relis les
uns aux autres sans quils ne sachent absolument rien sur les autres composants auxquels ils
sont connects.
Ce mcanisme est appel systme de mta-objets et propose deux services essentiels : les
signaux-slots et lintrospection. La fonction dintrospection est ncessaire pour implmenter
des signaux et des slots et permet aux programmeurs dapplications dobtenir des "mtainformations" sur les sous-classes de QObject lexcution, y compris la liste des signaux et des
slots pris en charge par lobjet et le nom de sa classe. Le mcanisme supporte galement
des proprits (pour le Qt Designer) et des traductions de texte (pour linternationalisation),
et il pose les fondements de QSA (Qt Script for Applications).
Le langage C++ standard ne propose pas de prise en charge des mta-informations dynamiques ncessaires pour le systme de mta-objets de Qt. Qt rsout ce problme en fournissant
un outil, moc, qui analyse les dfinitions de classe Q_OBJECT et rend les informations disponibles
par le biais de fonctions C++. Etant donn que moc implmente toute sa fonctionnalit en utilisant un langage C++ pur, le systme de mta-objets de Qt fonctionne avec nimporte quel
compilateur C++.
Ce mcanisme fonctionne de la manire suivante :
La macro Q_OBJECT dclare certaines fonctions dintrospection qui doivent tre implmentes
dans chaque sous-classe de QObject: metaObject(), tr(), qt_metacall() et quelques
autres.
Loutil moc de Qt gnre des implmentations pour les fonctions dclares par Q_OBJECT
et pour tous les signaux.
Les fonctions membres de QObject, telles que connect() et disconnect(), utilisent les
fonctions dintrospection pour effectuer leurs tches.
Tout est gr automatiquement par qmake, moc et QObject, vous ne vous en souciez donc que
trs rarement. Nanmoins, par curiosit, vous pouvez parcourir la documentation de la classe
QMetaObject et analyser les fichiers sources C++ gnrs par moc pour dcouvrir comment
fonctionne limplmentation.
26
les formulaires. Ils la trouvent en effet plus naturelle et plus rapide, et ils veulent tre en
mesure de tester et de modifier leurs conceptions plus rapidement et facilement quavec des
formulaires cods manuellement.
Le Qt Designer dveloppe les options disposition des programmeurs en proposant une fonctionnalit visuelle de conception. Le Qt Designer peut tre employ pour dvelopper tous les
formulaires dune application ou juste quelques-uns. Les formulaires crs laide du Qt Designer tant uniquement constitus de code C++, le Qt Designer peut tre utilis avec une chane
doutils traditionnelle et nimpose aucune exigence particulire au compilateur.
Dans cette section, nous utiliserons le Qt Designer afin de crer la bote de dialogue Go-to-Cell
prsente en Figure 2.4. Quelle que soit la mthode de conception choisie, la cration dune
bote de dialogue implique toujours les mmes tapes cls :
Figure 2.4
La bote de dialogue
Go-to-Cell
Pour lancer le Qt Designer, cliquez sur Qt by Trolltech v4.x.y > Designer dans le menu Dmarrer sous Windows, saisissez designer dans la ligne de commande sous Unix ou doublecliquez sur Designer dans le Finder de Mac OS X. Quand le Qt Designer dmarre, une liste de
modles saffiche. Cliquez sur le modle "Widget", puis sur OK. (Le modle "Dialog with
Buttons Bottom" peut tre tentant, mais pour cet exemple nous crerons les boutons OK et
Cancel manuellement pour vous expliquer le processus.) Vous devriez prsent vous trouver
dans une fentre appele "Untitled".
Par dfaut, linterface utilisateur du Qt Designer consiste en plusieurs fentres de haut niveau.
Si vous prfrez une interface de style MDI, avec une fentre de haut niveau et plusieurs sousfentres, cliquez sur Edit > User Interface Mode > Docked Window (voir Figure 2.5).
La premire tape consiste crer les widgets enfants et les placer sur le formulaire. Crez
une tiquette, un diteur de lignes, un lment despacement horizontal et deux boutons de
commande. Pour chaque lment, faites glisser son nom ou son icne depuis la bote des
widgets du Qt Designer vers son emplacement sur le formulaire. Llment despacement, qui
est invisible dans le formulaire final, est affich dans le Qt Designer sous forme dun ressort bleu.
Chapitre 2
27
Figure 2.5
Le Qt Designer en mode
daffichage fentres
ancres sous Windows
Faites glisser le bas du formulaire vers le haut pour le rtrcir. Vous devriez voir un formulaire
similaire celui de la Figure 2.6. Ne perdez pas trop de temps positionner les lments sur le
formulaire ; les gestionnaires de disposition de Qt les disposeront prcisment par la suite.
Figure 2.6
Le formulaire
avec quelques widgets
28
Figure 2.7
Le formulaire dont les
proprits sont dfinies
Cliquez prsent sur Edit > Edit Tab Order. Un nombre apparatra dans un rectangle bleu
ct de chaque widget qui peut devenir actif (voir Figure 2.9). Cliquez sur chaque widget dans
lordre dans lequel vous voulez quils reoivent le focus, puis cliquez sur Edit > Edit widgets
pour quitter le mode ddition de lordre de tabulation.
Figure 2.9
Dfinir lordre
de tabulation
du formulaire
Pour avoir un aperu de la bote de dialogue, slectionnez loption Form > Preview du menu.
Vrifiez lordre de tabulation en appuyant plusieurs fois sur la touche Tab. Fermez la bote de
dialogue en appuyant sur le bouton de fermeture dans la barre de titre.
Chapitre 2
29
Excutez maintenant qmake pour crer un fichier .pro et un Makefile (qmake -project; qmake
goto-cell.pro). Loutil qmake est suffisamment intelligent pour dtecter le fichier de linterface utilisateur gotocelldialog.ui et pour gnrer les rgles appropries du fichier Makefile. Il va donc appeler uic, le compilateur Qt de linterface utilisateur. Loutil uic convertit
gotocelldialog.ui en langage C++ et intgre les rsultats dans ui_gotocelldialog.h.
Le fichier ui_gotocelldialog.h gnr contient la dfinition de la classe Ui::GoToCellDialog qui est un quivalent C++ du fichier gotocelldialog.ui. La classe dclare des
variables membres qui stockent les widgets enfants et les dispositions du formulaire, et une
fonction setupUi() qui initialise le formulaire. Voici la syntaxe de la classe gnre :
class Ui::GoToCellDialog
{
public:
QLabel *label;
QLineEdit *lineEdit;
QSpacerItem *spacerItem;
QPushButton *okButton;
QPushButton *cancelButton;
...
Cette classe nhrite pas de nimporte quelle classe Qt. Quand vous utilisez le formulaire dans
main.cpp, vous crez un QDialog et vous le transmettez setupUi().
30
Si vous excutez le programme maintenant, la bote de dialogue fonctionnera, mais pas exactement
comme vous le souhaitiez :
Le bouton OK est toujours dsactiv.
Le bouton Cancel ne fait rien.
Lditeur de lignes accepte nimporte quel texte, au lieu daccepter uniquement des emplacements de cellule valides.
Pour faire fonctionner correctement la bote de dialogue, vous devrez crire du code. La
meilleure approche consiste crer une nouvelle classe qui hrite la fois de QDialog et de
Ui::GoToCellDialog et qui implmente la fonctionnalit manquante (ce qui prouve que tout
problme logiciel peut tre rsolu simplement en ajoutant une autre couche dindirection).
Notre convention de dnomination consiste attribuer cette nouvelle classe le mme nom
que la classe gnre par uic, mais sans le prfixe Ui::.
A laide dun diteur de texte, crez un fichier nomm gotocelldialog.h qui contient le
code suivant :
#ifndef GOTOCELLDIALOG_H
#define GOTOCELLDIALOG_H
#include <QDialog>
#include "ui_gotocelldialog.h"
class GoToCellDialog: public QDialog, public Ui::GoToCellDialog
{
Q_OBJECT
public:
GoToCellDialog(QWidget *parent = 0);
private slots:
void on_lineEdit_textChanged();
};
#endif
Chapitre 2
31
Dans le constructeur, nous appelons setupUi() pour initialiser le formulaire. Grce lhritage multiple, nous pouvons accder directement aux membres de Ui::GoToCellDialog.
Aprs avoir cr linterface utilisateur, setupUi() connectera galement automatiquement
tout slot qui respecte la convention de dnomination on_Nomobjet_NomSignal() au signal
nomSignal() correspondant de objectName. Dans notre exemple, cela signifie que setupUi()
tablira la connexion signal-slot suivante :
connect(lineEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(on_lineEdit_textChanged()));
Toujours dans le constructeur, nous dfinissons un validateur qui restreint la plage des valeurs
en entre. Qt propose trois classes de validateurs :QIntValidator, QDoubleValidator et
QRegExpValidator. Nous utilisons ici QRegExpValidator avec lexpression rgulire
"[A-Za-z][1-9][0-9]{0,2}", qui signifie : autoriser une lettre majuscule ou minuscule, suivie
dun chiffre entre 1 et 9, puis de zro, un ou deux chiffres entre 0 et 9. (En guise dintroduction
aux expressions rgulires, consultez la documentation de la classe QRegExp.)
Si vous transmettez ceci au constructeur de QRegExpValidator, vous en faites un enfant de
lobjet GoToCellDialog. Ainsi, vous navez pas besoin de prvoir la suppression de QRegExpValidator; il sera supprim automatiquement en mme temps que son parent.
Le mcanisme parent-enfant de Qt est implment dans QObject. Quand vous crez un objet
(un widget, un validateur, ou autre) avec un parent, le parent ajoute lobjet sa liste denfants.
Quand le parent est supprim, il parcourt sa liste denfants et les supprime. Les enfants eux-mmes
effacent ensuite tous leurs enfants, et ainsi de suite jusqu ce quil nen reste plus aucun.
Ce mcanisme parent-enfant simplifie nettement la gestion de la mmoire, rduisant les risques
de fuites de mmoire. Les seuls objets que vous devrez supprimer explicitement sont les objets
que vous crez avec new et qui nont pas de parent. Et si vous supprimez un objet enfant avant
son parent, Qt supprimera automatiquement cet objet de la liste des enfants du parent.
Sagissant des widgets, le parent a une signification supplmentaire : les widgets enfants sont
affichs dans la zone du parent. Quand vous supprimez le widget parent, lenfant est effac de
la mmoire mais galement de lcran.
A la fin du constructeur, nous connectons le bouton OK au slot accept() de QDialog et le
bouton Cancel au slot reject(). Les deux slots ferment la bote de dialogue, mais accept()
dfinit la valeur de rsultat de la bote de dialogue en QDialog::Accepted (qui est gal 1),
32
Chapitre 2
33
Les botes de dialogue extensibles ont habituellement une apparence simple, mais elles proposent un bouton de basculement qui permet lutilisateur dalterner entre les apparences simple
et dveloppe de la bote de dialogue. Ces botes de dialogue sont gnralement utilises dans
des applications qui tentent de rpondre la fois aux besoins des utilisateurs occasionnels et
ceux des utilisateurs expriments, masquant les options avances moins que lutilisateur ne
demande explicitement les voir. Dans cette section, nous utiliserons le Qt Designer afin de
crer la bote de dialogue extensible prsente en Figure 2.10.
Cest une bote de dialogue Sort dans un tableur, o lutilisateur a la possibilit de slectionner
une ou plusieurs colonnes trier. Lapparence simple de cette bote de dialogue permet
lutilisateur de saisir une seule cl de tri, et son apparence dveloppe propose deux cls de tri
supplmentaires. Grce au bouton More, lutilisateur bascule entre les apparences simple et
dveloppe.
Figure 2.10
La bote de dialogue Sort
dans ses deux versions,
simple et dveloppe
Nous crerons le widget avec son apparence dveloppe dans le Qt Designer, et nous masquerons les cls secondaires et tertiaires lexcution. Le widget semble complexe, mais il est
assez simple raliser dans le Qt Designer. Lastuce est de se charger dabord de la cl
primaire, puis de la dupliquer deux fois pour obtenir les cls secondaires et tertiaires :
1. Cliquez sur File > New Form et slectionnez le modle "Dialog with Buttons Right".
2. Crez le bouton More et faites-le glisser dans la disposition verticale, sous llment
despacement vertical. Dfinissez la proprit text du bouton More en &More et sa
proprit checkable en true. Configurez la proprit default du bouton OK en true.
3. Crez une zone de groupe, deux tiquettes, deux zones de liste droulante et un lment
despacement horizontal, puis placez-les sur le formulaire.
4. Faites glisser le coin infrieur droit de la zone de groupe pour lagrandir. Puis dplacez les
autres widgets dans la zone de groupe pour les positionner peu prs comme dans la
Figure 2.11 (a).
34
5. Faites glisser le bord droit de la seconde zone de liste droulante, de sorte quelle soit environ
deux fois plus grande que la premire zone de liste.
6. Dfinissez la proprit title de la zone de groupe en &Primary Key, la proprit text de
la premire tiquette en Column: et celle de la deuxime tiquette en Order:.
7. Cliquez du bouton droit sur la premire zone de liste droulante et slectionnez Edit Items
dans le menu contextuel pour ouvrir lditeur de zone de liste droulante du Qt Designer.
Crez un lment avec le texte "None".
8. Cliquez du bouton droit sur la seconde zone de liste droulante et slectionnez Edit Items.
Crez les lments "Ascending" et "Descending".
9. Cliquez sur la zone de groupe, puis sur Form > Lay Out in a Grid. Cliquez nouveau sur la
zone de groupe et sur Form > Adjust Size. Vous aboutirez la disposition affiche dans
la Figure 2.11 (b).
Figure 2.11
Disposer les enfants
de la zone de groupe
dans une grille
Si une disposition ne savre pas trs bonne ou si vous avez fait une erreur, vous pouvez toujours
cliquer sur Edit > Undo ou Form > Break Layout, puis repositionner les widgets et ressayer.
Figure 2.12
Disposer les enfants
du formulaire dans
une grille
Chapitre 2
35
Nous allons maintenant ajouter les zones de groupe Secondary Key et Tertiary Key :
1. Prenez garde ce que la fentre soit assez grande pour accueillir les composants supplmentaires.
2. Maintenez la touche Ctrl enfonce (Alt sur Mac) et cliquez sur la zone de groupe Primary
Key pour en crer une copie (et de son contenu) au-dessus de loriginal. Faites glisser la
copie sous la zone de groupe originale en gardant toujours la touche Ctrl (ou Alt) enfonce.
Rptez ce processus pour crer une troisime zone de groupe, en la faisant glisser sous la
deuxime zone.
3. Transformez leurs proprits title en &Secondary Key et &Tertiary Key.
4. Crez un lment despacement vertical et placez-le entre la zone de la cl primaire et celle
de la cl secondaire.
5. Disposez les widgets comme illustr en Figure 2.12 (a).
6. Cliquez sur le formulaire pour annuler la slection de tout widget, puis sur Form > Lay Out
in a Grid. Le formulaire devrait dsormais correspondre celui de la Figure 2.12 (b).
7. Dfinissez la proprit sizeHint des deux lments despacement verticaux en [20, 0].
La disposition de type grille qui en rsulte comporte deux colonnes et quatre lignes, ce qui fait
un total de huit cellules. La zone de groupe Primary Key, llment despacement vertical le
plus gauche, les zones de groupe Secondary Key et Tertiary Key occupent chacun une seule
cellule. La disposition verticale qui contient les boutons OK, Cancel et More occupe deux
cellules. Il reste donc deux cellules vides en bas droite de la bote de dialogue. Si ce nest pas
le cas, annulez la disposition, repositionnez les widgets et essayez nouveau.
Renommez le formulaire "SortDialog" et changez le titre de la fentre en "Sort".Dfinissez les
noms des widgets enfants comme dans la Figure 2.13.
Figure 2.13
Nommer les widgets
du formulaire
primaryGroupBox
okButton
primaryColumnCombo
cancelButton
primaryOrderCombo
moreButton
secondaryGroupBox
secondaryColumnCombo
secondaryOrderCombo
tertiaryGroupBox
tertiaryColumnCombo
tertiaryOrderCombo
36
Cliquez sur Edit > Edit Tab Order. Cliquez sur chaque zone de liste droulante de haut en bas,
puis cliquez sur les boutons OK, Cancel et More situs droite. Cliquez sur Edit > Edit
Widgets pour quitter le mode dition de lordre de tabulation.
Maintenant que le formulaire a t conu, nous sommes prts le rendre fonctionnel en configurant certaines connexions signal-slot. Le Qt Designer vous permet dtablir des connexions
entre les widgets qui font partie du mme formulaire. Nous devons tablir deux connexions.
Cliquez sur Edit > Edit Signals/Slots pour passer en mode de connexion dans le Qt Designer.
Les connexions sont reprsentes par des flches bleues entre les widgets du formulaire. Vu
que nous avons choisi le modle "Dialog with Buttons Right", les boutons OK et Cancel sont
dj connects aux slots accept() et reject() de QDialog. Les connexions sont galement
rpertories dans lditeur de signal/slot du Qt Designer.
Pour tablir une connexion entre deux widgets, cliquez sur le widget "expditeur" et faites glisser la flche rouge vers le widget "destinataire".Une bote de dialogue souvre o vous pouvez
choisir le signal et le slot connecter.
Figure 2.14
Connecter les widgets
du formulaire
Chapitre 2
37
Figure 2.15
Lditeur de connexions
du Qt Designer
Enregistrez la bote de dialogue sous sortdialog.ui dans un rpertoire appel sort. Pour
ajouter du code au formulaire, vous emploierez la mme technique dhritage multiple que
celle utilise pour la bote de dialogue Go-to-Cell de la section prcdente.
Crez tout dabord un fichier sortdialog.h qui comporte les lments suivants :
#ifndef SORTDIALOG_H
#define SORTDIALOG_H
#include <QDialog>
#include "ui_sortdialog.h"
class SortDialog: public QDialog, public Ui::SortDialog
{
Q_OBJECT
public:
SortDialog(QWidget *parent = 0);
void setColumnRange(QChar first, QChar last);
};
#endif
#include <QtGui>
#include "sortdialog.h"
3
4
5
6
SortDialog::SortDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
7
8
secondaryGroupBox->hide();
tertiaryGroupBox->hide();
38
layout()->setSizeConstraint(QLayout::SetFixedSize);
setColumnRange(A, Z);
10
11 }
12
13
14
15
16
17
18
secondaryColumnCombo->addItem(tr("None"));
tertiaryColumnCombo->addItem(tr("None"));
19
20
primaryColumnCombo->setMinimumSize(
secondaryColumnCombo->sizeHint());
QChar ch = first;
while (ch <= last) {
primaryColumnCombo->addItem(QString(ch));
secondaryColumnCombo->addItem(QString(ch));
tertiaryColumnCombo->addItem(QString(ch));
ch = ch.unicode() + 1;
}
21
22
23
24
25
26
27
28 }
Le constructeur masque les zones secondaire et tertiaire de la bote de dialogue. Il dfinit aussi
la proprit sizeConstraint de la disposition du formulaire en QLayout::SetFixedSize,
lutilisateur ne pourra donc pas la redimensionner. La disposition se charge ensuite de redimensionner automatiquement la bote de dialogue quand des widgets enfants sont affichs ou
masqus, vous tre donc sr que la bote de dialogue sera toujours prsente dans sa taille
optimale.
Le slot setColumnRange() initialise le contenu des zones de liste droulante en fonction des
colonnes slectionnes dans le tableur. Nous insrons un lment "None" dans ces zones de
liste pour les cls secondaire et tertiaire (facultatives).
Les lignes 19 et 20 prsentent un comportement subtil de la disposition. La fonction QWidget::sizeHint() retourne la taille "idale" dun widget, ce que le systme de disposition
essaie de respecter. Ceci explique pourquoi les diffrents types de widgets, ou des widgets
similaires avec un contenu diffrent, peuvent se voir attribuer des tailles diffrentes par le
systme de disposition. Concernant les zones de liste droulante, cela signifie que les zones
secondaire et tertiaire qui contiennent "None" seront plus grandes que la zone primaire qui ne
contient que des entres une lettre. Pour viter cette incohrence, nous dfinissons la taille
minimale de la zone de liste droulante primaire en taille idale de la zone secondaire.
Voici une fonction de test main() qui configure la plage de manire inclure les colonnes C
F, puis affiche la bote de dialogue :
#include <QApplication>
#include "sortdialog.h"
Chapitre 2
39
Ceci termine la bote de dialogue extensible. Vous pouvez constater que ce type de bote de
dialogue nest pas plus compliqu concevoir quune bote de dialogue ordinaire : tout ce dont
vous avez besoin, cest un bouton de basculement, quelques connexions signal-slot supplmentaires et une disposition non redimensionnable. Dans des applications de production, il est
assez frquent que le bouton qui contrle lextension affiche le texte Advanced >>> quand
seule la bote de dialogue de base est visible et Advanced <<< quand elle est dveloppe. Cest
facile concevoir dans Qt en appelant setText() sur QPushButton ds quon clique dessus.
Lautre type courant de bote de dialogue multiforme, les botes de dialogue multipages, est
encore plus facile concevoir dans Qt, soit en crant le code, soit par le biais du Qt Designer.
De telles botes de dialogue peuvent tre gnres de diverses manires.
40
La fonction findChild<T>() est une fonction membre modle qui retourne lobjet enfant qui
correspond au nom et au type donn. Vu les limites du compilateur, elle nest pas disponible
pour MSVC 6. Si vous devez utiliser le compilateur MSVC 6, appelez plutt la fonction
globale qFindChild<T>() qui fonctionne exactement de la mme faon.
La classe QUiLoader se situe dans une bibliothque part. Pour employer QUiLoader depuis
une application Qt, vous devez ajouter cette ligne de code au fichier .pro de lapplication :
CONFIG += uitools
Les botes de dialogue dynamiques vous permettent de modifier la disposition dun formulaire
sans recompiler lapplication. Elles peuvent aussi servir crer des applications client lger, o
lexcutable intgre principalement un formulaire frontal et o tous les autres formulaires sont
crs comme ncessaire.
QToolButton
QCheckBox
QRadioButton
Qt propose quatre types de "boutons" : QPushButton, QToolButton, QCheckBox et QRadioButton. QPushButton et QToolButton sont le plus souvent ajouts pour initier une action
quand on clique dessus, mais ils peuvent aussi se comporter comme des boutons de basculement
Chapitre 2
41
(clic pour enfoncer, clic pour restaurer). QCheckBox peut servir pour les options indpendantes
on/off, alors que les QRadioButton sexcluent mutuellement.
Figure 2.17
Les widgets conteneurs
une seule page de Qt
QGroupBox
QFrame
Les widgets conteneurs de Qt sont des widgets qui contiennent dautres widgets. QFrame peut
aussi tre employ seul pour tracer simplement des lignes et il est hrit par la plupart des
autres classes de widgets, dont QToolBox et QLabel.
Figure 2.18
Les widgets conteneurs
multipages de Qt
QTabWidget
QToolBox
QTabWidget et QToolBox sont des widgets multipages. Chaque page est un widget enfant, et
les pages sont numrotes en commenant 0.
Figure 2.19
Les widgets daffichage
dlments de Qt
QListView(liste)
QTreeView
QListView(icnes)
QTableView
42
Les affichages dlments sont optimiss pour grer de grandes quantits de donnes et font
souvent appel des barres de dfilement. Le mcanisme de la barre de dfilement est implment dans QAbstractScrollArea, une classe de base pour les affichages dlments et
dautres types de widgets dfilement.
Qt fournit quelques widgets simplement destins la prsentation des informations. QLabel
est le plus important dentre eux et peut tre employ pour afficher un texte enrichi (grce
une syntaxe simple de style HTML) et des images.
QTextBrowser est une sous-classe de QTextEdit en lecture seule qui prend en charge la
syntaxe HTML de base, y compris les listes, les tables, les images et les liens hypertexte.
LAssistant de Qt se sert de QTextBrowser pour prsenter la documentation lutilisateur.
Figure 2.20
Les widgets daffichage
de Qt
QLabel(texte)
QLabel (image)
QLCDNumber
QProgressBar
QTextBrowser
Qt propose plusieurs widgets pour les entres de donnes. QLineEdit peut contrler son
entre par le biais dun masque de saisie ou dun validateur. QTextEdit est une sous-classe de
QAbstractScrollArea capable de modifier de grandes quantits de texte.
Qt met votre disposition un ensemble standard de botes de dialogue courantes pratiques pour
demander lutilisateur de choisir une couleur, une police ou un fichier ou dimprimer un
document.
Sous Windows et Mac OS X, Qt exploite les botes de dialogue natives plutt que ses propres
botes de dialogue si possible.
Une bote de message polyvalente et une bote de dialogue derreur qui conserve les messages
affichs apparaissent. La progression des oprations longues peut tre indique dans un QProgressDialog ou QProgressBar prsent prcdemment. QInputDialog se rvle trs pratique
quand une seule ligne de texte ou un seul chiffre est demand lutilisateur.
Les widgets intgrs et les botes de dialogue courantes mettent votre disposition de
nombreuses fonctionnalits prtes lemploi. Des exigences plus particulires peuvent
souvent tre satisfaites en configurant les proprits du widget ou en connectant des signaux
des slots et en implmentant un comportement personnalis dans les slots.
Chapitre 2
Figure 2.21
Les widgets dentre
de Qt
QSpinBox
QDoubleSpinBox
QDateEdit
QTimeEdit
QScrollBar
QComboBox
QDateTimeEdit
QSlider
QLineEdit
QTextEdit
QDial
Figure 2.22
Les botes de dialogue
relatives la couleur
et la police de Qt
QColorDialog
QFontDialog
Figure 2.23
Les botes de dialogue
relatives limpression
et aux fichiers de Qt
QPageSetupDialog
QFileDialog
QPrintDialog
43
44
Figure 2.24
Les botes de dialogue
de feedback de Qt
QInputDialog
QProgressDialog
QMessageBox
QErrorMessage
Dans certains cas, il est judicieux de crer un widget personnalis en partant de zro. Qt facilite
normment ce processus, et les widgets personnaliss peuvent accder la mme fonction de
dessin indpendante de la plate-forme que les widgets intgrs de Qt. Les widgets personnaliss
peuvent mme tre intgrs par le biais du Qt Designer, de sorte quils puissent tre employs
de la mme faon que les widgets intgrs de Qt. Le Chapitre 5 vous explique comment crer
des widgets personnaliss.
3
Crer des fentres
principales
Au sommaire de ce chapitre
Drivation de QMainWindow
Crer des menus et des barres doutils
Configurer la barre dtat
Implmenter le menu File
Utiliser des botes de dialogue
Stocker des paramtres
Documents multiples
Pages daccueil
Ce chapitre vous apprendra crer des fentres principales avec Qt. Vous serez ainsi
capable de concevoir toute linterface utilisateur dune application, constitue de menus,
de barres doutils, dune barre dtat et dautant de botes de dialogue que ncessaire.
La fentre principale dune application fournit le cadre dans lequel linterface utilisateur
est gnre. Celle de lapplication Spreadsheet illustre en Figure 3.1 servira de base
pour ltude dans ce chapitre. Cette application emploie les botes de dialogue Find,
Go-to-Cell et Sort cres au Chapitre 2.
46
Figure 3.1
Lapplication
Spreadsheet
Derrire la plupart des applications GUI se cache du code qui fournit les fonctionnalits sousjacentes par exemple, le code qui lit et crit des fichiers ou qui traite les donnes prsentes
dans linterface utilisateur. Au Chapitre 4, vous verrez comment implmenter de telles fonctionnalits, toujours en utilisant lapplication Spreadsheet comme exemple.
Drivation de QMainWindow
La fentre principale dune application est cre en drivant QMainWindow. La plupart des
techniques tudies dans le Chapitre 2 pour crer des botes de dialogue sappliquent galement la conception de fentres principales, puisque QDialog et QMainWindow hritent de
QWidget.
Les fentres principales peuvent tre cres laide du Qt Designer, mais dans ce chapitre
nous effectuerons tout le processus dans du code pour vous montrer le fonctionnement. Si vous
prfrez une approche plus visuelle, consultez le chapitre "Creating Main Windows in Qt
Designer" dans le manuel en ligne de cet outil.
Le code source de la fentre principale de lapplication Spreadsheet est rparti entre
mainwindow.h et mainwindow.cpp. Commenons par examiner le fichier den-tte :
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class
class
class
class
QAction;
QLabel;
FindDialog;
Spreadsheet;
Chapitre 3
47
Nous dfinissons la classe MainWindow comme une sous-classe de QMainWindow. Elle contient
la macro Q_ OBJECT puisquelle fournit ses propres signaux et slots.
La fonction closeEvent() est une fonction virtuelle dans QWidget qui est appele automatiquement quand lutilisateur ferme la fentre. Elle est rimplmente dans MainWindow, de
sorte que vous puissiez poser lutilisateur la question standard "Voulez-vous enregistrer vos
modifications ?" et sauvegarder les prfrences de lutilisateur sur le disque.
private slots:
void newFile();
void open();
bool save();
bool saveAs();
void find();
void goToCell();
void sort();
void about();
Certaines options de menu, telles que File > New (Fichier > Nouveau) et Help > About (Aide >
A propos), sont implmentes comme des slots privs dans MainWindow. La majorit des
slots ont une valeur de retour void, mais save() et saveAs() retournent une valeur bool.
La valeur de retour est ignore quand un slot est excut en rponse un signal, mais lorsque
vous invoquez un slot comme une fonction, la valeur de retour est disponible, comme si vous
aviez appel nimporte quelle fonction C++ ordinaire.
void openRecentFile();
void updateStatusBar();
void spreadsheetModified();
private:
void
void
void
void
void
void
void
bool
bool
bool
createActions();
createMenus();
createContextMenu();
createToolBars();
createStatusBar();
readSettings();
writeSettings();
okToContinue();
loadFile(const QString &fileName);
saveFile(const QString &fileName);
48
La fentre principale a besoin de slots privs et de plusieurs fonctions prives pour prendre en
charge linterface utilisateur.
Spreadsheet *spreadsheet;
FindDialog *findDialog;
QLabel *locationLabel;
QLabel *formulaLabel;
QStringList recentFiles;
QString curFile;
enum { MaxRecentFiles = 5 };
QAction *recentFileActions[MaxRecentFiles];
QAction *separatorAction;
QMenu *fileMenu;
QMenu *editMenu;
...
QToolBar *fileToolBar;
QToolBar *editToolBar;
QAction *newAction;
QAction *openAction;
...
QAction *aboutQtAction;
};
#endif
En plus de ses slots et fonctions privs, MainWindow possde aussi de nombreuses variables
prives. Elles seront analyses au fur et mesure que vous les rencontrerez.
Nous allons dsormais passer en revue limplmentation :
#include
#include
#include
#include
#include
#include
<QtGui>
"finddialog.h"
"gotocelldialog.h"
"mainwindow.h"
"sortdialog.h"
"spreadsheet.h"
Nous incluons le fichier den-tte <QtGui>, qui contient la dfinition de toutes les classes Qt
utilises dans notre sous-classe. Nous englobons aussi certains fichiers den-tte personnaliss,
notamment finddialog.h, gotocelldialog.h et sortdialog.h du Chapitre 2.
MainWindow::MainWindow()
{
spreadsheet = new Spreadsheet;
setCentralWidget(spreadsheet);
createActions();
Chapitre 3
49
createMenus();
createContextMenu();
createToolBars();
createStatusBar();
readSettings();
findDialog = 0;
setWindowIcon(QIcon(":/images/icon.png"));
setCurrentFile("");
}
Dans le constructeur, nous commenons par crer un widget Spreadsheet et nous le configurons de manire ce quil devienne le widget central de la fentre principale. Le widget central
se trouve au milieu de la fentre principale (voir Figure 3.2). La classe Spreadsheet est une
sous-classe de QTableWidget avec certaines fonctions de tableur, comme la prise en charge
des formules de tableur. Nous limplmenterons dans le Chapitre 4.
Nous appelons les fonctions prives createActions(), createMenus(), createContextMenu(), createToolBars() et createStatusBar() pour configurer le reste de la fentre
principale. Nous invoquons galement la fonction prive readSettings() afin de lire les
paramtres stocks de lapplication.
Nous initialisons le pointeur findDialog pour que ce soit un pointeur nul ; au premier appel
de MainWindow::find(), nous crerons lobjet FindDialog.
A la fin du constructeur, nous dfinissons licne de la fentre en icon.png, un fichier PNG.
Qt supporte de nombreux formats dimage, dont BMP, GIF1, JPEG, PNG, PNM, XBM et
XPM. Lappel de QWidget::setWindowIcon() dfinit licne affiche dans le coin suprieur
gauche de la fentre. Malheureusement, il nexiste aucun moyen indpendant de la plate-forme
pour configurer licne de lapplication qui apparat sur le bureau. Les procdures spcifiques
la plate-forme sont expliques ladresse suivante :
http://doc.trolltech.com/4.1/appicon.html.
Les applications GUI utilisent gnralement beaucoup dimages. Il existe plusieurs mthodes
pour introduire des images dans une application. Les plus communes sont les suivantes :
1. La prise en charge du format GIF est dsactive dans Qt par dfaut, parce que lalgorithme de dcompression utilis par les fichiers GIF tait brevet dans certains pays o les brevets logiciels taient
reconnus. Nous pensons que ce brevet est arriv expiration dans le monde entier. Pour activer le
support GIF dans Qt, transmettez loption de ligne de commande -qt-gif au script configure ou
dfinissez loption approprie dans le programme dinstallation de Qt.
50
Figure 3.2
Les zones
de QMainWindow
Titre de la fentre
Barre de menus
Zones de la barre d'outils
Zones de la fentre ancre
Widget central
Barre d'tat
Dans notre cas, nous employons le mcanisme des ressources de Qt, puisquil savre plus
pratique que de charger des fichiers lexcution et il est compatible avec nimporte quel
format dimage pris en charge. Nous avons choisi de stocker les images dans larborescence
source dans un sous-rpertoire nomm images.
Pour utiliser le systme des ressources de Qt, nous devons crer un fichier de ressources et
ajouter une ligne au fichier .pro qui identifie le fichier de ressources. Dans cet exemple, nous
avons appel le fichier de ressources spreadsheet.qrc, nous insrons donc la ligne suivante
dans le fichier .pro:
RESOURCES = spreadsheet.qrc
Le fichier de ressources lui-mme utilise un format XML simple. Voici un extrait de celui que
nous avons employ :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/icon.png</file>
...
<file>images/gotocell.png</file>
</qresource>
</RCC>
Les fichiers de ressources sont compils dans lexcutable de lapplication, vous ne pouvez
donc pas les perdre. Quand vous vous rfrez aux ressources, vous codez le prfixe :/ (double
point, slash), cest pourquoi licne est spcifie comme suit, :/images/icon.png. Les
ressources peuvent tre de nimporte quel type (pas uniquement des images) et vous avez la
possibilit de les utiliser la plupart des emplacements o Qt attend un nom de fichier. Vous
les tudierez plus en dtail au Chapitre 12.
Chapitre 3
51
Figure 3.3
Les menus de lapplication Spreadsheet
Qt simplifie la programmation des menus et des barres doutils grce son concept daction.
Une action est un lment qui peut tre ajout nimporte quel nombre de menus et barres
doutils. Crer des menus et des barres doutils dans Qt implique ces tapes :
Laction New a un bouton daccs rapide (New), un parent (la fentre principale), une icne
(new.png), un raccourci clavier (Ctrl+N) et une infobulle lie ltat. Nous connectons le
signal triggered() de laction au slot priv newFile() de la fentre principale, que nous
implmenterons dans la prochaine section. Cette connexion garantit que lorsque lutilisateur
slectionne File > New, clique sur le bouton New de la barre doutils, ou appuie sur Ctrl+N, le
slot newFile() est appel.
52
Les actions Open, Save et Save As ressemblent beaucoup laction New, nous passerons donc
directement la partie "fichiers ouverts rcemment" du menu File :
...
for (int i = 0; i < MaxRecentFiles; ++i) {
recentFileActions[i] = new QAction(this);
recentFileActions[i]->setVisible(false);
connect(recentFileActions[i], SIGNAL(triggered()),
this, SLOT(openRecentFile()));
}
Nous alimentons le tableau recentFileActions avec des actions. Chaque action est masque
et connecte au slot openRecentFile(). Plus tard, nous verrons comment afficher et utiliser
les actions relatives aux fichiers rcents.
Nous pouvons donc passer laction Select All:
...
selectAllAction = new QAction(tr("&All"), this);
selectAllAction->setShortcut(tr("Ctrl+A"));
selectAllAction->setStatusTip(tr("Select all the cells in the "
"spreadsheet"));
connect(selectAllAction, SIGNAL(triggered()),
spreadsheet, SLOT(selectAll()));
Le slot selectAll() est fourni par lun des anctres de QTableWidget, QAbstractItemView, nous navons donc pas limplmenter nous-mmes.
Continuons donc par laction Show Grid dans le menu Options :
...
showGridAction = new QAction(tr("&Show Grid"), this);
showGridAction->setCheckable(true);
showGridAction->setChecked(spreadsheet->showGrid());
showGridAction->setStatusTip(tr("Show or hide the spreadsheets "
"grid"));
connect(showGridAction, SIGNAL(toggled(bool)),
spreadsheet, SLOT(setShowGrid(bool)));
Show Grid est une action cocher. Elle est affiche avec une coche dans le menu et est implmente comme un bouton bascule dans la barre doutils. Quand laction est active, le composant Spreadsheet affiche une grille. Nous initialisons laction avec la valeur par dfaut du
composant Spreadsheet, de sorte quelles soient synchronises au dmarrage. Puis nous
connectons le signal toggled(bool) de laction Show Grid au slot setShowGrid(bool) du
composant Spreadsheet, quil hrite de QTableWidget. Lorsque cette action est ajoute un
menu ou une barre doutils, lutilisateur peut activer ou dsactiver laffichage de la grille.
Les actions Show Grid et Auto-Recalculate sont des actions cocher indpendantes. Qt
prend aussi en charge des actions qui sexcluent mutuellement par le biais de la classe
QActionGroup.
Chapitre 3
53
...
aboutQtAction = new QAction(tr("About &Qt"), this);
aboutQtAction->setStatusTip(tr("Show the Qt librarys About box"));
connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
}
Concernant laction About Qt, nous utilisons le slot aboutQt() de lobjet QApplication,
accessible via la variable globale qApp.
Figure 3.4
About Qt
Maintenant que nous avons cr les actions, nous pouvons poursuivre en concevant un systme
de menus qui les englobe :
void MainWindow::createMenus()
{
fileMenu = menuBar()->addMenu(tr("&File"));
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addAction(saveAsAction);
separatorAction = fileMenu->addSeparator();
for (int i = 0; i < MaxRecentFiles; ++i)
fileMenu->addAction(recentFileActions[i]);
fileMenu->addSeparator();
fileMenu->addAction(exitAction);
Dans Qt, les menus sont des instances de QMenu. La fonction addMenu() cre un widget
QMenu avec le texte spcifi et lajoute la barre de menus. La fonction QMainWindow::menuBar() retourne un pointeur vers un QMenuBar. La barre de menus est cre la
premire fois que menuBar() est appel.
Nous commenons par crer le menu File, puis nous y ajoutons les actions New, Open, Save et
Save As. Nous insrons un sparateur pour regrouper visuellement des lments connexes.
Nous utilisons une boucle for pour ajouter les actions (masques lorigine) du tableau
recentFileActions, puis nous ajoutons laction exitAction la fin.
54
Nous avons conserv un pointeur vers lun des sparateurs. Nous avons ainsi la possibilit de
le masquer (sil ny a pas de fichiers rcents) ou de lafficher, parce que nous ne voulons pas
afficher deux sparateurs sans rien entre eux.
editMenu = menuBar()->addMenu(tr("&Edit"));
editMenu->addAction(cutAction);
editMenu->addAction(copyAction);
editMenu->addAction(pasteAction);
editMenu->addAction(deleteAction);
selectSubMenu = editMenu->addMenu(tr("&Select"));
selectSubMenu->addAction(selectRowAction);
selectSubMenu->addAction(selectColumnAction);
selectSubMenu->addAction(selectAllAction);
editMenu->addSeparator();
editMenu->addAction(findAction);
editMenu->addAction(goToCellAction);
Occupons-nous dsormais de crer le menu Edit, en ajoutant des actions avec QMenu::addAction() comme nous lavons fait pour le menu File et en ajoutant le sous-menu avec
QMenu::addMenu() lendroit o nous souhaitons quil apparaisse. Le sous-menu, comme le
menu auquel il appartient, est un QMenu.
toolsMenu = menuBar()->addMenu(tr("&Tools"));
toolsMenu->addAction(recalculateAction);
toolsMenu->addAction(sortAction);
optionsMenu = menuBar()->addMenu(tr("&Options"));
optionsMenu->addAction(showGridAction);
optionsMenu->addAction(autoRecalcAction);
menuBar()->addSeparator();
helpMenu = menuBar()->addMenu(tr("&Help"));
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
}
Nous crons les menus Tools, Options et Help de manire similaire. Nous introduisons un
sparateur entre les menus Options et Help. En styles Motif et CDE, le sparateur aligne le
menu Help droite ; dans les autres styles, le sparateur est ignor.
Figure 3.5
Barre de menus en styles
Motif et Windows
void MainWindow::createContextMenu()
{
spreadsheet->addAction(cutAction);
Chapitre 3
55
spreadsheet->addAction(copyAction);
spreadsheet->addAction(pasteAction);
spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu);
}
Tout widget Qt peut avoir une liste de QAction associe. Pour proposer un menu contextuel
pour lapplication, nous ajoutons les actions souhaites au widget Spreadsheet et nous dfinissons la stratgie du menu contextuel de ce widget de sorte quil affiche un menu contextuel
avec ces actions. Les menus contextuels sont invoqus en cliquant du bouton droit sur un
widget ou en appuyant sur une touche spcifique la plate-forme.
Figure 3.6
Le menu contextuel de
lapplication Spreadsheet
Il existe un moyen plus labor de proposer des menus contextuels : implmenter nouveau la
fonction QWidget::contextMenuEvent(), crer un widget QMenu, lalimenter avec les
actions voulues et appeler exec().
void MainWindow::createToolBars()
{
fileToolBar = addToolBar(tr("&File"));
fileToolBar->addAction(newAction);
fileToolBar->addAction(openAction);
fileToolBar->addAction(saveAction);
editToolBar = addToolBar(tr("&Edit"));
editToolBar->addAction(cutAction);
editToolBar->addAction(copyAction);
editToolBar->addAction(pasteAction);
editToolBar->addSeparator();
editToolBar->addAction(findAction);
editToolBar->addAction(goToCellAction);
}
La cration de barres doutils ressemble beaucoup celle des menus. Nous concevons les
barres doutils File et Edit. Comme un menu, une barre doutils peut possder des sparateurs.
Figure 3.7
Les barres doutils de
lapplication Spreadsheet
56
Normal
Message temporaire
Chapitre 3
57
58
setCurrentFile("");
}
}
Le slot newFile() est appel lorsque lutilisateur clique sur loption File > New ou sur le
bouton New de la barre doutils. La fonction prive okToContinue() demande lutilisateur
sil dsire enregistrer ses modifications, si certaines modifications nont pas t sauvegardes
(voir Figure 3.9). Elle retourne true si lutilisateur choisit Yes ou No (vous enregistrez le
document en appuyant sur Yes), et false si lutilisateur clique sur Cancel. La fonction
Spreadsheet::clear() efface toutes les cellules et formules du tableur. La fonction prive
setCurrentFile() met jour le titre de la fentre pour indiquer quun document sans titre
est en train dtre modifi, en plus de configurer la variable prive curFile et de mettre jour
la liste des fichiers ouverts rcemment.
Figure 3.9
"Voulez-vous enregistrer
vos modifications ?"
bool MainWindow::okToContinue()
{
if (isWindowModified()) {
int r = QMessageBox::warning(this, tr("Spreadsheet"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No,
QMessageBox::Cancel | QMessageBox::Escape);
if (r == QMessageBox::Yes) {
return save();
} else if (r == QMessageBox::Cancel) {
return false;
}
}
return true;
}
Chapitre 3
59
QMessageBox propose aussi information(), question() et critical(), chacun possdant sa propre icne.
Figure 3.10
Icnes de bote
de message
Information
Question
Avertissement
Critique
void MainWindow::open()
{
if (okToContinue()) {
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (!fileName.isEmpty())
loadFile(fileName);
}
}
Le slot open() correspond File > Open. Comme newFile(), il appelle dabord okToContinue() pour grer toute modification non sauvegarde. Puis il utilise la fonction statique
QFileDialog::getOpenFileName() trs pratique pour demander le nom du nouveau fichier
lutilisateur. La fonction ouvre une bote de dialogue, permet lutilisateur de choisir un
fichier et retourne le nom de ce dernier ou une chane vide si lutilisateur clique sur Cancel.
Le premier argument de QFileDialog::getOpenFileName() est le widget parent. La relation parent-enfant ne signifie pas la mme chose pour les botes de dialogue et pour les autres
widgets. Une bote de dialogue est toujours une fentre en soi, mais si elle a un parent, elle est
centre en haut de ce dernier par dfaut. Une bote de dialogue enfant partage aussi lentre de
la barre des tches de son parent.
Le second argument est le titre que la bote de dialogue doit utiliser. Le troisime argument
indique le rpertoire depuis lequel il doit dmarrer, dans notre cas le rpertoire en cours.
Le quatrime argument spcifie les filtres de fichier. Un filtre de fichier est constitu dun texte
descriptif et dun modle gnrique. Si nous avions pris en charge les fichiers de valeurs spares par des virgules et les fichiers Lotus 1-2-3, en plus du format de fichier natif de
Spreadsheet, nous aurions employ le filtre suivant :
tr("Spreadsheet files (*.sp)\n"
"Comma-separated values files (*.csv)\n"
"Lotus 1-2-3 files (*.wk1 *.wks)")
La fonction prive loadFile() a t invoque dans open() pour charger le fichier. Nous en
avons fait une fonction indpendante, parce que nous aurons besoin de la mme fonctionnalit
pour charger les fichiers ouverts rcemment :
bool MainWindow::loadFile(const QString &fileName)
{
if (!spreadsheet->readFile(fileName)) {
60
Le slot save() correspond File > Save. Si le fichier porte dj un nom puisque il a dj t
ouvert ou enregistr, save() appelle saveFile() avec ce nom ; sinon il invoque simplement
saveAs().
bool MainWindow::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (fileName.isEmpty())
return false;
Chapitre 3
61
return saveFile(fileName);
}
Le slot saveAs() correspond File > Save As. Nous appelons QFileDialog::getSaveFileName() pour que lutilisateur indique un nom de fichier. Si lutilisateur clique sur
Cancel, nous retournons false, qui est ensuite transmis son appelant (save() ou okToContinue()).
Si le fichier existe dj, la fonction getSaveFileName() demandera lutilisateur de
confirmer quil veut bien le remplacer. Ce comportement peut tre modifi en transmettant
QFileDialog::DontConfirmOverwrite comme argument supplmentaire getSaveFileName().
void MainWindow::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
writeSettings();
event->accept();
} else {
event->ignore();
}
}
Quand lutilisateur clique sur File > Exit ou sur le bouton de fermeture dans la barre de titre de
la fentre, le slot QWidget::close() est invoqu. Un vnement "close" est donc envoy au
widget. En implmentant nouveau QWidget::closeEvent(), nous avons la possibilit de
fermer la fentre principale et de dcider si nous voulons aussi fermer la fentre ou non.
Si certaines modifications nont pas t enregistres et si lutilisateur slectionne Cancel, nous
"ignorons" lvnement et la fentre nen sera pas affecte. En temps normal, nous acceptons
lvnement ; Qt masque donc la fentre. Nous invoquons galement la fonction prive
writeSettings() afin de sauvegarder les paramtres en cours de lapplication.
Quand la dernire fentre est ferme, lapplication se termine. Si ncessaire, nous pouvons
dsactiver ce comportement en configurant la proprit quitOnLastWindowClosed de
QApplication en false, auquel cas lapplication continue tre excute jusqu ce que
nous appelions QApplication::quit().
void MainWindow::setCurrentFile(const QString &fileName)
{
curFile = fileName;
setWindowModified(false);
QString shownName = "Untitled";
if (!curFile.isEmpty()) {
shownName = strippedName(curFile);
recentFiles.removeAll(curFile);
recentFiles.prepend(curFile);
updateRecentFileActions();
}
62
setWindowTitle(tr("%1[*] - %2").arg(shownName)
.arg(tr("Spreadsheet")));
}
QString MainWindow::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}
Dans setCurrentFile(), nous dfinissons la variable prive curFile qui stocke le nom du
fichier en cours de modification. Avant dafficher le nom du fichier dans la barre de titre, nous
supprimons le chemin daccs du fichier avec strippedName() pour le rendre plus convivial.
Chaque QWidget possde une proprit windowModified qui doit tre dfinie en true si le
document prsente des modifications non sauvegardes et en false dans les autres cas. Sous
Mac OS X, les documents non sauvegards sont indiqus par un point dans le bouton de
fermeture de la barre de titre de la fentre ; sur les autres plates-formes, ils sont indiqus par un
astrisque aprs le nom de fichier. Qt se charge automatiquement de ce comportement, tant que
nous mettons jour la proprit windowModified et que nous plaons le marqueur "[*]" dans
le titre de la fentre lendroit o nous souhaitons voir apparatre lastrisque.
Le texte transmis la fonction setWindowTitle() tait le suivant :
tr("%1[*] - %2").arg(shownName)
.arg(tr("Spreadsheet"))
La fonction QString::arg() remplace le paramtre "%n" de numro le plus bas par son
argument et retourne la chane ainsi obtenue. Dans ce cas, arg() est utilis avec deux paramtres "%n". Le premier appel de arg() remplace "%1" ; le second appel remplace "%2".Si le
nom de fichier est "budget.sp" et quaucun fichier de traduction nest charg, la chane obtenue
serait "budget.sp[*] Spreadsheet".Il aurait t plus simple dcrire
setWindowTitle(shownName + tr("[*] - Spreadsheet"));
mais arg() offre une plus grande souplesse pour les traducteurs.
Sil existe un nom de fichier, nous mettons jour recentFiles, la liste des fichiers ouverts
rcemment. Nous invoquons removeAll() pour supprimer toutes les occurrences du nom de
fichier dans la liste afin dviter les copies ; puis nous appelons prepend() pour ajouter le
nom de fichier en tant que premier lment. Aprs la mise jour de la liste, nous appelons
la fonction prive updateRecentFileActions() de manire mettre jour les entres dans
le menu File.
void MainWindow::updateRecentFileActions()
{
QMutableStringListIterator i(recentFiles);
while (i.hasNext()) {
if (!QFile::exists(i.next()))
i.remove();
Chapitre 3
63
}
for (int j = 0; j < MaxRecentFiles; ++j) {
if (j < recentFiles.count()) {
QString text = tr("&%1%2")
.arg(j + 1)
.arg(strippedName(recentFiles[j]));
recentFileActions[j]->setText(text);
recentFileActions[j]->setData(recentFiles[j]);
recentFileActions[j]->setVisible(true);
} else {
recentFileActions[j]->setVisible(false);
}
}
separatorAction->setVisible(!recentFiles.isEmpty());
}
Nous commenons par supprimer tout fichier qui nexiste plus laide dun itrateur de style
Java. Certains fichiers peuvent avoir t utiliss dans une session antrieure, mais ont t
supprims depuis. La variable recentFiles est de type QStringList (liste de QString). Le
Chapitre 11 tudie en dtail les classes conteneur comme QStringList vous expliquant
comment elles sont lies la bibliothque C++ STL (Standard Template Library), et vous
explique comment employer les classes ditrateurs de style Java dans Qt.
Nous parcourons ensuite nouveau la liste des fichiers, mais cette fois-ci en utilisant une
indexation de style tableau. Pour chaque fichier, nous crons une chane compose dun caractre &, dun chiffre (j + 1), dun espace et du nom de fichier (sans son chemin daccs). Nous
dfinissons laction correspondante pour quelle utilise ce texte. Par exemple, si le premier
fichier tait C:\My Documents\tab04.sp, le texte de la premire action serait "&1 tab04.sp".
Figure 3.11
Le menu File
avec les fichiers
ouverts rcemment
separatorAction
recentFileActions[0]
recentFileActions[1]
recentFileActions[2]
recentFileActions[3]
recentFileActions[4]
Chaque action peut avoir un lment "donne" associ de type QVariant. Le type QVariant
peut contenir des valeurs de plusieurs types C++ et Qt ; cest expliqu au Chapitre 11. Ici, nous
64
stockons le nom complet du fichier dans llment "donne" de laction, pour pouvoir le rcuprer
facilement par la suite. Nous configurons galement laction de sorte quelle soit visible.
Sil y a plus dactions de fichiers que de fichiers rcents, nous masquons simplement les actions
supplmentaires. Enfin, sil y a au moins un fichier rcent, nous dfinissons le sparateur pour
quil saffiche.
void MainWindow::openRecentFile()
{
if (okToContinue()) {
QAction *action = qobject_cast<QAction *>(sender());
if (action)
loadFile(action->data().toString());
}
}
Quand lutilisateur slectionne un fichier rcent, le slot openRecentFile() est appel. La fonction okToContinue() est excute si des changements nont pas t sauvegards, et si lutilisateur
nannule pas, nous identifions quelle action a appel le slot grce QObject::sender().
La fonction qobject_cast<T>() accomplit une conversion dynamique base sur les mtainformations gnres par moc, le compilateur des mta-objets de Qt. Elle retourne un pointeur
vers la sous-classe QObject demande, ou 0 si lobjet na pas pu tre converti dans ce type.
Contrairement dynamic_cast<T>() du langage C++ standard, qobject_cast<T>() de Qt
fonctionne correctement dans les bibliothques dynamiques. Dans notre exemple, nous utilisons qobject_cast<T>() pour convertir un pointeur QObject en une action QAction. Si la
conversion a t effectue avec succs (ce devrait tre le cas), nous appelons loadFile()
avec le nom complet du fichier que nous extrayons des donnes de laction.
Notez qutant donn que nous savons que lexpditeur est de type QAction, le programme
fonctionnerait toujours si nous avions utilis static_cast<T>() ou une conversion traditionnelle de style C. Consultez la section "Conversions de type" en Annexe B pour connatre les
diverses conversions C++.
Chapitre 3
65
Figure 3.12
La bote de dialogue Find
de lapplication
Spreadsheet
Lorsque des botes de dialogue non modales sont cres, leurs signaux sont normalement
connects aux slots qui rpondent aux interactions de lutilisateur.
void MainWindow::find()
{
if (!findDialog) {
findDialog = new FindDialog(this);
connect(findDialog, SIGNAL(findNext(const QString &,
Qt::CaseSensitivity)),
spreadsheet, SLOT(findNext(const QString &,
Qt::CaseSensitivity)));
connect(findDialog, SIGNAL(findPrevious(const QString &,
Qt::CaseSensitivity)),
spreadsheet, SLOT(findPrevious(const QString &,
Qt::CaseSensitivity)));
}
findDialog->show();
findDialog->activateWindow();
}
La bote de dialogue Find est une fentre qui permet lutilisateur de rechercher du texte dans
le tableur. Le slot find() est appel lorsque lutilisateur clique sur Edit > Find pour ouvrir la
bote de dialogue Find. A ce stade, plusieurs scnarios sont possibles :
Cest la premire fois que lutilisateur appelle la bote de dialogue Find.
La bote de dialogue Find a dj t appele auparavant, mais lutilisateur la ferme.
La bote de dialogue Find a dj t appele auparavant et est toujours affiche.
Si la bote de dialogue Find nexiste pas encore, nous la crons et nous connectons ses signaux
findNext() et findPrevious() aux slots Spreadsheet correspondants. Nous aurions aussi
pu crer la bote de dialogue dans le constructeur de MainWindow, mais ajourner sa cration
rend le dmarrage plus rapide. De mme, si la bote de dialogue nest jamais utilise, elle nest
jamais cre, ce qui vous fait gagner du temps et de la mmoire.
Nous invoquons ensuite show() et activateWindow() pour nous assurer que la fentre est
visible et active. Un seul appel de show() est suffisant pour afficher et activer une fentre
masque, mais la bote de dialogue Find peut tre appele quand sa fentre est dj visible,
auquel cas show() ne fait rien et activateWindow() est ncessaire pour activer la fentre.
Vous auriez aussi pu crire
if (findDialog->isHidden()) {
66
findDialog->show();
} else {
findDialog->activateWindow();
}
Ce code revient regarder des deux cts dune route sens unique avant de traverser.
Nous allons prsent analyser la bote de dialogue Go-to-Cell. Nous voulons que lutilisateur
louvre, lutilise, puis la ferme sans pouvoir basculer vers dautres fentres dans lapplication.
Cela signifie que la bote de dialogue Go-to-Cell doit tre modale. Une fentre modale est une
fentre qui saffiche quand elle est appele et bloque lapplication. Tout autre traitement ou
interaction est impossible tant que la fentre nest pas ferme. Les botes de dialogue douverture de fichier et les botes de message utilises prcdemment taient modales.
Figure 3.13
La bote de dialogue
Go-to-Cell de lapplication Spreadsheet
Une bote de dialogue nest pas modale si elle est appele laide de show() ( moins que
nous appelions setModal() au pralable pour la rendre modale) ; elle est modale si elle est
invoque avec exec().
void MainWindow::goToCell()
{
GoToCellDialog dialog(this);
if (dialog.exec()) {
QString str = dialog.lineEdit->text().toUpper();
spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,
str[0].unicode() - A);
}
}
Chapitre 3
67
majuscule de la chane. Nous savons que la chane aura le bon format parce que QRegExpValidator cr pour la bote de dialogue nautorise lactivation du bouton OK que sil y a une
lettre suivie par 3 chiffres maximum.
La fonction goToCell() diffre de tout le code tudi jusqu prsent, puisquelle cre un
widget (GoToCellDialog) sous la forme dune variable sur la pile. En ajoutant une ligne,
nous aurions pu utiliser tout aussi facilement new et delete:
void MainWindow::goToCell()
{
GoToCellDialog *dialog = new GoToCellDialog(this);
if (dialog->exec()) {
QString str = dialog->lineEdit->text().toUpper();
spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,
str[0].unicode() - A);
}
delete dialog;
}
La cration de botes de dialogue modales (et de menus contextuels dans des rimplmentations QWidget::contextMenuEvent()) sur la pile est un modle de programmation courant,
parce quen rgle gnrale, nous navons plus besoin de la bote de dialogue (ou du menu)
aprs lavoir utilise, et elle sera automatiquement dtruite la fin de la porte dans laquelle
elle volue.
Examinons maintenant la bote de dialogue Sort. Celle-ci est une bote de dialogue modale qui
permet lutilisateur de trier la zone slectionne sur les colonnes quil spcifie. La Figure 3.14
montre un exemple de tri, avec la colonne B comme cl de tri primaire et la colonne A comme
cl de tri secondaire (toutes les deux par ordre croissant).
Figure 3.14
Trier la zone
slectionne
du tableur
void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange(A + range.leftColumn(),
A + range.rightColumn());
if (dialog.exec()) {
68
SpreadsheetCompare compare;
compare.keys[0] =
dialog.primaryColumnCombo->currentIndex();
compare.keys[1] =
dialog.secondaryColumnCombo->currentIndex() - 1;
compare.keys[2] =
dialog.tertiaryColumnCombo->currentIndex() - 1;
compare.ascending[0] =
(dialog.primaryOrderCombo->currentIndex() == 0);
compare.ascending[1] =
(dialog.secondaryOrderCombo->currentIndex() == 0);
compare.ascending[2] =
(dialog.tertiaryOrderCombo->currentIndex() == 0);
spreadsheet->sort(compare);
}
}
Le code dans sort() suit un modle similaire celui utilis pour goToCell():
Si lutilisateur clique sur OK, nous extrayons les valeurs saisies par ce dernier partir des
widgets de la bote de dialogue et nous les utilisons.
Lappel de setColumnRange() dfinit les colonnes disponibles pour le tri sur les colonnes
slectionnes. Par exemple, en utilisant la slection illustre en Figure 3.14, range.leftColumn() produirait 0, ce qui fait A + 0 = A, et range.rightColumn() produirait 2, ce
qui fait A + 2 = C.
Lobjet compare stocke les cls de tri primaire, secondaire et tertiaire, ainsi que leurs ordres de
tri. (Nous verrons la dfinition de la classe SpreadsheetCompare dans le prochain chapitre.)
Lobjet est employ par Spreadsheet::sort() pour comparer deux lignes. Le tableau keys
stocke les numros de colonne des cls. Par exemple, si la slection stend de C2 E5, la
colonne C correspond la position 0. Le tableau ascending conserve lordre associ chaque
cl comme une valeur bool. QComboBox::currentIndex() retourne lindex de llment
slectionn, en commenant 0. Concernant les cls secondaire et tertiaire, nous soustrayons
un de llment en cours pour prendre en compte llment "None (Aucun)".
La fonction sort() rpond la demande, mais elle manque de fiabilit. Elle suppose que la
bote de dialogue Sort est implmente de manire particulire, avec des zones de liste droulante et des lments "None". Cela signifie que si nous concevons nouveau la bote de dialogue Sort, nous devrions galement rcrire ce code. Alors que cette approche convient pour
une bote de dialogue qui est toujours appele depuis le mme emplacement, elle conduit un
vritable cauchemar pour la maintenance si elle est employe plusieurs endroits.
Une mthode plus fiable consiste rendre la classe SortDialog plus intelligente en la faisant
crer un objet SpreadsheetCompare auquel son appelant peut ensuite accder. Cela simplifie
significativement MainWindow::sort():
Chapitre 3
69
void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange(A + range.leftColumn(),
A + range.rightColumn());
if (dialog.exec())
spreadsheet->performSort(dialog.comparisonObject());
}
Cette approche reproduit la premire : lappelant na pas besoin de connatre la bote de dialogue
dans les moindres dtails, mais cest la bote de dialogue qui doit totalement connatre les structures
de donnes fournies par lappelant. Cette technique peut tre pratique quand la bote de dialogue
doit appliquer des changements en direct. Mais comme le code dappel peu fiable de la premire
approche, cette troisime mthode ne fonctionne plus si les structures de donnes changent.
Certains dveloppeurs choisissent une approche quant lutilisation des botes de dialogue et
nen changent plus. Cela prsente lavantage de favoriser la familiarit et la simplicit, parce
que toutes leurs botes de dialogue respectent le mme schma, mais ils passent ct des
bnfices apports par les autres approches. La meilleure approche consiste choisir la
mthode au cas par cas.
Nous allons clore cette section avec la bote de dialogue About. Nous pourrions crer une bote
de dialogue personnalise comme pour les botes de dialogue Find ou Go-to-Cell pour prsenter les informations relatives lapplication, mais vu que la plupart des botes About adoptent
le mme style, Qt propose une solution plus simple.
void MainWindow::about()
{
QMessageBox::about(this, tr("About Spreadsheet"),
tr("<h2>Spreadsheet 1.1</h2>"
70
Vous obtenez la bote About en appelant tout simplement la fonction statique QMessageBox::about(). Cette fonction ressemble beaucoup QMessageBox::warning(), sauf quelle
emploie licne de la fentre parent au lieu de licne standard davertissement.
Figure 3.15
La bote About
de Spreadsheet
Jusqu prsent, nous avons utilis plusieurs fonctions statiques commodes dans QMessageBox et QFileDialog. Ces fonctions crent une bote de dialogue, linitialisent et appellent
exec(). Il est galement possible, mme si cest moins pratique, de crer un widget QMessageBox ou QFileDialog comme nimporte quel autre widget et dappeler explicitement
exec() ou mme show().
Chapitre 3
71
La fonction writeSettings() enregistre la disposition (position et taille) de la fentre principale, la liste des fichiers ouverts rcemment et les options Show Grid et Auto-Recalculate.
Par dfaut, QSettings stocke les paramtres de lapplication des emplacements spcifiques
la plate-forme. Sous Windows, il utilise le registre du systme ; sous Unix, il stocke les
donnes dans des fichiers texte ; sous Mac OS X, il emploie lAPI des prfrences de Core
Foundation.
Les arguments du constructeur spcifient les noms de lorganisation et de lapplication. Ces
informations sont exploites dune faon spcifique la plate-forme pour trouver un emplacement
aux paramtres.
QSettings stocke les paramtres sous forme de paires cl-valeur. La cl est similaire au
chemin daccs du systme de fichiers. Des sous-cls peuvent tre spcifies grce une
syntaxe de style chemin daccs (par exemple, findDialog/matchCase) ou beginGroup() et
endGroup():
settings.beginGroup("findDialog");
settings.setValue("matchCase", caseCheckBox->isChecked());
settings.setValue("searchBackward", backwardCheckBox->isChecked());
settings.endGroup();
La valeur peut tre de type int, bool, double, QString, QStringList, ou de nimporte quel
autre type pris en charge par QVariant, y compris des types personnaliss enregistrs.
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Spreadsheet");
QRect rect = settings.value("geometry",
QRect(200, 200, 400, 400)).toRect();
move(rect.topLeft());
resize(rect.size());
recentFiles = settings.value("recentFiles").toStringList();
updateRecentFileActions();
bool showGrid = settings.value("showGrid", true).toBool();
showGridAction->setChecked(showGrid);
bool autoRecalc = settings.value("autoRecalc", true).toBool();
autoRecalcAction->setChecked(autoRecalc);
}
La fonction readSettings() charge les paramtres qui taient sauvegards par writeSettings(). Le deuxime argument de la fonction value() indique une valeur par dfaut, dans
le cas o aucun paramtre nest disponible. Les valeurs par dfaut sont utilises la premire
fois que lapplication est excute. Etant donn quaucun second argument nest indiqu pour
la liste des fichiers rcents, il sera dfini en liste vide la premire excution.
72
Qt propose une fonction QWidget::setGeometry() pour complter QWidget::geometry(), mais elle ne fonctionne pas toujours comme prvu sous X11 en raison des limites de
la plupart des gestionnaires de fentre. Cest pour cette raison que nous utilisons plutt move()
et resize(). (Voir http://doc.trolltech.com/4.1/geometry.html pour une explication plus
dtaille.)
Nous avons opt pour une organisation dans MainWindow parmi de nombreuses approches
possibles, avec tout le code associ QSettings dans readSettings() et writeSettings(). Un objet QSettings peut tre cr pour identifier ou modifier un paramtre pendant
lexcution de lapplication et nimporte o dans le code.
Nous avons dsormais implment MainWindow dans Spreadsheet. Dans les sections suivantes, nous verrons comment lapplication Spreadsheet peut tre modifie de manire grer
plusieurs documents, et comment implmenter une page daccueil. Nous complterons ses
fonctionnalits, notamment avec la gestion des formules et le tri, dans le prochain chapitre.
Documents multiples
Nous sommes dsormais prts coder la fonction main() de lapplication Spreadsheet :
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
return app.exec();
}
Cette fonction main() est lgrement diffrente de celles que nous avons crites jusque l :
nous avons cr linstance MainWindow comme une variable sur la pile au lieu dutiliser new.
Linstance MainWindow est ensuite automatiquement dtruite quand la fonction se termine.
Avec la fonction main() prsente ci-dessus, lapplication Spreadsheet propose une seule
fentre principale et ne peut grer quun document la fois. Si vous voulez modifier plusieurs
documents en mme temps, vous pourriez dmarrer plusieurs instances de lapplication
Spreadsheet. Mais ce nest pas aussi pratique pour les utilisateurs que davoir une seule
instance de lapplication proposant plusieurs fentres principales, tout comme une instance
dun navigateur Web peut fournir plusieurs fentres de navigateur simultanment.
Nous modifierons lapplication Spreadsheet de sorte quelle puisse grer plusieurs documents.
Nous avons tout dabord besoin dun menu File lgrement diffrent :
File > New cre une nouvelle fentre principale avec un document vide, au lieu de rutiliser
la fentre principale existante.
Chapitre 3
73
Figure 3.16
Le nouveau menu File
Avec plusieurs fentres, il est maintenant intressant de crer MainWindow avec new, puisque
nous avons ensuite la possibilit dexcuter delete sur une fentre principale quand nous
avons fini afin de librer la mmoire.
Voici le nouveau slot MainWindow::newFile():
void MainWindow::newFile()
{
MainWindow *mainWin = new MainWindow;
mainWin->show();
}
Nous crons simplement une nouvelle instance de MainWindow. Cela peut sembler stupide de
ne pas conserver un pointeur vers la nouvelle fentre, mais ce nest pas un problme tant
donn que Qt assure le suivi de toutes les fentres pour nous.
Voici les actions pour Close et Exit :
void MainWindow::createActions()
{
...
closeAction = new QAction(tr("&Close"), this);
closeAction->setShortcut(tr("Ctrl+W"));
closeAction->setStatusTip(tr("Close this window"));
74
Il ordonne Qt de supprimer la fentre lorsquelle est ferme. Lattribut Qt::WA_DeleteOnClose est lun des nombreux indicateurs qui peuvent tre dfinis sur un QWidget pour
influencer son comportement.
La fuite de mmoire nest pas le seul problme rencontr. La conception de notre application
dorigine supposait que nous aurions une seule fentre principale. Dans le cas de plusieurs
fentres, chaque fentre principale possde sa propre liste de fichiers ouverts rcemment et ses
propres options. Il est vident que la liste des fichiers ouverts rcemment doit tre globale
toute lapplication. En fait, il suffit de dclarer la variable recentFiles comme statique pour
quune seule instance soit gre par lapplication. Mais nous devons ensuite garantir que tous
les appels de updateRecentFileActions() destins mettre jour le menu File concernent
bien toutes les fentres principales. Voici le code pour obtenir ce rsultat :
foreach (QWidget *win, QApplication::topLevelWidgets()) {
if (MainWindow *mainWin = qobject_cast<MainWindow *>(win))
mainWin->updateRecentFileActions();
}
Chapitre 3
75
Ce code sappuie sur la construction foreach de Qt (explique au Chapitre 11) pour parcourir
toutes les fentres de lapplication et appelle updateRecentFileActions() sur tous les
widgets de type MainWindow. Un code similaire peut tre employ pour synchroniser les options
Show Grid et Auto-Recalculate ou pour sassurer que le mme fichier nest pas charg
deux fois.
Les applications qui proposent un document par fentre principale sont appeles des applications SDI (single document interface). Il existe une alternative courante sous Windows : MDI
(multiple document interface), o lapplication comporte une seule fentre principale qui gre
plusieurs fentres de document dans sa zone daffichage centrale. Qt peut tre utilis pour crer
des applications SDI et MDI sur toutes les plates-formes prises en charge. La Figure 3.17 montre
les deux versions de lapplication Spreadsheet. MDI est abord au Chapitre 6.
Figure 3.17
SDI versus MDI
Pages daccueil
De nombreuses applications affichent une page daccueil au dmarrage. Certains dveloppeurs
se servent de cette page pour dissimuler un dmarrage lent, alors que dautres lexploitent pour
leurs services marketing. La classe QSplashScreen facilite lajout dune page daccueil aux
applications Qt.
Cette classe affiche une image avant lapparition de la fentre principale. Elle peut aussi crire
des messages sur limage pour informer lutilisateur de la progression du processus dinitialisation de lapplication. En gnral, le code de la page daccueil se situe dans main(), avant
lappel de QApplication::exec().
Le code suivant est un exemple de fonction main() qui utilise QSplashScreen pour prsenter
une page daccueil dans une application qui charge des modules et tablit des connexions
rseau au dmarrage.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
76
Figure 3.18
Une page daccueil
4
Implmenter
la fonctionnalit dapplication
Au sommaire de ce chapitre
Le widget central
Drivation de QTable
Widget
Chargement et sauvegarde
Implmenter le menu Edit
Implmenter les autres menus
Drivation de QTableWidgetItem
Dans les deux prcdents chapitres, nous vous avons expliqu comment crer linterface
utilisateur de lapplication Spreadsheet. Dans ce chapitre, nous terminerons le
programme en codant sa fonctionnalit sous-jacente. Nous verrons entre autres
comment charger et sauvegarder des fichiers, stocker des donnes en mmoire, implmenter des oprations du presse-papiers et ajouter une prise en charge des formules de
la feuille de calcul QTableWidget.
78
Le widget central
La zone centrale dun QMainWindow peut tre occupe par nimporte quel type de widget.
Voici quelques possibilits :
1. Utiliser un widget Qt standard.
Un widget standard comme QTableWidget ou QTextEdit peut tre employ comme
widget central. Dans ce cas, la fonctionnalit de lapplication, telle que le chargement et la
sauvegarde des fichiers, doit tre implmente quelque part (par exemple dans une sousclasse QMainWindow).
2. Utiliser un widget personnalis.
Des applications spcialises ont souvent besoin dafficher des donnes dans un widget
personnalis. Par exemple, un programme dditeur dicnes aurait un widget IconEditor
comme widget central. Le Chapitre 5 vous explique comment crire des widgets personnaliss dans Qt.
3. Utiliser un QWidget ordinaire avec un gestionnaire de disposition.
Il peut arriver que la zone centrale de lapplication soit occupe par plusieurs widgets.
Cest possible grce lutilisation dun QWidget comme parent de tous les autres widgets
et de gestionnaires de disposition pour dimensionner et positionner les widgets enfants.
4. Utiliser un sparateur.
Il existe un autre moyen dutiliser plusieurs widgets ensembles : un QSplitter. QSplitter dispose ses widgets enfants horizontalement ou verticalement et le sparateur offre la
possibilit lutilisateur dagir sur cette disposition. Les sparateurs peuvent contenir tout
type de widgets, y compris dautres sparateurs.
5. Utiliser un espace de travail MDI.
Si lapplication utilise MDI, la zone centrale est occupe par un widget QWorkspace et
chaque fentre MDI est un enfant de ce widget.
Les dispositions, les sparateurs et les espaces de travail MDI peuvent tre combins des
widgets Qt standards ou personnaliss. Le Chapitre 6 traite de ces classes trs en dtail.
Concernant lapplication Spreadsheet, une sous-classe QTableWidget sert de widget central.
La classe QTableWidget propose dj certaines fonctionnalits de feuille de calcul dont nous
avons besoin, mais elle ne prend pas en charge les oprations du presse-papiers et ne comprend
pas les formules comme "=A1+A2+A3".Nous implmenterons cette fonction manquante dans
la classe Spreadsheet.
Chapitre 4
79
Drivation de QTableWidget
La classe Spreadsheet hrite de QTableWidget. Un QTableWidget est une grille qui reprsente un tableau en deux dimensions. Il affiche nimporte quelle cellule que lutilisateur fait
dfiler, dans ses dimensions spcifies. Quand lutilisateur saisit du texte dans une cellule vide,
QTableWidget cre automatiquement un QTableWidgetItem pour stocker le texte.
Implmentons Spreadsheet en commenant par le fichier den-tte :
#ifndef SPREADSHEET_H
#define SPREADSHEET_H
#include <QTableWidget>
class Cell;
class SpreadsheetCompare;
Len-tte commence par les dclarations pralables des classes Cell et SpreadsheetCompare.
Figure 4.1
Arbres dhritage pour
Spreadsheet et Cell
QObject
QWidget
QTableWidget
QTableWidgetItem
Spreadsheet
Cell
Les attributs dune cellule QTableWidget, tels que son texte et son alignement, sont conservs
dans un QTableWidgetItem. Contrairement QTableWidget, QTableWidgetItem nest pas
une classe de widget ; cest une classe de donnes. La classe Cell hrite de QTableWidgetItem. Elle est dtaille pendant la prsentation de son implmentation dans la dernire section
de ce chapitre.
class Spreadsheet: public QTableWidget
{
Q_OBJECT
public:
Spreadsheet(QWidget *parent = 0);
bool autoRecalculate() const { return autoRecalc; }
QString currentLocation() const;
QString currentFormula() const;
QTableWidgetSelectionRange selectedRange() const;
void clear();
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
void sort(const SpreadsheetCompare &compare);
80
La fonction autoRecalculate() est implmente en mode inline (en ligne) parce quelle
indique simplement si le recalcul automatique est activ ou non.
Dans le Chapitre 3, nous nous sommes bass sur des fonctions publiques dans Spreadsheet lorsque nous avons implment MainWindow. Par exemple, nous avons appel clear() depuis MainWindow::newFile() pour rinitialiser la feuille de calcul. Nous avons aussi utilis certaines
fonctions hrites de QTableWidget, notamment setCurrentCell() et setShowGrid().
public slots:
void cut();
void copy();
void paste();
void del();
void selectCurrentRow();
void selectCurrentColumn();
void recalculate();
void setAutoRecalculate(bool recalc);
void findNext(const QString &str, Qt::CaseSensitivity cs);
void findPrevious(const QString &str, Qt::CaseSensitivity cs);
signals:
void modified();
Spreadsheet propose plusieurs slots implmentant des actions depuis les menus Edit, Tools
et Options, de mme quun signal, modified(), pour annoncer tout changement.
private slots:
void somethingChanged();
Dans la section prive de la classe, nous dclarons trois constantes, quatre fonctions et une
variable.
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];
Chapitre 4
81
bool ascending[KeyCount];
};
#endif
Normalement, quand lutilisateur saisit du texte dans une cellule vide, QTableWidget cre
automatiquement un QTableWidgetItem pour contenir le texte. Dans notre feuille de calcul,
nous voulons plutt crer des lments Cell. Pour y parvenir, nous appelons setItemPrototype() dans le constructeur. En interne, QTableWidget copie llment transmis comme un
prototype chaque fois quun nouvel lment est requis.
Toujours dans le constructeur, nous dfinissons le mode de slection en QAbstractItemView::ContiguousSelection pour autoriser une seule slection rectangulaire. Nous
connectons le signal itemChanged() du widget de la table au slot priv somethingChanged(); lorsque lutilisateur modifie une cellule, nous sommes donc srs que le slot somethingChanged() est appel. Enfin, nous invoquons clear() pour redimensionner la table et
configurer les en-ttes de colonne.
void Spreadsheet::clear()
{
setRowCount(0);
setColumnCount(0);
setRowCount(RowCount);
setColumnCount(ColumnCount);
for (int i = 0; i < ColumnCount; ++i) {
QTableWidgetItem *item = new QTableWidgetItem;
item->setText(QString(QChar(A + i)));
82
setHorizontalHeaderItem(i, item);
}
setCurrentCell(0, 0);
}
viewport()
verticalScrollBar()
horizontalHeader()
verticalHeader()
Figure 4.2
Les widgets
qui constituent
QTableWidget
horizontalScrollBar()
Un QTableWidget se compose de plusieurs widgets enfants (voir Figure 4.2). Un QHeaderView horizontal est positionn en haut, un QHeaderView vertical gauche et deux QScrollBar terminent cette composition. La zone au centre est occupe par un widget spcial appel
viewport, sur lequel notre QTableWidget dessine les cellules. Les divers widgets enfants
sont accessibles par le biais de fonctions hrites de QTableView et QAbstractScrollArea
(voir Figure 4.2). QAbstractScrollArea fournit un viewport quip de deux barres de dfilement, que vous pouvez activer ou dsactiver. Sa sous-classe QScrollArea est aborde au
Chapitre 6.
Chapitre 4
83
Les classes dlments de Qt peuvent tre directement employes comme des conteneurs de
donnes. Par exemple, un QTableWidgetItem stocke par dfinition quelques attributs, y compris
une chane, une police, une couleur et une icne, ainsi quun pointeur vers QTableWidget.
Les lments ont aussi la possibilit de contenir des donnes (QVariant), dont des types
personnaliss enregistrs, et en drivant la classe dlments, nous pouvons proposer des fonctionnalits supplmentaires.
Dautres kits doutils fournissent un pointeur void dans leurs classes dlments pour conserver
des donnes personnalises. Dans Qt, lapproche la plus naturelle consiste utiliser setData()
avec un QVariant, mais si un pointeur void est ncessaire, vous driverez simplement une
classe dlments et vous ajouterez une variable membre pointeur void.
Quand la gestion des donnes devient plus exigeante, comme dans le cas de jeux de donnes
de grande taille, dlments de donnes complexes, dune intgration de base de donnes et de
vues multiples de donnes, Qt propose un ensemble de classes modle/vue qui sparent les
donnes de leur reprsentation visuelle. Ces thmes sont traits au Chapitre 10.
La fonction prive cell() retourne lobjet Cell pour une ligne et une colonne donnes. Elle
est presque identique QTableWidget::item(), sauf quelle renvoie un pointeur de Cell au
lieu dun pointeur de QTableWidgetItem.
QString Spreadsheet::text(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->text();
} else {
return "";
}
}
La fonction prive text() retourne le texte dune cellule particulire. Si cell() retourne un
pointeur nul, la cellule est vide, une chane vide est donc renvoye.
QString Spreadsheet::formula(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->formula();
} else {
return "";
}
}
84
La fonction formula() retourne la formule de la cellule. Dans la plupart des cas, la formule et le
texte sont identiques ; par exemple, la formule "Hello" dtermine la chane "Hello", donc si lutilisateur tape "Hello" dans une cellule et appuie sur Entre, cette cellule affichera le texte "Hello".
Mais il y a quelques exceptions :
Si la formule est un nombre, elle est interprte en tant que tel. Par exemple, la formule
"1,50" interprte la valeur en type double 1,5, qui est affich sous la forme "1,5" justifi
droite dans la feuille de calcul.
Si la formule commence par une apostrophe, le reste de la formule est considr comme du
texte. Par exemple, la formule " 12345" interprte cette valeur comme la chane "12345."
Si la formule commence par un signe gal (=), elle est considre comme une formule
arithmtique. Par exemple, si la cellule A1 contient "12" et si la cellule A2 comporte le
chiffre "6", la formule "=A1+A2" est gale 18.
La tche qui consiste convertir une formule en valeur est accomplie par la classe Cell. Pour
linstant, limportant est de se souvenir que le texte affich dans la cellule est le rsultat de
lvaluation dune formule et pas la formule en elle-mme.
void Spreadsheet::setFormula(int row, int column,
const QString &formula)
{
Cell *c = cell(row, column);
if (!c) {
c = new Cell;
setItem(row, column, c);
}
c->setFormula(formula);
}
Chapitre 4
85
Chargement et sauvegarde
Nous allons dsormais implmenter le chargement et la sauvegarde des fichiers Spreadsheet
grce un format binaire personnalis. Pour ce faire, nous emploierons QFile et QDataStream
qui, ensemble, fournissent des entres/sorties binaires indpendantes de la plate-forme.
Nous commenons par crire un fichier Spreadsheet :
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot write file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(MagicNumber);
QApplication::setOverrideCursor(Qt::WaitCursor);
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
QApplication::restoreOverrideCursor();
return true;
}
86
Nous crons un objet QFile avec un nom de fichier donn et nous invoquons open() pour
ouvrir le fichier en criture. Nous crons aussi un objet QDataStream qui agit sur le QFile et
sen sert pour crire les donnes.
Juste avant dcrire les donnes, nous changeons le pointeur de lapplication en pointeur
dattente standard (gnralement un sablier) et nous restaurons le pointeur normal lorsque
toutes les donnes ont t crites. A la fin de la fonction, le fichier est ferm automatiquement
par le destructeur de QFile.
QDataStream prend en charge les types C++ de base, de mme que plusieurs types de Qt.
La syntaxe est conue selon les classes <iostream> du langage C++ Standard. Par exemple,
out << x << y << z;
les lit depuis un flux. Etant donn que les types C++ de base char, short, int, long et long
long peuvent prsenter des tailles diffrentes selon les plates-formes, il est plus sr de convertir ces valeurs en qint8, quint8, qint16, quint16, qint32, quint32, qint64 ou
quint64; vous avez ainsi la garantie de travailler avec un type de la taille annonce (en bits).
Le format de fichier de lapplication Spreadsheet est assez simple (voir Figure 4.3). Un fichier
Spreadsheet commence par un numro sur 32 bits qui identifie le format de fichier (MagicNumber, dfini par 0x7F51C883 dans spreadsheet.h, un chiffre alatoire). Ce numro est
suivi dune srie de blocs, chacun deux contenant la ligne, la colonne et la formule dune
seule cellule. Pour conomiser de lespace, nous ncrivons pas de cellules vides.
Figure 4.3
Le format du fichier
Spreadsheet
0x7F51C883
123
5 Fr
123
6 Francium
La reprsentation binaire prcise des types de donnes est dtermine par QDataStream. Par
exemple, un type quint16 est stock sous forme de deux octets en ordre big-endian, et un
QString se compose de la longueur de la chane suivie des caractres Unicode.
La reprsentation binaire des types Qt a largement volu depuis Qt 1.0. Il est fort probable
quelle continue son dveloppement dans les futures versions de Qt pour suivre lvolution des
types existants et pour tenir compte des nouveaux types Qt. Par dfaut, QDataStream utilise la
version la plus rcente du format binaire (version 7 dans Qt 4.1), mais il peut tre configur de
manire lire des versions antrieures. Pour viter tout problme de compatibilit si lapplication est recompile par la suite avec une nouvelle version de Qt, nous demandons explicitement
QDataStream demployer la version 7 quelle que soit la version de Qt utilise pour la compilation. (QDataStream::Qt_4_1 est une constante pratique qui vaut 7.)
QDataStream est trs polyvalent. Il peut tre employ sur QFile, mais aussi sur QBuffer,
QProcess, QTcpSocket ou QUdpSocket. Qt propose galement une classe QTextStream qui
Chapitre 4
87
peut tre utilise la place de QDataStream pour lire et crire des fichiers texte. Le Chapitre 12 se penche en dtail sur ces classes et dcrit les diverses approches consistant grer les
diffrentes versions de QDataStream.
bool Spreadsheet::readFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("Cannot read file %1:\n%2.")
.arg(file.fileName())
.arg(file.errorString()));
return false;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_1);
quint32 magic;
in >> magic;
if (magic!= MagicNumber) {
QMessageBox::warning(this, tr("Spreadsheet"),
tr("The file is not a Spreadsheet file."));
return false;
}
clear();
quint16 row;
quint16 column;
QString str;
QApplication::setOverrideCursor(Qt::WaitCursor);
while (!in.atEnd()) {
in >> row >> column >> str;
setFormula(row, column, str);
}
QApplication::restoreOverrideCursor();
return true;
}
La fonction readFile() ressemble beaucoup writeFile(). Nous utilisons QFile pour lire
le fichier, mais cette fois-ci avec QIODevice::ReadOnly et pas QIODevice::WriteOnly.
Puis nous dfinissons la version de QDataStream en 7. Le format de lecture doit toujours tre
le mme que celui de lcriture.
Si le fichier dbute par le nombre magique appropri, nous appelons clear() pour vider
toutes les cellules de la feuille de calcul et nous lisons les donnes de la cellule. Vu que le
fichier ne contient que des donnes pour des cellules non vides, et quil est trs improbable que
chaque cellule de la feuille de calcul soit dfinie, nous devons nous assurer que toutes les cellules
sont effaces avant la lecture.
88
Le slot cut() correspond Edit > Cut. Limplmentation est simple parce que Cut est quivalent
Copy suivi de Delete.
Figure 4.4
Le menu Edit de lapplication Spreadsheet
void Spreadsheet::copy()
{
QTableWidgetSelectionRange range = selectedRange();
QString str;
for (int i = 0; i < range.rowCount(); ++i) {
if (i > 0)
str += "\n";
for (int j = 0; j < range.columnCount(); ++j) {
if (j > 0)
str += "\t";
str += formula(range.topRow() + i, range.leftColumn() + j);
}
}
QApplication::clipboard()->setText(str);
}
Le slot copy() correspond Edit > Copy. Il parcourt la slection actuelle (qui est simplement
la cellule en cours sil ny a pas de slection explicite). La formule de chaque cellule slectionne est ajoute QString, avec des lignes spares par des sauts de ligne et des colonnes spares
par des tabulations.
Le presse-papiers est disponible dans Qt par le biais de la fonction statique QApplication::clipboard(). En appelant QClipboard::setText(), le texte est disponible dans le
Chapitre 4
89
presse-papiers, la fois pour cette application et pour dautres qui prennent en charge le texte
brut (voir Figure 4.5). Notre format, bas sur les tabulations et les sauts de lignes en tant que
sparateurs, est compris par une multitude dapplications, dont Microsoft Excel.
Figure 4.5
Copier une slection
dans le presse-papiers
"Red\tGreen\tBlue\nCyan\tMagenta\tYellow"
Sil ny a quune slection, nous retournons simplement la premire (et unique). Vous ne
devriez jamais vous trouver dans le cas o il ny a aucune slection, tant donn que le mode
ContiguousSelection considre la cellule en cours comme slectionne. Toutefois pour
prvenir tout bogue dans notre programme, nous grons ce cas de figure.
void Spreadsheet::paste()
{
QTableWidgetSelectionRange range = selectedRange();
QString str = QApplication::clipboard()->text();
QStringList rows = str.split(\n);
int numRows = rows.count();
int numColumns = rows.first().count(\t) + 1;
if (range.rowCount() * range.columnCount()!= 1
&& (range.rowCount()!= numRows
|| range.columnCount()!= numColumns)) {
QMessageBox::information(this, tr("Spreadsheet"),
tr("The information cannot be pasted because the copy "
"and paste areas arent the same size."));
return;
}
for (int i = 0; i < numRows; ++i) {
90
Le slot paste() correspond Edit > Paste. Nous rcuprons le texte dans le presse-papiers et
nous appelons la fonction statique QString::split() pour adapter la chane au QStringList.
Chaque ligne devient une chane dans la liste.
Nous dterminons ensuite la dimension de la zone de copie. Le nombre de lignes correspond
au nombre de chanes dans QStringList; le nombre de colonnes est le nombre de tabulations la premire ligne, plus 1. Si une seule cellule est slectionne, nous nous servons de
cette cellule comme coin suprieur gauche de la zone de collage ; sinon, nous utilisons la
slection actuelle comme zone de collage.
Pour effectuer le collage, nous parcourons les lignes et nous les divisons en cellules grce
QString::split(), mais cette fois-ci avec une tabulation comme sparateur. La Figure 4.6
illustre ces tapes.
"Red\tGreen\tBlue\nCyan\tMagenta\tYellow"
Figure 4.6
Coller le texte du pressepapiers dans la feuille
de calcul
["Red\tGreen\tBlue","Cyan\tMagenta\tYellow"]
["Red","Green","Blue"]
["Cyan","Magenta","Yellow"]
void Spreadsheet::del()
{
foreach (QTableWidgetItem *item, selectedItems())
delete item;
}
Le slot del() correspond Edit > Delete. Il suffit dutiliser delete sur chaque objet Cell de
la slection pour effacer les cellules. QTableWidget remarque quand ses QTableWidgetItem
sont supprims et se redessine automatiquement si lun des lments tait visible. Si nous invoquons cell() avec lemplacement dune cellule supprime, il renverra un pointeur nul.
Chapitre 4
91
void Spreadsheet::selectCurrentRow()
{
selectRow(currentRow());
}
void Spreadsheet::selectCurrentColumn()
{
selectColumn(currentColumn());
}
Le slot findNext() parcourt les cellules en commenant par la cellule droite du pointeur et
en se dirigeant vers la droite jusqu la dernire colonne, puis il poursuit par la premire
colonne dans la ligne du dessous et ainsi de suite jusqu trouver le texte recherch ou jusqu
atteindre la toute dernire cellule. Par exemple, si la cellule en cours est la cellule C24, nous
recherchons D24, E24, , Z24, puis A25, B25, C25, , Z25, et ainsi de suite jusqu Z999. Si
nous trouvons une correspondance, nous supprimons la slection actuelle, nous dplaons le
pointeur vers cette cellule et nous activons la fentre qui contient Spreadsheet. Si aucune
correspondance nest dcouverte, lapplication met un signal sonore pour indiquer que la
recherche na pas abouti.
void Spreadsheet::findPrevious(const QString &str,
Qt::CaseSensitivity cs)
{
92
Le slot findPrevious() est similaire findNext(), sauf quil effectue une recherche dans
lautre sens et sarrte la cellule A1.
Le slot recalculate() correspond Tools > Recalculate. Il est aussi appel automatiquement
par Spreadsheet si ncessaire.
Chapitre 4
93
Nous parcourons toutes les cellules et invoquons setDirty() sur chacune delles pour signaler
celles qui doivent tre recalcules. La prochaine fois que QTableWidget appelle text() sur
Cell pour obtenir la valeur afficher dans la feuille de calcul, la valeur sera recalcule.
Nous appelons ensuite update() sur le viewport pour redessiner la feuille de calcul complte.
Le code de raffichage dans QTableWidget invoque ensuite text() sur chaque cellule visible
pour obtenir la valeur afficher. Vu que nous avons appel setDirty() sur chaque cellule, les
appels de text() utiliseront une valeur nouvellement calcule. Le calcul pourrait exiger que
les cellules non visibles soient recalcules, rpercutant la mme opration jusqu ce que chaque
cellule qui a besoin dtre recalcule pour afficher le bon texte dans le viewport ait une valeur
ractualise. Le calcul est effectu par la classe Cell.
void Spreadsheet::setAutoRecalculate(bool recalc)
{
autoRecalc = recalc;
if (autoRecalc)
recalculate();
}
94
Le tri sopre sur la slection actuelle et rorganise les lignes selon les cls et les ordres de tri
stocks dans lobjet compare. Nous reprsentons chaque ligne de donnes avec QStringList
et nous conservons la slection sous forme de liste de lignes (voir Figure 4.8). Nous nous
servons de lalgorithme qStableSort() de Qt et pour une question de simplicit, le tri
seffectue sur la formule plutt que sur la valeur. Les algorithmes standards, de mme que les
structures de donnes de Qt sont abords au Chapitre 11.
Figure 4.8
Stocker la slection
sous forme de liste
de lignes
index
value
["Edsger","Dijkstra","1930-05-11"]
["Tony","Hoare","1934-01-11"]
["Niklaus","Wirth","1934-02-15"]
["Donald","Knuth","1938-01-10"]
La fonction qStableSort() reoit un itrateur de dbut et de fin, ainsi quune fonction de comparaison. La fonction de comparaison est une fonction qui reoit deux arguments (deux QStringList) et qui retourne true si le premier argument est "infrieur" au second argument et false
dans les autres cas. Lobjet compare que nous transmettons comme fonction de comparaison
nest pas vraiment une fonction, mais il peut tre utilis comme telle, comme nous allons le voir.
Figure 4.9
Rintgrer les
donnes dans
la table aprs le tri
index
0
value
["Donald","Knuth","1938-01-10"]
["Edsger","Dijkstra","1930-05-11"]
["Niklaus","Wirth","1934-02-15"]
["Tony","Hoare","1934-01-11"]
Aprs avoir excut qStableSort(), nous rintgrons les donnes dans la table (voir
Figure 4.9), nous effaons la slection et nous appelons somethingChanged().
Dans spreadsheet.h, la classe SpreadsheetCompare tait dfinie comme suit :
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];
bool ascending[KeyCount];
};
La classe SpreadsheetCompare est spciale parce quelle implmente un oprateur (). Nous
avons donc la possibilit dutiliser la classe comme si ctait une fonction. De telles classes
sont appeles des objets fonction, ou foncteurs. Pour comprendre comment fonctionnent les
foncteurs, nous dbutons par un exemple simple :
Chapitre 4
95
class Square
{
public:
int operator()(int x) const { return x * x; }
}
La classe Square fournit une fonction, operator()(int), qui retourne le carr de son paramtre. En nommant la fonction operator()(int), au lieu de compute(int) par exemple,
nous avons la possibilit dutiliser un objet de type Square comme si ctait une fonction :
Square square;
int y = square(5);
Lobjet compare peut tre employ comme sil tait une fonction compare() ordinaire. De
plus, son implmentation peut accder toutes les cls et ordres de tri stocks comme variables
membres.
Il existe une alternative : nous aurions pu conserver tous les ordres et cls de tri dans des variables
globales et utiliser une fonction compare() ordinaire. Cependant, la communication via les
variables globales nest pas trs lgante et peut engendrer des bogues subtils. Les foncteurs
sont plus puissants pour interfacer avec des fonctions modles comme qStableSort().
Voici limplmentation de la fonction employe pour comparer deux lignes de la feuille de calcul :
bool SpreadsheetCompare::operator()(const QStringList &row1,
const QStringList &row2) const
{
for (int i = 0; i < KeyCount; ++i) {
int column = keys[i];
if (column!= -1) {
if (row1[column]!= row2[column]) {
if (ascending[i]) {
return row1[column] < row2[column];
} else {
return row1[column] > row2[column];
}
}
}
}
return false;
}
96
Loprateur retourne true si la premire ligne est infrieure la seconde et false dans les
autres cas. La fonction qStableSort() utilise ce rsultat pour effectuer le tri.
Les tables keys et ascending de lobjet SpreadsheetCompare sont alimentes dans la fonction
MainWindow::sort() (vue au Chapitre 2). Chaque cl possde un index de colonne ou 1
("None").
Nous comparons les entres de cellules correspondantes dans les deux lignes pour chaque cl
dans lordre. Ds que nous dcouvrons une diffrence, nous retournons une valeur true ou
false. Sil savre que toutes les comparaisons sont gales, nous retournons false. La fonction qStableSort() sappuie sur lordre avant le tri pour rsoudre les situations dgalit ; si
row1 prcdait row2 lorigine et nest jamais "infrieur " lautre, row1 prcdera toujours
row2 dans le rsultat. Cest ce qui distingue qStableSort() de son cousin qSort() dont le
rsultat est moins prvisible.
Nous avons dsormais termin la classe Spreadsheet. Dans la prochaine section, nous allons
analyser la classe Cell. Cette classe est employe pour contenir les formules des cellules et
propose une rimplmentation de la fonction QTableWidgetItem::data()
que
Spreadsheet appelle indirectement par le biais de la fonction QTableWidgetItem::text().
Lobjectif est dafficher le rsultat du calcul de la formule dune cellule.
Drivation de QTableWidgetItem
La classe Cell hrite de QTableWidgetItem. Cette classe est conue pour bien fonctionner
avec Spreadsheet, mais elle ne dpend pas spcifiquement de cette classe et pourrait, en
thorie, tre utilise dans nimporte quel QTableWidget. Voici le fichier den-tte :
#ifndef CELL_H
#define CELL_H
#include <QTableWidgetItem>
class Cell: public QTableWidgetItem
{
public:
Cell();
QTableWidgetItem *clone() const;
void setData(int role, const QVariant &value);
QVariant data(int role) const;
void setFormula(const QString &formula);
QString formula() const;
void setDirty();
private:
QVariant value() const;
QVariant evalExpression(const QString &str, int &pos) const;
QVariant evalTerm(const QString &str, int &pos) const;
Chapitre 4
97
Nous utilisons QVariant parce que certaines cellules ont une valeur double alors que dautres
ont une valeur QString.
Les variables cachedValue et cacheIsDirty sont dclares avec le mot-cl C++ mutable.
Nous avons ainsi la possibilit de modifier ces variables dans des fonctions const. Nous pourrions
aussi recalculer la valeur chaque fois que text() est appele, mais ce serait tout fait inefficace.
Notez quil ny a pas de macro Q_OBJECT dans la dfinition de classe. Cell est une classe C++
ordinaire, sans signaux ni slots. En fait, vu que QTableWidgetItem nhrite pas de QObject,
nous ne pouvons pas avoir de signaux et de slots dans Cell. Les classes dlments de Qt
nhritent pas de QObject pour optimiser les performances. Si des signaux et des slots se rvlent ncessaires, ils peuvent tre implments dans le widget qui contient les lments ou,
exceptionnellement, en utilisant lhritage multiple avec QObject.
Voici le dbut de cell.cpp:
#include <QtGui>
#include "cell.h"
Cell::Cell()
{
setDirty();
}
Dans le constructeur, nous devons simplement dfinir le cache comme tant actualiser
(dirty). Vous navez pas besoin de transmettre un parent ; quand la cellule est insre dans un
QTableWidget avec setItem(), le QTableWidget prend automatiquement possession de
celle-ci.
Chaque QTableWidgetItem peut contenir des donnes, jusqu un QVariant pour chaque
"rle" de donnes. Les rles les plus couramment utiliss sont Qt::EditRole et
Qt::DisplayRole. Le rle de modification est employ pour des donnes qui doivent tre
modifies et le rle daffichage pour des donnes qui doivent tre affiches. Il arrive souvent
que ces donnes soient les mmes, mais dans Cell le rle de modification correspond la
98
La fonction clone() est invoque par QTableWidget quand il doit crer une nouvelle
cellule par exemple quand lutilisateur commence taper dans une cellule vide qui na
encore jamais t utilise. Linstance transmise QTableWidget::setItemPrototype() est
llment qui est clon. Vu que la copie au niveau du membre est suffisante pour Cell, nous
nous basons sur le constructeur de copie par dfaut cr automatiquement par C++ dans le but
de crer de nouvelles instances Cell dans la fonction clone().
void Cell::setFormula(const QString &formula)
{
setData(Qt::EditRole, formula);
}
Si nous avons affaire une nouvelle formule, nous dfinissons cacheIsDirty en true pour
garantir que la cellule sera recalcule la prochaine fois que text() est appel.
Aucune fonction text() nest dfinie dans Cell, mme si nous appelons text() sur des
instances Cell dans Spreadsheet::text(). La fonction text() est une fonction de
convenance propose par QTableWidgetItem; cela revient au mme que dappeler
data(Qt::DisplayRole).toString().
void Cell::setDirty()
{
cacheIsDirty = true;
}
Chapitre 4
99
La fonction setDirty() est invoque pour forcer le recalcul de la valeur dune cellule. Elle
dfinit simplement cacheIsDirty en true, ce qui signifie que cachedValue nest plus
jour. Le recalcul nest effectu que lorsquil est ncessaire.
QVariant Cell::data(int role) const
{
if (role == Qt::DisplayRole) {
if (value().isValid()) {
return value().toString();
} else {
return "####";
}
} else if (role == Qt::TextAlignmentRole) {
if (value().type() == QVariant::String) {
return int(Qt::AlignLeft | Qt::AlignVCenter);
} else {
return int(Qt::AlignRight | Qt::AlignVCenter);
}
} else {
return QTableWidgetItem::data(role);
}
}
La fonction data() est rimplmente dans QTableWidgetItem. Elle retourne le texte qui
doit tre affich dans la feuille de calcul si elle est appele avec Qt::DisplayRole, et la
formule si elle est invoque avec Qt::EditRole. Elle renvoie lalignement appropri si elle
est appele avec Qt::TextAlignmentRole. Dans le cas de DisplayRole, elle se base sur
value() pour calculer la valeur de la cellule. Si la valeur nest pas valide (parce que la
formule est mauvaise), nous retournons "####".
La fonction Cell::value() utilise dans data() retourne un QVariant. Un QVariant peut
stocker des valeurs de diffrents types, comme double et QString, et propose des fonctions
pour convertir les variants dans dautres types. Par exemple, appeler toString() sur un
variant qui contient une valeur double produit une chane de double. Un QVariant construit
avec le constructeur par dfaut est un variant "invalide".
const QVariant Invalid;
QVariant Cell::value() const
{
if (cacheIsDirty) {
cacheIsDirty = false;
QString formulaStr = formula();
if (formulaStr.startsWith(\)) {
cachedValue = formulaStr.mid(1);
} else if (formulaStr.startsWith(=)) {
cachedValue = Invalid;
QString expr = formulaStr.mid(1);
expr.replace(" ", "");
expr.append(QChar::Null);
100
int pos = 0;
cachedValue = evalExpression(expr, pos);
if (expr[pos]!= QChar::Null)
cachedValue = Invalid;
} else {
bool ok;
double d = formulaStr.toDouble(&ok);
if (ok) {
cachedValue = d;
} else {
cachedValue = formulaStr;
}
}
}
return cachedValue;
}
La fonction prive value() retourne la valeur de la cellule. Si cacheIsDirty est true, nous
devons la recalculer.
Si la formule commence par une apostrophe (par exemple " 12345"), lapostrophe se trouve
la position 0 et la valeur est la chane allant de la position 1 la fin.
Si la formule commence par un signe gal (=), nous prenons la chane partir de la position 1
et nous supprimons tout espace quelle contient. Nous appelons ensuite evalExpression()
pour calculer la valeur de lexpression. Largument pos est transmis par rfrence ; il indique
la position du caractre o lanalyse doit commencer. Aprs lappel de evalExpression(), le
caractre la position pos doit tre le caractre QChar::Null que nous avons ajout, sil a t
correctement analys. Si lanalyse a chou avant la fin, nous dfinissons cachedValue de
sorte quil soit Invalid.
Si la formule ne commence pas par une apostrophe ni par un signe gal, nous essayons de la
convertir en une valeur virgule flottante laide de toDouble(). Si la conversion fonctionne,
nous configurons cachedValue pour y stocker le nombre obtenu ; sinon, nous dfinissons
cachedValue avec la chane de la formule. Par exemple, avec une formule de "1,50," toDouble() dfinit ok en true et retourne 1,5, alors quavec une formule de "World Population"
toDouble() dfinit ok en false et renvoie 0,0.
En transmettant toDouble() un pointeur de type bool, nous sommes en mesure de faire une
distinction entre la conversion dune chane qui donne la valeur numrique 0,0 et une erreur de
conversion (o 0,0 est aussi retourn mais bool est dfini en false). Il est cependant parfois
ncessaire de retourner une valeur zro sur un chec de conversion, auquel cas nous navons
pas besoin de transmettre de pointeur de bool. Pour des questions de performances et de portabilit, Qt nutilise jamais dexceptions C++ pour rapporter des checs. Ceci ne vous empche pas
de les utiliser dans des programmes Qt, condition que votre compilateur les prenne en charge.
La fonction value() est dclare const. Nous devions dclarer cachedValue et cacheIsValid comme des variables mutable, de sorte que le compilateur nous permette de les modifier
dans des fonctions const. Ce pourrait tre tentant de rendre value() non-const et de supprimer
Chapitre 4
101
les mots cls mutable, mais le rsultat ne compilerait pas parce que nous appelons value()
depuis data(), une fonction const.
Nous avons dsormais fini lapplication Spreadsheet, except lanalyse des formules. Le reste
de cette section est ddie evalExpression() et les deux fonctions evalTerm() et evalFactor(). Le code est un peu compliqu, mais il est introduit ici pour complter lapplication.
Etant donn que le code nest pas li la programmation dinterfaces graphiques utilisateurs,
vous pouvez tranquillement lignorer et continuer lire le Chapitre 5.
La fonction evalExpression() retourne la valeur dune expression dans la feuille de calcul.
Une expression est dfinie sous la forme dun ou plusieurs termes spars par les oprateurs
"+" ou "".Les termes eux-mmes sont dfinis comme un ou plusieurs facteurs spars par les
oprateurs "*" ou "/".En divisant les expressions en termes et les termes en facteurs, nous
sommes srs que les oprateurs sont appliqus dans le bon ordre.
Par exemple, "2*C5+D6" est une expression avec "2*C5" comme premier terme et "D6"
comme second terme. Le terme "2*C5" a "2" comme premier facteur et "C5" comme
deuxime facteur, et le terme "D6" est constitu dun seul facteur "D6".Un facteur peut tre un
nombre ("2"), un emplacement de cellule ("C5") ou une expression entre parenthses, prcde
facultativement dun signe moins unaire.
Expression
Terme
Terme
Facteur
Facteur
Nombre
Emplacement de cellule
(
Expression
Figure 4.10
Diagramme de la syntaxe des expressions de la feuille de calcul
La syntaxe des expressions de la feuille de calcul est prsente dans la Figure 4.10. Pour
chaque symbole de la grammaire (Expression, Terme et Facteur), il existe une fonction membre
correspondante qui lanalyse et dont la structure respecte scrupuleusement cette grammaire.
Les analyseurs crits de la sorte sont appels des analyseurs vers le bas rcursifs.
Commenons par evalExpression(), la fonction qui analyse une Expression :
QVariant Cell::evalExpression(const QString &str, int &pos) const
{
QVariant result = evalTerm(str, pos);
while (str[pos]!= QChar::Null) {
QChar op = str[pos];
if (op!= + && op!= -)
return result;
++pos;
102
Nous appelons tout dabord evalTerm() pour obtenir la valeur du premier terme. Si le caractre suivant est "+" ou "", nous appelons evalTerm() une deuxime fois ; sinon, lexpression est constitue dun seul terme et nous retournons sa valeur en tant que valeur de toute
lexpression. Une fois que nous avons les valeurs des deux premiers termes, nous calculons le
rsultat de lopration en fonction de loprateur. Si les deux termes ont t valus en double,
nous calculons le rsultat comme tant double; sinon nous dfinissons le rsultat comme
tant Invalid.
Nous continuons de cette manire jusqu ce quil ny ait plus de termes. Ceci fonctionne
correctement parce que les additions et les soustractions sont de type associatif gauche ; cest-dire que "123" signifie "(12)3" et non "1(23)."
QVariant Cell::evalTerm(const QString &str, int &pos) const
{
QVariant result = evalFactor(str, pos);
while (str[pos]!= QChar::Null) {
QChar op = str[pos];
if (op!= * && op!= /)
return result;
++pos;
QVariant factor = evalFactor(str, pos);
if (result.type() == QVariant::Double
&& factor.type() == QVariant::Double) {
if (op == *) {
result = result.toDouble() * factor.toDouble();
} else {
if (factor.toDouble() == 0.0) {
result = Invalid;
} else {
result = result.toDouble() / factor.toDouble();
}
}
} else {
Chapitre 4
103
result = Invalid;
}
}
return result;
}
104
if (!ok)
result = Invalid;
}
}
if (negative) {
if (result.type() == QVariant::Double) {
result = -result.toDouble();
} else {
result = Invalid;
}
}
return result;
}
La fonction evalFactor() est un peu plus complique que evalExpression() et evalTerm(). Nous regardons dabord si le facteur est prcd du signe ngatif. Nous examinons
ensuite sil commence par une parenthse ouverte. Si cest le cas, nous valuons le contenu des
parenthses comme une expression en appelant evalExpression(). Lorsque nous valuons
une expression entre parenthses, evalExpression() appelle evalTerm(), qui invoque
evalFactor(), qui appelle nouveau evalExpression(). Cest l quintervient la rcursivit dans lanalyseur.
Si le facteur nest pas une expression imbrique, nous extrayons le prochain jeton, qui devrait
tre un emplacement de cellule ou un nombre. Si le jeton correspond QRegExp, nous le considrons comme une rfrence de cellule et nous appelons value() sur la cellule lemplacement donn. La cellule pourrait se trouver nimporte o dans la feuille de calcul et pourrait tre
dpendante dautres cellules. Les dpendances ne sont pas un problme ; elles dclencheront
simplement plus dappels de value() et (pour les cellules " recalculer") plus danalyse
jusqu ce que les valeurs des cellules dpendantes soient calcules. Si le jeton nest pas un
emplacement de cellule, nous le considrons comme un nombre.
Que se passe-t-il si la cellule A1 contient la formule "=A1" ? Ou si la cellule A1 contient
"=A2" et la cellule A2 comporte "=A1" ? Mme si nous navons pas crit de code spcial pour
dtecter des dpendances circulaires, lanalyseur gre ces cas en retournant un QVariant invalide. Ceci fonctionne parce que nous dfinissons cacheIsDirty en false et cachedValue en
Invalid dans value() avant dappeler evalExpression(). Si evalExpression() appelle
de manire rcursive value() sur la mme cellule, il renvoie immdiatement Invalid et toute
lexpression est donc value en Invalid.
Nous avons dsormais termin lanalyseur de formules. Il nest pas compliqu de ltendre
pour quil gre des fonctions prdfinies de la feuille de calcul, comme sum() et avg(), en
dveloppant la dfinition grammaticale de Facteur. Une autre extension facile consiste implmenter loprateur "+" avec des oprandes de chane (comme une concatnation) ; aucun changement de grammaire nest exig.
5
Crer des widgets
personnaliss
Au sommaire de ce chapitre
Personnaliser des widgets Qt
Driver QWidget
Intgrer des widgets personnaliss avec
le Qt Designer
Double mise en mmoire tampon
Ce chapitre vous explique comment concevoir des widgets personnaliss laide de Qt.
Les widgets personnaliss peuvent tre crs en drivant un widget Qt existant ou en
drivant directement QWidget. Nous vous prsenterons les deux approches et nous
verrons galement comment introduire un widget personnalis avec le Qt Designer de
sorte quil puisse tre utilis comme nimporte quel widget Qt intgr. Nous terminerons ce chapitre en vous parlant dun widget personnalis qui emploie la double mise en
mmoire tampon, une technique puissante pour actualiser trs rapidement laffichage.
106
Dans cette section, nous dvelopperons un pointeur toupie hexadcimal pour vous prsenter
son fonctionnement (voir Figure 5.1). QSpinBox ne prend en charge que les nombres dcimaux, mais grce la drivation, il est plutt facile de lui faire accepter et afficher des valeurs
hexadcimales.
#ifndef HEXSPINBOX_H
#define HEXSPINBOX_H
#include <QSpinBox>
class QRegExpValidator;
class HexSpinBox: public QSpinBox
{
Q_OBJECT
public:
HexSpinBox(QWidget *parent = 0);
protected:
QValidator::State validate(QString &text, int &pos) const;
int valueFromText(const QString &text) const;
QString textFromValue(int value) const;
private:
QRegExpValidator *validator;
};
#endif
Chapitre 5
107
: QSpinBox(parent)
{
setRange(0, 255);
validator = new QRegExpValidator(QRegExp("[0-9A-Fa-f]{1,8}"), this);
}
Nous dfinissons la plage par dfaut avec les valeurs 0 255 (0x00 0xFF), qui est plus approprie pour un pointeur toupie hexadcimal que les valeurs par dfaut de QSpinBox allant de 0
99.
Lutilisateur peut modifier la valeur en cours dun pointeur toupie, soit en cliquant sur ses
flches vers le haut et le bas, soit en saisissant une valeur dans son diteur de lignes. Dans le
second cas, nous souhaitons restreindre lentre de lutilisateur aux nombres hexadcimaux valides. Pour ce faire, nous employons QRegExpValidator qui accepte entre un et huit caractres,
chacun deux devant appartenir lun des ensembles suivants, "0" "9," "A" "F" et "a" "f".
QValidator::State HexSpinBox::validate(QString &text, int &pos) const
{
return validator->validate(text, pos);
}
Cette fonction est appele par QSpinBox pour vrifier que le texte saisi jusqu prsent est
valide. Il y a trois possibilits : Invalid (le texte ne correspond pas lexpression rgulire),
Intermediate (le texte est une partie plausible dune valeur valide) et Acceptable (le texte
est valide). QRegExpValidator possde une fonction validate() approprie, nous retournons donc simplement le rsultat de son appel. En thorie, nous devrions renvoyer Invalid ou
Intermediate pour les valeurs qui se situent en dehors de la plage du pointeur toupie, mais
QSpinBox est assez intelligent pour dtecter cette condition sans aucune aide.
QString HexSpinBox::textFromValue(int value) const
{
return QString::number(value, 16).toUpper();
}
La fonction valueFromText() effectue une conversion inverse, dune chane en une valeur
entire. Elle est appele par QSpinBox quand lutilisateur saisit une valeur dans la zone de
lditeur du pointeur toupie et appuie sur Entre. Nous excutons la fonction QString::toInt()
108
pour essayer de convertir le texte en cours en une valeur entire, toujours en base 16. Si la
chane nest pas au format hexadcimal, ok est dfini en false et toInt() retourne 0. Ici,
nous ne sommes pas obligs denvisager cette possibilit, parce que le validateur naccepte que
la saisie de chanes hexadcimales valides. Au lieu de transmettre ladresse dune variable sans
intrt (ok), nous pourrions transmettre un pointeur nul comme premier argument de toInt().
Nous avons termin le pointeur toupie hexadcimal. La personnalisation dautres widgets Qt
suit le mme processus : choisir un widget Qt adapt, le driver et rimplmenter certaines
fonctions virtuelles pour modifier son comportement.
Driver QWidget
De nombreux widgets personnaliss sont simplement obtenus partir dune combinaison de
widgets existants, que ce soit des widgets Qt intgrs ou dautres widgets personnaliss
comme HexSpinBox. Les widgets personnaliss ainsi conus peuvent gnralement tre dvelopps dans le Qt Designer :
Si vous avez besoin dun comportement pour lequel de simples signaux et slots sont insuffisants, crivez le code ncessaire dans une classe qui hrite de QWidget et de celle gnre
par uic.
Il est vident que combiner des widgets existants peut se faire entirement dans du code.
Quelle que soit lapproche choisie, la classe en rsultant hrite directement de QWidget.
Si le widget ne possde aucun signal ni slot et quil ne rimplmente pas de fonction virtuelle, il
est mme possible de concevoir le widget simplement en combinant des widgets existants sans
sous-classe. Cest la technique que nous avons employe dans le Chapitre 1 pour crer lapplication Age, avec QWidget, QSpinBox et QSlider. Pourtant nous aurions pu tout aussi facilement
driver QWidget et crer QSpinBox et QSlider dans le constructeur de la sous-classe.
Lorsquaucun des widgets Qt ne convient une tche particulire et lorsquil nexiste aucun
moyen de combiner ou dadapter des widgets existants pour obtenir le rsultat souhait, nous
pouvons toujours crer le widget que nous dsirons. Pour ce faire, nous devons driver QWidget et rimplmenter quelques gestionnaires dvnements pour dessiner le widget et rpondre
aux clics de souris. Cette approche nous autorise une libert totale quant la dfinition et au
contrle de lapparence et du comportement de notre widget. Les widgets intgrs de Qt,
comme QLabel, QPushButton et QTableWidget, sont implments de cette manire. Sils
nexistaient pas dans Qt, il serait encore possible de les crer nous-mmes en utilisant les fonctions
publiques fournies par QWidget de faon totalement indpendante de la plate-forme.
Chapitre 5
109
Pour vous montrer comment crire un widget personnalis en se basant sur cette technique,
nous allons crer le widget IconEditor illustr en Figure 5.2. IconEditor est un widget qui
pourrait tre utilis dans un programme dditeur dicnes.
Figure 5.2
Le widget IconEditor
La classe IconEditor utilise la macro Q_PROPERTY() pour dclarer trois proprits personnalises : penColor, iconImage et zoomFactor. Chaque proprit a un type de donnes, une
fonction de "lecture" et une fonction facultative "dcriture".Par exemple, la proprit penColor est
de type QColor et peut tre lue et crite grce aux fonctions penColor() et setPenColor().
110
Quand nous utilisons le widget dans le Qt Designer, les proprits personnalises apparaissent
dans lditeur de proprits du Qt Designer sous les proprits hrites de QWidget. Ces
proprits peuvent tre de nimporte quel type pris en charge par QVariant. La macro
Q_OBJECT est ncessaire pour les classes qui dfinissent des proprits.
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void paintEvent(QPaintEvent *event);
private:
void setImagePixel(const QPoint &pos, bool opaque);
QRect pixelRect(int i, int j) const;
QColor curColor;
QImage image;
int zoom;
};
#endif
Chapitre 5
111
QRgb est simplement le typedef dun type unsigned int, et qRgb() et qRgba() sont des
fonctions en ligne qui combinent leurs arguments en une valeur entire 32 bits. Il est aussi
possible dcrire
QRgb red = 0xFFFF0000;
La fonction sizeHint() est rimplmente dans QWidget et retourne la taille idale dun
widget. Dans ce cas, nous recevons la taille de limage multiplie par le facteur de zoom, avec
un pixel supplmentaire dans chaque direction pour sadapter une grille si le facteur de zoom
est de 3 ou plus. (Nous naffichons pas de grille si le facteur de zoom est de 2 ou 1, parce
quelle ne laisserait presque pas de place pour les pixels de licne.)
112
La taille requise dun widget est utile dans la plupart des cas lorsquelle est associe aux dispositions. Les gestionnaires de disposition de Qt essaient au maximum de respecter cette taille
quand ils disposent les widgets enfants dun formulaire. Pour que IconEditor se comporte
correctement, il doit signaler une taille requise crdible.
En plus de cette taille requise, la taille des widgets suit une stratgie qui indique au systme de
disposition sils peuvent tre tirs ou rtrcis. En appelant setSizePolicy() dans le constructeur
avec les stratgies horizontale et verticale QSizePolicy::Minimum, tout gestionnaire de
disposition responsable de ce widget sait que la taille requise de ce dernier correspond vraiment sa taille minimale. En dautres termes, le widget peut tre tir si ncessaire, mais ne
doit jamais tre rtrci une taille infrieure la taille requise. Vous pouvez annuler ce
comportement dans le Qt Designer en configurant la proprit sizePolicy du widget.
La signification des diverses stratgies lies la taille est explique au Chapitre 6.
void IconEditor::setPenColor(const QColor &newColor)
{
curColor = newColor;
}
La fonction setPenColor() dfinit la couleur du crayon. La couleur sera utilise pour les
pixels que vous dessinerez.
void IconEditor::setIconImage(const QImage &newImage)
{
if (newImage!= image) {
image = newImage.convertToFormat(QImage::Format_ARGB32);
update();
updateGeometry();
}
}
La fonction setIconImage() dtermine limage modifier. Nous invoquons convertToFormat() pour obtenir une image 32 bits avec une mmoire tampon alpha si elle nest pas
dans ce format. Ailleurs dans le code, nous supposerons que les donnes de limage sont stockes
sous forme de valeurs ARGB 32 bits.
Aprs avoir configur la variable image, nous appelons QWidget::update() pour forcer le
rafrachissement de laffichage du widget avec la nouvelle image. Nous invoquons ensuite
QWidget::updateGeometry() pour informer toute disposition qui contient le widget que
la taille requise du widget a chang. La disposition sadaptera automatiquement cette
nouvelle taille.
void IconEditor::setZoomFactor(int newZoom)
{
if (newZoom < 1)
newZoom = 1;
if (newZoom!= zoom) {
zoom = newZoom;
Chapitre 5
113
update();
updateGeometry();
}
}
La fonction setZoomFactor() dfinit le facteur de zoom de limage. Pour viter une division
par zro, nous corrigeons toute valeur infrieure 1. A nouveau, nous appelons update() et
updateGeometry() pour actualiser laffichage du widget et pour informer tout gestionnaire
de disposition de la modification de la taille requise.
Les fonctions penColor(), iconImage() et zoomFactor() sont implmentes en tant que
fonctions en ligne dans le fichier den-tte.
Nous allons maintenant passer en revue le code de la fonction paintEvent(). Cette fonction
est la fonction la plus importante de IconEditor. Elle est invoque ds que le widget a besoin
dtre redessin. Limplmentation par dfaut dans QWidget na aucune consquence, le
widget reste vide.
Tout comme closeEvent(), que nous avons rencontr dans le Chapitre 3, paintEvent() est
un gestionnaire dvnements. Qt propose de nombreux autres gestionnaires dvnements,
chacun deux correspondant un type diffrent dvnement. Le Chapitre 7 aborde en dtail le
traitement des vnements.
Il existe beaucoup de situations o un vnement paint est dclench et o paintEvent()
est appel :
Quand un widget est affich pour la premire fois, le systme gnre automatiquement un
vnement paint pour obliger le widget se dessiner lui-mme.
Quand un widget est redimensionn, le systme dclenche un vnement paint.
Si le widget est masqu par une autre fentre, puis affich nouveau, un vnement paint
est dclench pour la zone qui tait masque ( moins que le systme de fentrage ait
stock la zone).
Nous avons aussi la possibilit de forcer un vnement paint en appelant QWidget::update() ou QWidget::repaint(). La diffrence entre ces deux fonctions est que repaint()
impose un rafrachissement immdiat de laffichage, alors que update() planifie simplement
un vnement paint pour le prochain traitement dvnements de Qt. (Ces deux fonctions ne
font rien si le widget nest pas visible lcran.) Si update() est invoqu plusieurs fois, Qt
compresse les vnements paint conscutifs en un seul vnement paint pour viter le
phnomne du scintillement. Dans IconEditor, nous utilisons toujours update().
Voici le code :
void IconEditor::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
if (zoom >= 3) {
painter.setPen(palette().foreground().color());
for (int i = 0; i <= image.width(); ++i)
114
painter.drawLine(zoom * i, 0,
zoom * i, zoom * image.height());
for (int j = 0; j <= image.height(); ++j)
painter.drawLine(0, zoom * j,
zoom * image.width(), zoom * j);
}
for (int i = 0; i < image.width(); ++i) {
for (int j = 0; j < image.height(); ++j) {
QRect rect = pixelRect(i, j);
if (!event->region().intersect(rect).isEmpty()) {
QColor color = QColor::fromRgba(image.pixel(i, j));
painter.fillRect(rect, color);
}
}
}
}
Nous commenons par construire un objet QPainter sur le widget. Si le facteur de zoom est
de 3 ou plus, nous dessinons des lignes horizontales et verticales qui forment une grille laide
de la fonction QPainter::drawLine().
Un appel de QPainter::drawLine() prsente la syntaxe suivante :
painter.drawLine(x1, y1, x2, y2);
o (x1, y1) est la position dune extrmit de la ligne et (x2, y2) la position de lautre
extrmit. Il existe galement une version surcharge de la fonction qui reoit deux QPoint au
lieu de quatre int.
Le pixel en haut gauche dun widget Qt se situe la position (0, 0), et le pixel en bas droite
se trouve (width() 1, height() 1). Cela ressemble au systme traditionnel de coordonnes cartsiennes, mais lenvers. Nous avons la possibilit de modifier le systme de
coordonnes de QPainter grce aux transformations, comme la translation, la mise
lchelle, la rotation et le glissement. Ces notions sont abordes au Chapitre 8 (Graphiques 2D
et 3D).
Figure 5.3
Tracer une ligne
avec QPainter
(0, 0)
(x1, y1)
(x2, y2)
(x
Chapitre 5
115
Avant dappeler drawLine() sur QPainter, nous dfinissons la couleur de la ligne au moyen
de setPen(). Nous pourrions coder une couleur, comme noir ou gris, mais il est plus judicieux
dutiliser la palette du widget.
Chaque widget est dot dune palette qui spcifie quelles couleurs doivent tre utilises selon
les situations. Par exemple, il existe une entre dans la palette pour la couleur darrire-plan
des widgets (gnralement gris clair) et une autre pour la couleur du texte sur ce fond (habituellement noir). Par dfaut, la palette dun widget adopte le modle de couleur du systme de
fentrage. En utilisant des couleurs de la palette, nous sommes srs que IconEditor respecte
les prfrences de lutilisateur.
La palette dun widget consiste en trois groupes de couleurs : active, inactive et disabled.
Vous choisirez le groupe de couleurs en fonction de ltat courant du widget :
Le groupe Active est employ pour des widgets situs dans la fentre actuellement active.
Le groupe Inactive est utilis pour les widgets des autres fentres.
Le groupe Disabled est utilis pour les widgets dsactivs dans nimporte quelle fentre.
La fonction QWidget::palette() retourne la palette du widget sous forme dobjet QPalette.
Les groupes de couleurs sont spcifis comme des numrations de type QPalette::ColorGroup.
Lorsque nous avons besoin dun pinceau ou dune couleur approprie pour dessiner, la bonne
approche consiste utiliser la palette courante, obtenue partir de QWidget::palette(), et
le rle requis, par exemple, QPalette::foreground(). Chaque fonction de rle retourne un
pinceau, qui correspond normalement ce que nous souhaitons, mais si nous navons besoin
que de la couleur, nous pouvons lextraire du pinceau, comme nous avons fait dans paintEvent(). Par dfaut, les pinceaux retourns sont adapts ltat du widget, nous ne sommes
donc pas forcs de spcifier un groupe de couleurs.
La fonction paintEvent() termine en dessinant limage elle-mme. Lappel de IconEditor::pixelRect() retourne un QRect qui dfinit la rgion redessiner. Pour une question
doptimisation simple, nous ne redessinons pas les pixels qui se trouvent en dehors de cette
rgion.
Figure 5.4
Dessiner un rectangle
avec QPainter
(0, 0)
(x, y)
h
w
(width() -1,height() -1)
116
La fonction pixelRect() retourne un QRect adapt QPainter::fillRect(). Les paramtres i et j sont les coordonnes du pixel dans QImage pas dans le widget. Si le facteur de
zoom est de 1, les deux systmes de coordonnes concident parfaitement.
Le constructeur de QRect suit la syntaxe QRect(x, y, width, height), o (x, y) est la
position du coin suprieur gauche du rectangle et width_height correspond la taille du
rectangle. Si le facteur de zoom est de 3 ou plus, nous rduisons la taille du rectangle dun pixel
horizontalement et verticalement, de sorte que le remplissage ne dborde pas sur les lignes de la
grille.
void IconEditor::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
setImagePixel(event->pos(), true);
} else if (event->button() == Qt::RightButton) {
setImagePixel(event->pos(), false);
}
}
Chapitre 5
117
La fonction setImagePixel() est appele depuis mousePressEvent() et mouseMoveEvent() pour dfinir ou effacer un pixel. Le paramtre pos correspond la position de la
souris dans le widget.
La premire tape consiste convertir la position de la souris dans les coordonnes du widget
vers les coordonnes de limage. Pour ce faire, les composants x() et y() de la position de la
souris sont diviss par le facteur de zoom. Puis nous vrifions si le point se trouve dans une
plage correcte. Ce contrle seffectue facilement en utilisant QImage::rect() et
QRect::contains(); vous vrifiez ainsi que i se situe entre 0 et image.width() 1 et que j
est entre 0 et image.height() 1.
Selon le paramtre opaque, nous dfinissons ou nous effaons le pixel dans limage. Effacer un
pixel consiste le rendre transparent. Nous devons convertir le crayon QColor en une valeur
ARGB 32 bits pour lappel de QImage::setPixel(). Nous terminons en appelant update()
avec un QRect de la zone qui doit tre redessine.
Maintenant que nous avons analys les fonctions membres, nous allons retourner lattribut
Qt::WA_StaticContents que nous avons utilis dans le constructeur. Cet attribut informe Qt
que le contenu du widget ne change pas quand le widget est redimensionn et que le contenu
reste ancr dans le coin suprieur gauche du widget. Qt se sert de ces informations pour viter
118
tout retraage inutile des zones qui sont dj affiches au moment du redimensionnement du
widget.
Normalement, quand un widget est redimensionn, Qt dclenche un vnement paint pour
toute la zone visible du widget (voir Figure 5.5). Mais si le widget est cr avec lattribut
Qt::WA_StaticContents, la rgion de lvnement paint se limite aux pixels qui ntaient
pas encore affichs auparavant. Ceci implique que si le widget est redimensionn dans une
taille plus petite, aucun vnement paint ne sera dclench.
Figure 5.5
Redimensionner
un widget
Qt::WA_StaticContents
Le widget IconEditor est maintenant termin. Grce aux informations et aux exemples des
chapitres prcdents, nous pourrions crire un code qui utilise IconEditor comme une vritable fentre, comme un widget central dans QMainWindow, comme un widget enfant dans une
disposition ou comme un widget enfant dans QScrollArea. Dans la prochaine section, nous
verrons comment lintgrer avec le Qt Designer.
Chapitre 5
119
Voil ! Le code gnr par uic contiendra hexspinbox.h au lieu de <QSpinBox> et instanciera un HexSpinBox. Dans le Qt Designer, le widget HexSpinBox sera reprsent par un
QSpinBox, ce qui nous permet de dfinir toutes les proprits dun QSpinBox (par exemple, la
plage et la valeur actuelle).
Figure 5.6
La bote de dialogue
du widget personnalis
dans le Qt Designer
Les inconvnients de lapproche de la promotion sont que les proprits spcifiques au widget
personnalis ne sont pas accessibles dans le Qt Designer et que le widget nest pas affich en
tant que tel. Ces deux problmes peuvent tre rsolus en utilisant lapproche du plug-in.
Lapproche du plug-in ncessite la cration dune bibliothque de plug-in que le Qt Designer
peut charger lexcution et utiliser pour crer des instances du widget. Le vritable widget est
ensuite employ par le Qt Designer pendant la modification du formulaire et la prvisualisation, et grce au systme de mta-objets de Qt, le Qt Designer peut obtenir dynamiquement la
liste de ses proprits. Pour voir comment cela fonctionne, nous intgrerons le widget
IconEditor de la section prcdente comme plug-in.
Nous devons dabord driver QDesignerCustomWidgetInterface et rimplmenter certaines fonctions virtuelles. Nous supposerons que le code source du plug-in se situe dans un
rpertoire appel iconeditorplugin et que le code source de IconEditor se trouve dans
un rpertoire parallle nomm iconeditor.
Voici la dfinition de classe :
#include <QDesignerCustomWidgetInterface>
class IconEditorPlugin: public QObject,
public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
IconEditorPlugin(QObject *parent = 0);
QString name() const;
QString includeFile() const;
QString group() const;
QIcon icon() const;
QString toolTip() const;
120
La fonction group() retourne le nom du groupe de widgets auquel doit appartenir ce widget
personnalis. Si le nom nest pas encore utilis, le Qt Designer crera un nouveau groupe pour
le widget.
QIcon IconEditorPlugin::icon() const
{
return QIcon(":/images/iconeditor.png");
}
La fonction icon() renvoie licne utiliser pour reprsenter le widget personnalis dans la
bote des widgets du Qt Designer. Dans notre cas, nous supposons que IconEditorPlugin
possde un fichier de ressources Qt associ avec une entre adapte pour limage de lditeur
dicnes.
Chapitre 5
121
La fonction whatsThis() retourne le texte "Whats this ?" que le Qt Designer doit afficher.
bool IconEditorPlugin::isContainer() const
{
return false;
}
La fonction isContainer() retourne true si le widget peut contenir dautres widgets ; sinon
elle retourne false. Par exemple, QFrame est un widget qui peut comporter dautres widgets.
En gnral, tout widget Qt peut renfermer dautres widgets, mais le Qt Designer ne lautorise
pas quand isContainer() renvoie false.
QWidget *IconEditorPlugin::createWidget(QWidget *parent)
{
return new IconEditor(parent);
}
La fonction create() est invoque par le Qt Designer pour crer une instance dune classe de
widget avec le parent donn.
Q_EXPORT_PLUGIN2(iconeditorplugin, IconEditorPlugin)
A la fin du fichier source qui implmente la classe de plug-in, nous devons utiliser la macro
Q_EXPORT_PLUGIN2() pour que le Qt Designer puisse avoir accs au plug-in. Le premier
argument est le nom que nous souhaitons attribuer au plug-in ; le second argument est le nom
de la classe qui limplmente.
Voici le code dun fichier .pro permettant de gnrer le plug-in :
TEMPLATE
CONFIG
HEADERS
SOURCES
RESOURCES
= lib
+= designer plugin release
= ../iconeditor/iconeditor.h \
iconeditorplugin.h
= ../iconeditor/iconeditor.cpp \
iconeditorplugin.cpp
= iconeditorplugin.qrc
122
DESTDIR
= $(QTDIR)/plugins/designer
Chapitre 5
123
Figure 5.7
Zoomer sur le
widget Plotter
Le widget Plotter peut enregistrer les donnes de nombreuses courbes. Il assure aussi la
maintenance dune pile dobjets PlotSettings, chacun deux correspondant un niveau
particulier de zoom.
Analysons dsormais la classe, en commenant par plotter.h:
#ifndef PLOTTER_H
#define PLOTTER_H
#include
#include
#include
#include
<QMap>
<QPixmap>
<QVector>
<QWidget>
class QToolButton;
class PlotSettings;
class Plotter: public QWidget
{
Q_OBJECT
public:
Plotter(QWidget *parent = 0);
void setPlotSettings(const PlotSettings &settings);
void setCurveData(int id, const QVector<QPointF> &data);
void clearCurve(int id);
QSize minimumSizeHint() const;
QSize sizeHint() const;
public slots:
void zoomIn();
void zoomOut();
Nous commenons par inclure les fichiers den-tte des classes Qt utilises dans len-tte du
fichier du traceur (plotter) puis nous dclarons les classes dsignes par des pointeurs ou des
rfrences dans len-tte.
124
Dans la classe Plotter, nous fournissons trois fonctions publiques pour configurer le trac et
deux slots publics pour faire des zooms avant et arrire. Nous rimplmentons aussi minimumSizeHint() et sizeHint() dans QWidget. Nous enregistrons les points dune courbe sous
forme de QVector<QPointF>, o QPointF est la version virgule flottante de QPoint.
protected:
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void wheelEvent(QWheelEvent *event);
Dans la section protge de la classe, nous dclarons tous les gestionnaires dvnements de
QWidget que nous dsirons rimplmenter.
private:
void updateRubberBandRegion();
void refreshPixmap();
void drawGrid(QPainter *painter);
void drawCurves(QPainter *painter);
enum { Margin = 50 };
QToolButton *zoomInButton;
QToolButton *zoomOutButton;
QMap<int, QVector<QPointF> > curveMap;
QVector<PlotSettings> zoomStack;
int curZoom;
bool rubberBandIsShown;
QRect rubberBandRect;
QPixmap pixmap;
};
Dans la section prive de la classe, nous dclarons quelques fonctions pour dessiner le widget,
une constante et quelques variables membres. La constante Margin sert introduire un peu
despace autour du graphique.
Parmi les variables membres, on compte un pixmap de type QPixmap. Cette variable conserve
une copie du rendu de tout le widget, identique celui affich lcran. Le trac est toujours
dessin sur ce pixmap dabord hors champ ; puis le pixmap est copi dans le widget.
class PlotSettings
{
public:
PlotSettings();
void scroll(int dx, int dy);
void adjust();
double spanX() const { return maxX - minX; }
Chapitre 5
125
La classe PlotSettings spcifie la plage des axes x et y et le nombre de graduations pour ces
axes. La Figure 5.8 montre la correspondance entre un objet PlotSettings et un widget
Plotter.
Par convention, numXTicks et numYTicks ont une unit de moins ; si numXTicks a la valeur 5,
Plotter dessinera 6 graduations sur laxe x. Cela simplifie les calculs par la suite.
maxY
numYTicks
Figure 5.8
Les variables membres
de PlotSettings
numXTicks
minY
minX
maxX
Nous incluons les fichiers den-ttes prvus et nous importons tous les symboles de lespace de
noms std dans lespace de noms global. Ceci nous permet daccder aux fonctions dclares
dans <cmath> sans les prfixer avec std:: (par exemple, floor() au lieu de std::floor()).
Plotter::Plotter(QWidget *parent)
: QWidget(parent)
{
setBackgroundRole(QPalette::Dark);
setAutoFillBackground(true);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
126
setFocusPolicy(Qt::StrongFocus);
rubberBandIsShown = false;
zoomInButton = new QToolButton(this);
zoomInButton->setIcon(QIcon(":/images/zoomin.png"));
zoomInButton->adjustSize();
connect(zoomInButton, SIGNAL(clicked()), this, SLOT(zoomIn()));
zoomOutButton = new QToolButton(this);
zoomOutButton->setIcon(QIcon(":/images/zoomout.png"));
zoomOutButton->adjustSize();
connect(zoomOutButton, SIGNAL(clicked()), this, SLOT(zoomOut()));
setPlotSettings(PlotSettings());
}
Chapitre 5
127
Toujours dans le constructeur, nous crons deux QToolButton, chacun avec une icne. Ces
boutons permettent lutilisateur de faire des zooms avant et arrire. Les icnes du bouton sont
stockes dans un fichier de ressources, donc toute application qui utilise le widget Plotter
aura besoin de cette entre dans son fichier .pro:
RESOURCES = plotter.qrc
Le fichier de ressources ressemble celui que nous avons utilis pour lapplication Spreadsheet :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>images/zoomin.png</file>
<file>images/zoomout.png</file>
</qresource>
</RCC>
Les appels de adjustSize() sur les boutons dfinissent leurs tailles de sorte quelles correspondent la taille requise. Les boutons ne font pas partie dune disposition ; nous les positionnerons manuellement dans lvnement resize de Plotter. Vu que nous ne nous servons pas
des dispositions, nous devons spcifier explicitement le parent des boutons en le transmettant
au constructeur de QPushButton.
Lappel de setPlotSettings() la fin termine linitialisation.
void Plotter::setPlotSettings(const PlotSettings &settings)
{
zoomStack.clear();
zoomStack.append(settings);
curZoom = 0;
zoomInButton->hide();
zoomOutButton->hide();
refreshPixmap();
}
128
Mais quand nous appelons explicitement hide() sur un widget enfant, il est masqu jusqu ce
nous appelions nouveau show() sur ce widget.)
Lappel de refreshPixmap() est ncessaire pour mettre jour laffichage. Normalement,
nous invoquerions update(), mais dans ce cas, nous agissons lgrement diffremment parce
que nous voulons conserver un QPixmap toujours mis jour. Aprs avoir rgnr le pixmap,
refreshPixmap() appelle update() pour copier le pixmap dans le widget.
void Plotter::zoomOut()
{
if (curZoom > 0) {
--curZoom;
zoomOutButton->setEnabled(curZoom > 0);
zoomInButton->setEnabled(true);
zoomInButton->show();
refreshPixmap();
}
}
Le slot zoomOut() fait un zoom arrire si vous avez dj zoom sur le graphique. Il dcrmente le niveau actuel de zoom et active le bouton Zoom Out selon quil est encore possible de
faire un zoom arrire ou pas. Le bouton Zoom In est activ et affich, et laffichage est mis
jour avec un appel de refreshPixmap().
void Plotter::zoomIn()
{
if (curZoom < zoomStack.count() - 1) {
++curZoom;
zoomInButton->setEnabled(curZoom < zoomStack.count() - 1);
zoomOutButton->setEnabled(true);
zoomOutButton->show();
refreshPixmap();
}
}
Chapitre 5
129
La fonction setCurveData() dfinit les donnes de courbe pour un ID de courbe donn. Sil
existe dj une courbe portant le mme ID dans curveMap, elle est remplace par les nouvelles
donnes de courbe ; sinon, la nouvelle courbe est simplement insre. La variable membre
curveMap est de type QMap<int,QVector<QPointF>>.
void Plotter::clearCurve(int id)
{
curveMap.remove(id);
refreshPixmap();
}
La fonction minimumSizeHint() est similaire sizeHint(); tout comme sizeHint() spcifie la taille idale dun widget, minimumSizeHint() spcifie la taille minimale idale dun widget.
Une disposition ne redimensionne jamais un widget en dessous de sa taille requise minimale.
La valeur que nous retournons est 300 _ 200 (vu que Margin est gal 50) pour laisser une
marge des quatre cts et un peu despace pour le trac. En dessous de cette taille, le trac
serait trop petit pour tre utile.
QSize Plotter::sizeHint() const
{
return QSize(12 * Margin, 8 * Margin);
}
Dans sizeHint nous retournons une taille "idale" proportionnelle la constante Margin et
avec le mme format dimage de 3:2 que nous avons utilis pour minimumSizeHint().
Ceci termine lanalyse des slots et des fonctions publiques de Plotter. Etudions prsent les
gestionnaires dvnements protgs.
void Plotter::paintEvent(QPaintEvent * /* event */)
{
QStylePainter painter(this);
painter.drawPixmap(0, 0, pixmap);
if (rubberBandIsShown) {
painter.setPen(palette().light().color());
painter.drawRect(rubberBandRect.normalized()
.adjusted(0, 0, -1, -1));
}
if (hasFocus()) {
QStyleOptionFocusRect option;
option.initFrom(this);
option.backgroundColor = palette().dark().color();
130
painter.drawPrimitive(QStyle::PE_FrameFocusRect, option);
}
}
Normalement, cest dans paintEvent() que nous effectuons toutes les oprations de dessin.
Cependant, dans notre exemple, nous avons dessin tout le trac auparavant dans refreshPixmap(), nous avons donc la possibilit dafficher tout le trac simplement en copiant le
pixmap dans le widget la position (0, 0).
Si le rectangle de slection est visible, nous le dessinons au-dessus du trac. Nous utilisons le
composant "light" du groupe de couleurs actuel du widget comme couleur du crayon pour
garantir un bon contraste avec larrire-plan "dark".Notez que nous dessinons directement sur
le widget, nous ne touchons donc pas au pixmap hors champ. Utiliser QRect::normalized()
vous assure que le rectangle de slection prsente une largeur et une hauteur positives (en
changeant les coordonnes si ncessaire), et adjusted() rduit la taille du rectangle dun
pixel pour tenir compte de son contour dun pixel.
Si le Plotter est activ, un rectangle "de focus" est dessin au moyen de la fonction drawPrimitive() correspondant au style de widget, avec QStyle::PE_FrameFocusRect comme
premier argument et QStyleOptionFocusRect comme second argument. Les options graphiques du rectangle de focus sont hrites du widget Plotter (par lappel de initFrom()). La
couleur darrire-plan doit tre spcifie explicitement.
Si vous voulez dessiner en utilisant le style actuel, vous pouvez appeler directement une fonction
QStyle, par exemple,
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &painter,
this);
ou utiliser un QStylePainter au lieu dun QPainter normal, comme nous avons procd
dans Plotter. Vous dessinez ainsi plus confortablement.
La fonction QWidget::style() retourne le style qui doit tre utilis pour dessiner le widget.
Dans Qt, le style de widget est une sous-classe de QStyle. Les styles intgrs englobent QWindowsStyle, QWindowsXPStyle, QMotifStyle, QCDEStyle, QMacStyle et QPlastiqueStyle.
Chacun de ces styles rimplmente les fonctions virtuelles dans QStyle afin dadapter le
dessin la plate-forme pour laquelle le style est mul. La fonction drawPrimitive() de
QStylePainter appelle la fonction QStyle du mme nom, qui peut tre employe pour dessiner
des "lments primitifs" comme les panneaux, les boutons et les rectangles de focus. Le style de
widget est gnralement le mme pour tous les widgets dune application (QApplication::style()), mais vous pouvez ladapter au cas par cas laide de QWidget::setStyle().
En drivant QStyle, il est possible de dfinir un style personnalis. Vous pouvez ainsi attribuer
un aspect trs particulier une application ou une suite dapplications. Alors quil est habituellement recommand dadopter laspect et lapparence natifs de la plate-forme cible, Qt offre
une grande flexibilit si vous souhaitez intervenir dans ce domaine.
Les widgets intgrs de Qt se basent presque exclusivement sur QStyle pour se dessiner. Cest
pourquoi ils ressemblent aux widgets natifs sur toutes les plates-formes prises en charge par Qt.
Chapitre 5
131
Les widgets personnaliss peuvent adopter le style courant soit en utilisant QStyle pour se
tracer eux-mmes, soit en employant les widgets Qt intgrs comme widgets enfants. Sagissant de Plotter, nous utilisons une combinaison des deux approches : le rectangle de focus
est dessin avec QStyle (via un QStylePainter) et les boutons Zoom In et Zoom Out sont
des widgets Qt intgrs.
void Plotter::resizeEvent(QResizeEvent * /* event */)
{
int x = width() - (zoomInButton->width()
+ zoomOutButton->width() + 10);
zoomInButton->move(x, 5);
zoomOutButton->move(x + zoomInButton->width() + 5, 5);
refreshPixmap();
}
Quand le widget Plotter est redimensionn, Qt dclenche un vnement "resize". Ici, nous
implmentons resizeEvent() pour placer les boutons Zoom In et Zoom Out en haut droite
du widget Plotter.
Nous dplaons les boutons Zoom In et Zoom Out pour quils soient cte cte, spars par un
espace de 5 pixels et dcals de 5 pixels par rapport aux bords suprieur et droit du widget parent.
Si nous avions voulu que les boutons restent ancrs dans le coin suprieur gauche, dont les
coordonnes sont (0, 0), nous les aurions simplement placs cet endroit dans le constructeur
de Plotter. Nanmoins, nous souhaitons assurer le suivi du coin suprieur droit, dont les
coordonnes dpendent de la taille du widget. Cest pour cette raison quil est ncessaire de
rimplmenter resizeEvent() et dy dfinir la position des boutons.
Nous navons pas configur les positions des boutons dans le constructeur de Plotter. Ce
nest pas un problme parce que Qt dclenche toujours un vnement resize avant dafficher
un widget pour la premire fois.
Plutt que de rimplmenter resizeEvent() et de disposer les widgets enfants manuellement, nous aurions pu faire appel un gestionnaire de disposition (par exemple, QGridLayout). Lutilisation dune disposition aurait t un peu plus complique et aurait
consomm davantage de ressources, mais les dispositions de droite gauche aurait t mieux
gres, notamment pour des langues comme larabe et lhbreu.
Nous terminons en invoquant refreshPixmap() pour redessiner le pixmap sa nouvelle taille.
void Plotter::mousePressEvent(QMouseEvent *event)
{
QRect rect(Margin, Margin,
width() - 2 * Margin, height() - 2 * Margin);
if (event->button() == Qt::LeftButton) {
if (rect.contains(event->pos())) {
rubberBandIsShown = true;
rubberBandRect.setTopLeft(event->pos());
rubberBandRect.setBottomRight(event->pos());
updateRubberBandRegion();
132
setCursor(Qt::CrossCursor);
}
}
}
Quand lutilisateur appuie sur le bouton gauche de la souris, nous commenons afficher un
rectangle de slection. Ceci implique de dfinir rubberBandIsShown en true, dinitialiser la
variable membre rubberBandRect la position actuelle du pointeur de la souris, de planifier
un vnement paint pour tracer le rectangle et de changer le pointeur de la souris pour afficher
un pointeur rticule.
La variable rubberBandRect est de type QRect. Un QRect peut tre dfini soit sous forme
dun quadruple (x, y, width, height) o (x, y) est la position du coin suprieur gauche
et width _ height correspond la taille du rectangle soit comme une paire de coordonnes
suprieur-gauche et infrieur-droit. Dans ce cas, nous avons employ la reprsentation avec
des paires de coordonnes. Nous dfinissons le point o lutilisateur a cliqu la fois comme
tant le coin suprieur gauche et le coin infrieur droit. Puis nous appelons updateRubberBandRegion() pour forcer le rafrachissement de laffichage de la (toute petite) zone couverte
par le rectangle de slection.
Qt propose deux mcanismes pour contrler la forme du pointeur de la souris :
QApplication::setOverrideCursor() dfinit la forme du pointeur pour toute lapplication, ignorant les pointeurs configurs par chaque widget jusqu ce que restoreOverrideCursor() soit invoque.
Dans le Chapitre 4, nous avons appel QApplication::setOverrideCursor() avec
Qt::WaitCursor pour changer le pointeur de lapplication en sablier.
Quand lutilisateur dplace le pointeur de la souris alors quil maintient le bouton gauche
enfonc, nous appelons dabord updateRubberBandRegion() pour planifier un vnement
paint afin de redessiner la zone o se trouvait le rectangle de slection, puis nous recalculons
rubberBandRect pour tenir compte du dplacement de la souris, et enfin nous invoquons
updateRubberBandRegion() une deuxime fois pour retracer la zone vers laquelle sest
Chapitre 5
133
+
+
-
dx
dx
dy
dy
*
*
*
*
rect.left();
rect.right();
rect.bottom();
rect.top();
zoomStack.resize(curZoom + 1);
zoomStack.append(settings);
zoomIn();
}
}
134
(0, 0)
(94, 73)
6
4
0
0
2.0
10
3.2
135
Figure 5.11
Ajuster les coordonnes
du traceur et zoomer sur
le rectangle de slection
6.5
68
6.8
2.4
10
10
10
7.0
10
7.0
6
4
3.0
2
0
5
4
3
10
Puis nous zoomons. Pour zoomer, nous devons appuyer sur le nouveau PlotSettings que
nous venons de calculer en haut de la pile de zoom et nous appelons zoomIn() qui se chargera
de la tche.
void Plotter::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Plus:
zoomIn();
break;
case Qt::Key_Minus:
zoomOut();
break;
case Qt::Key_Left:
zoomStack[curZoom].scroll(-1, 0);
refreshPixmap();
break;
case Qt::Key_Right:
zoomStack[curZoom].scroll(+1, 0);
refreshPixmap();
break;
case Qt::Key_Down:
Chapitre 5
135
zoomStack[curZoom].scroll(0, -1);
refreshPixmap();
break;
case Qt::Key_Up:
zoomStack[curZoom].scroll(0, +1);
refreshPixmap();
break;
default:
QWidget::keyPressEvent(event);
}
}
Quand lutilisateur appuie sur une touche et que le widget Plotter est actif, la fonction
keyPressEvent() est invoque. Nous la rimplmentons ici pour rpondre six touches : +, ,
Haut, Bas, Gauche et Droite. Si lutilisateur a appuy sur une touche que nous ne grons pas,
nous appelons limplmentation de la classe de base. Pour une question de simplicit, nous
ignorons les touches de modification Maj, Ctrl et Alt, disponibles via QKeyEvent::modifiers().
void Plotter::wheelEvent(QWheelEvent *event)
{
int numDegrees = event->delta() / 8;
int numTicks = numDegrees / 15;
if (event->orientation() == Qt::Horizontal) {
zoomStack[curZoom].scroll(numTicks, 0);
} else {
zoomStack[curZoom].scroll(0, numTicks);
}
refreshPixmap();
}
Les vnements wheel se dclenchent quand la molette de la souris est actionne. La majorit
des souris ne proposent quune molette verticale, mais certaines sont quipes dune molette
horizontale. Qt prend en charge les deux types de molette. Les vnements wheel sont transmis au widget actif. La fonction delta() retourne la distance parcourue par la molette en
huitimes de degr. Les souris proposent habituellement une plage de 15 degrs. Dans notre
exemple, nous faisons dfiler le nombre de graduations demandes en modifiant llment le
plus haut dans la pile de zoom et nous mettons jour laffichage au moyen de refreshPixmap().
Nous utilisons la molette de la souris le plus souvent pour faire drouler une barre de dfilement.
Quand nous employons QScrollArea (trait dans le Chapitre 6) pour proposer des barres de
dfilement, QScrollArea gre automatiquement les vnements lis la molette de la souris,
nous navons donc pas rimplmenter wheelEvent() nous-mmes.
Ceci achve limplmentation des gestionnaires dvnements. Passons maintenant en revue
les fonctions prives.
void Plotter::updateRubberBandRegion()
{
136
La fonction updateRubberBand() est appele depuis mousePressEvent(), mouseMoveEvent() et mouseReleaseEvent() pour effacer ou redessiner le rectangle de slection. Elle
est constitue de quatre appels de update() qui planifient un vnement paint pour les
quatre petites zones rectangulaires couvertes par le rectangle de slection (deux lignes verticales et deux lignes horizontales). Qt propose la classe QRubberBand pour dessiner des
rectangles de slection, mais dans ce cas, lcriture du code permet de mieux contrler lopration.
void Plotter::refreshPixmap()
{
pixmap = QPixmap(size());
pixmap.fill(this, 0, 0);
QPainter painter(&pixmap);
painter.initFrom(this);
drawGrid(&painter);
drawCurves(&painter);
update();
}
La fonction refreshPixmap() redessine le trac sur le pixmap hors champ et met laffichage
jour. Nous redimensionnons le pixmap de sorte quil ait la mme taille que le widget et nous
le remplissons avec la couleur deffacement du widget. Cette couleur correspond au composant
"dark" de la palette en raison de lappel de setBackgroundRole() dans le constructeur de
Plotter. Si larrire-plan nest pas uni, QPixmap::fill() doit connatre la position du
pixmap dans le widget pour aligner correctement le motif de couleur. Dans notre cas, le pixmap
correspond la totalit du widget, nous spcifions donc la position (0, 0).
Nous crons ensuite un QPainter pour dessiner sur le pixmap. Lappel de initFrom() dfinit
le crayon, larrire-plan et la police pour quils soient identiques ceux du widget Plotter.
Puis nous invoquons drawGrid() et drawCurves() pour raliser le dessin. Nous appelons
enfin update() pour planifier un vnement paint pour la totalit du widget. Le pixmap est
copi dans le widget dans la fonction paintEvent().
void Plotter::drawGrid(QPainter *painter)
{
QRect rect(Margin, Margin,
width() - 2 * Margin, height() - 2 * Margin);
if (!rect.isValid())
return;
PlotSettings settings = zoomStack[curZoom];
QPen quiteDark = palette().dark().color().light();
QPen light = palette().light().color();
Chapitre 5
137
La fonction drawGrid() dessine la grille derrire les courbes et les axes. La zone dans
laquelle nous dessinons la grille est spcifie par rect. Si le widget nest pas assez grand pour
sadapter au graphique, nous retournons immdiatement.
La premire boucle for trace les lignes verticales de la grille et les graduations sur laxe x. La
seconde boucle for trace les lignes horizontales de la grille et les graduations sur laxe y. A la fin,
nous dessinons un rectangle le long des marges. La fonction drawText() dessine les numros
correspondants aux graduations sur les deux axes.
Les appels de drawText() ont la syntaxe suivante :
painter->drawText(x, y, width, height, alignment, text);
138
if (!rect.isValid())
return;
painter->setClipRect(rect.adjusted(+1, +1, -1, -1));
QMapIterator<int, QVector<QPointF> > i(curveMap);
while (i.hasNext()) {
i.next();
int id = i.key();
const QVector<QPointF> &data = i.value();
QPolygonF polyline(data.count());
for (int j = 0; j < data.count(); ++j) {
double dx = data[j].x() - settings.minX;
double dy = data[j].y() - settings.minY;
double x = rect.left() + (dx * (rect.width() - 1)
/ settings.spanX());
double y = rect.bottom() - (dy * (rect.height() - 1)
/ settings.spanY());
polyline[j] = QPointF(x, y);
}
painter->setPen(colorForIds[uint(id) % 6]);
painter->drawPolyline(polyline);
}
}
La fonction drawCurves() dessine les courbes au-dessus de la grille. Nous commenons par
appeler setClipRect() pour dfinir la zone daction de QPainter comme gale au rectangle
qui contient les courbes (except les marges et le cadre autour du graphique). QPainter ignorera ensuite les oprations de dessin sur les pixels situs en dehors de cette zone.
Puis, nous parcourons toutes les courbes laide dun itrateur de style Java, et pour chacune
delles, nous parcourons les QPointF dont elle est constitue. La fonction key() donne lID
de la courbe et la fonction value() donne les donnes de courbe correspondantes comme un
QVector<QPointF>. La boucle interne for convertit chaque QPointF pour transformer les
coordonnes du traceur en coordonnes du widget et les stocke dans la variable polyline.
Une fois que nous avons converti tous les points dune courbe en coordonnes du widget, nous
dterminons la couleur de crayon pour la courbe (en utilisant un des ensembles de couleurs
prdfinies) et nous appelons drawPolyline() pour tracer une ligne qui passe par tous les
points de cette dernire.
Voici la classe Plotter termine. Tout ce qui reste, ce sont quelques fonctions dans PlotSettings.
PlotSettings::PlotSettings()
{
minX = 0.0;
maxX = 10.0;
numXTicks = 5;
Chapitre 5
139
minY = 0.0;
maxY = 10.0;
numYTicks = 5;
}
La fonction scroll() incrmente (ou dcrmente) minX, maxX, minY et maxY de la valeur de
lintervalle entre deux graduations multiplie par un nombre donn. Cette fonction est utilise
pour implmenter le dfilement dans Plotter::keyPressEvent().
void PlotSettings::adjust()
{
adjustAxis(minX, maxX, numXTicks);
adjustAxis(minY, maxY, numYTicks);
}
La fonction adjust() est invoque dans mouseReleaseEvent() pour arrondir les valeurs
minX, maxX, minY et maxY en valeurs "conviviales" et pour dterminer le bon nombre de
graduations pour chaque axe. La fonction prive adjustAxis() sexcute sur un axe la fois.
void PlotSettings::adjustAxis(double &min, double &max,
int &numTicks)
{
const int MinTicks = 4;
double grossStep = (max - min) / MinTicks;
double step = pow(10.0, floor(log10(grossStep)));
if (5 * step < grossStep) {
step *= 5;
} else if (2 * step < grossStep) {
step *= 2;
}
numTicks = int(ceil(max / step) - floor(min / step));
if (numTicks < MinTicks)
numTicks = MinTicks;
min = floor(min / step) * step;
max = ceil(max / step) * step;
}
140
II
Qt : niveau
intermdiaire
6
Graphiques 2D et 3D
Glisser-dposer
10
11
Classes conteneur
12
Entres/Sorties
13
14
Gestion de rseau
15
XML
16
Aide en ligne
6
Gestion des dispositions
Au sommaire de ce chapitre
Disposer des widgets sur un formulaire
Dispositions empiles
Sparateurs
Zones droulantes
Widgets et barres doutils ancrables
MDI (Multiple Document Interface)
Chaque widget plac dans un formulaire doit se voir attribuer une taille et une position
appropries. Qt propose plusieurs classes qui disposent les widgets dans un formulaire :
QHBoxLayout, QVBoxLayout, QGridLayout et QStackLayout. Ces classes sont si
pratiques et faciles utiliser que presque tous les dveloppeurs Qt sen servent, soit
directement dans du code source, soit par le biais du Qt Designer.
Il existe une autre raison demployer les classes de disposition (layout) de Qt : elles
garantissent que les formulaires sadaptent automatiquement aux diverses polices,
langues et plates-formes. Si lutilisateur modifie les paramtres de police du systme,
les formulaires de lapplication rpondront immdiatement en se redimensionnant euxmmes si ncessaire. Si vous traduisez linterface utilisateur de lapplication en dautres
langues, les classes de disposition prennent en compte le contenu traduit des widgets
pour viter toute coupure de texte.
144
QSplitter, QScrollArea, QMainWindow et QWorkspace sont dautres classes qui se chargent de grer la disposition. Le point commun de ces classes cest quelles procurent une
disposition trs flexible sur laquelle lutilisateur peut agir. Par exemple, QSplitter propose un
sparateur que lutilisateur peut faire glisser pour redimensionner les widgets, et QWorkspace
prend en charge MDI (multiple document interface), un moyen dafficher plusieurs documents
simultanment dans la fentre principale dune application. Etant donn quelles sont souvent
utilises comme des alternatives aux classes de disposition, elles sont aussi prsentes dans ce
chapitre.
Le positionnement absolu est le moyen le plus rudimentaire de disposer des widgets. Il suffit
dassigner dans du code des tailles et des positions aux widgets enfants du formulaire et une
taille fixe au formulaire. Voici quoi ressemble le constructeur de FindFileDialog avec le
positionnement absolu :
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 200, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 200, 25);
Chapitre 6
145
146
Figure 6.2
Redimensionner une bote de dialogue redimensionnable
Chapitre 6
147
: QDialog(parent)
{
...
QGridLayout *leftLayout = new QGridLayout;
leftLayout->addWidget(namedLabel, 0, 0);
leftLayout->addWidget(namedLineEdit, 0, 1);
leftLayout->addWidget(lookInLabel, 1, 0);
leftLayout->addWidget(lookInLineEdit, 1, 1);
leftLayout->addWidget(subfoldersCheckBox, 2, 0, 1, 2);
leftLayout->addWidget(tableWidget, 3, 0, 1, 2);
leftLayout->addWidget(messageLabel, 4, 0, 1, 2);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(stopButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();
rightLayout->addWidget(helpButton);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find Files or Folders"));
}
148
Figure 6.3
La disposition
de la bote
de dialogue
Find File
Titre de fentre
QLabel
QLineEdit
QPushButton
QLabel
QLineEdit
QPushButton
mainLayout
QCheckBox
QPushButton
QTreeWidget
QLabel
QPushButton
leftLayout
rightLayout
Dans ce cas, widget est le widget enfant insrer dans la disposition, (row, column) est la
cellule en haut gauche occupe par le widget, rowSpan correspond au nombre de lignes
occupes par le widget et columnSpan est le nombre de colonnes occupes par le widget.
Sils sont omis, les paramtres rowSpan et columnSpan ont la valeur 1 par dfaut.
Lappel de addStretch() ordonne au gestionnaire de disposition dutiliser lespace cet
endroit dans la disposition. En ajoutant un lment dtirement, nous avons demand au
gestionnaire de disposition de placer tout espace excdentaire entre les boutons Close et Help.
Dans le Qt Designer, nous pouvons aboutir au mme effet en insrant un lment despacement.
Les lments despacement apparaissent dans le Qt Designer sous forme de "ressorts" bleus.
Utiliser des gestionnaires de disposition prsente des avantages supplmentaires par rapport
ceux dcrits jusque l. Si nous ajoutons ou supprimons un widget dans une disposition, celle-ci
sadaptera automatiquement la nouvelle situation. Il en va de mme si nous invoquons hide()
ou show() sur un widget enfant. Si la taille requise dun widget enfant change, la disposition
sera automatiquement corrige, en tenant compte de cette nouvelle taille. En outre, les gestionnaires de disposition dfinissent automatiquement une taille minimale pour le formulaire, en
fonction des tailles minimales et des tailles requises des widgets enfants de ce dernier.
Dans les exemples donns jusqu prsent, nous avons simplement plac les widgets dans des
dispositions et utilis des lments despacement pour combler tout espace excdentaire. Dans
certains cas, ce nest pas suffisant pour que la disposition ressemble exactement ce que nous
voulons. Nous pouvons donc ajuster la disposition en changeant les stratgies de taille (rgles
auxquelles la taille est soumise, voir chapitre prcdent) et les tailles requises des widgets
disposer.
Grce la stratgie de taille dun widget, le systme de disposition sait comment ce widget
doit tre tir ou rtrci. Qt propose des stratgies par dfaut raisonnables pour tous ses
widgets intgrs. Cependant, puisquil nexiste pas de valeur par dfaut qui pourrait tenir
compte de toutes les dispositions possibles, il est courant que les dveloppeurs modifient les
Chapitre 6
149
taille requise
Fixed
SomeText
Minimum
SomeText
SomeText
Maximum
Som
SomeText
Preferred
Som
SomeText
SomeText
Expanding
Som
SomeText
SomeText
150
Dispositions empiles
La classe QStackedLayout dispose un ensemble de widgets enfants, ou "pages," et nen affiche quun seul la fois, en masquant les autres lutilisateur. QStackedLayout est invisible
en soi et lutilisateur na aucun moyen de changer une page. Les petites flches et le cadre gris
fonc dans la Figure 6.5 sont fournis par le Qt Designer pour faciliter la conception avec la
disposition. Pour des questions pratiques, Qt inclut galement un QStackedWidget qui
propose un QWidget avec un QStackedLayout intgr.
Figure 6.5
QStackedLayout
Les pages sont numrotes en commenant 0. Pour afficher un widget enfant spcifique, nous
pouvons appeler setCurrentIndex() avec un numro de page. Le numro de page dun
widget enfant est disponible grce indexOf().
Figure 6.6
Deux pages
de la bote
de dialogue
Preferences
Chapitre 6
151
La bote de dialogue Preferences illustre en Figure 6.6 est un exemple qui utilise QStackedLayout. Elle est constitue dun QListWidget gauche et dun QStackedLayout droite.
Chaque lment dans QListWidget correspond une page diffrente dans QStackedLayout.
Voici le code du constructeur de la bote de dialogue :
PreferenceDialog::PreferenceDialog(QWidget *parent)
: QDialog(parent)
{
...
listWidget = new QListWidget;
listWidget->addItem(tr("Appearance"));
listWidget->addItem(tr("Web Browser"));
listWidget->addItem(tr("Mail & News"));
listWidget->addItem(tr("Advanced"));
stackedLayout = new QStackedLayout;
stackedLayout->addWidget(appearancePage);
stackedLayout->addWidget(webBrowserPage);
stackedLayout->addWidget(mailAndNewsPage);
stackedLayout->addWidget(advancedPage);
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
...
listWidget->setCurrentRow(0);
}
Nous crons un QListWidget et nous lalimentons avec les noms des pages. Nous crons
ensuite un QStackedLayout et nous invoquons addWidget() pour chaque page. Nous
connectons le signal currentRowChanged(int) du widget liste setCurrentIndex(int)
de la disposition empile pour implmenter le changement de page, puis nous appelons
setCurrentRow() sur le widget liste la fin du constructeur pour commencer la page 0.
Ce genre de formulaire est aussi trs facile crer avec le Qt Designer :
1. crez un nouveau formulaire en vous basant sur les modles "Dialog" ou "Widget" ;
2. ajoutez un QListWidget et un QStackedWidget au formulaire ;
3. remplissez chaque page avec des widgets enfants et des dispositions ;
(Pour crer une nouvelle page, cliquez du bouton droit et slectionnez Insert Page ; pour
changer de page, cliquez sur la petite flche gauche ou droite situe en haut droite du
QStackedWidget)
4. disposez les widgets cte cte grce une disposition horizontale ;
5. connectez le signal currentRowChanged(int) du widget liste au slot setCurrentIndex(int) du widget empil ;
6. dfinissez la valeur de la proprit currentRow du widget liste en 0.
Etant donn que nous avons implment le changement de page en utilisant des signaux et des
slots prdfinis, la bote de dialogue prsentera le bon comportement quand elle sera prvisualise dans le Qt Designer.
152
Sparateurs
QSplitter est un widget qui comporte dautres widgets. Les widgets dans un sparateur sont
spars par des poignes. Les utilisateurs peuvent changer les tailles des widgets enfants du
sparateur en faisant glisser ces poignes. Les sparateurs peuvent souvent tre utiliss comme
une alternative aux gestionnaires de disposition, pour accorder davantage de contrle lutilisateur.
Figure 6.7
Lapplication
Splitter
Les widgets enfants dun QSplitter sont automatiquement placs cte cte (ou un endessous de lautre) dans lordre dans lequel ils sont crs, avec des barres de sparation entre
les widgets adjacents. Voici le code permettant de crer la fentre illustre en Figure 6.7 :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextEdit *editor1 = new QTextEdit;
QTextEdit *editor2 = new QTextEdit;
QTextEdit *editor3 = new QTextEdit;
QSplitter splitter(Qt::Horizontal);
splitter.addWidget(editor1);
splitter.addWidget(editor2);
splitter.addWidget(editor3);
...
splitter.show();
return app.exec();
}
Lexemple est constitu de trois QTextEdit disposs horizontalement par un widget QSplitter.
Contrairement aux gestionnaires de disposition qui se contentent dorganiser les widgets
enfants dun formulaire et ne proposent aucune reprsentation visuelle, QSplitter hrite de
QWidget et peut tre utilis comme nimporte quel autre widget.
Vous obtenez des dispositions complexes en imbriquant des QSplitter horizontaux et verticaux. Par exemple, lapplication Mail Client prsente en Figure 6.9 consiste en un QSplitter
horizontal qui contient un QSplitter vertical sur sa droite.
Chapitre 6
Figure 6.8
Les widgets
de lapplication
Splitter
153
Titre de fentre
QSplitter
QTextEdit
QTextEdit
QTextEdit
Figure 6.9
Lapplication
Mail Client
sous Mac OS X
Aprs avoir cr les trois widgets que nous voulons afficher, nous crons un sparateur vertical, rightSplitter, et nous ajoutons les deux widgets dont nous avons besoin sur la droite.
154
Nous crons ensuite un sparateur horizontal, mainSplitter, et nous ajoutons le widget que
nous voulons quil affiche sur la gauche. Nous crons aussi rightSplitter dont les widgets
doivent safficher droite. mainSplitter devient le widget central de QMainWindow.
Quand lutilisateur redimensionne une fentre, QSplitter distribue normalement lespace de
sorte que les tailles relatives des widgets enfants restent les mmes. Dans lexemple Mail
Client, nous ne souhaitons pas ce comportement ; nous voulons plutt que QTreeWidget et
QTableWidget conservent leurs dimensions et nous voulons attribuer tout espace supplmentaire QTextEdit (voir Figure 6.10). Nous y parvenons grce aux deux appels de setStretchFactor(). Le premier argument est lindex de base zro du widget enfant du sparateur et le
second argument est le facteur dtirement que nous dsirons dfinir ; la valeur par dfaut est
gale 0.
mainSplitter
0
foldersTreeWidget
Figure 6.10
Index du sparateur de
lapplication Mail Client
1
messagesTableWidget
0
rightSplitter
textEdit
Chapitre 6
155
QSplitter est totalement pris en charge par le Qt Designer. Pour placer des widgets dans un
sparateur, positionnez les widgets enfants plus ou moins leurs emplacements, slectionnez-les
et cliquez sur Form > Lay Out Horizontally in Splitter ou Form > Lay Out Vertically in Splitter.
Zones droulantes
Figure 6.11
Les widgets qui constituent
QScrollArea
viewport()
verticalScrollBar()
La classe QScrollArea propose une fentre daffichage droulante et deux barres de dfilement, comme le montre la Figure 6.11. Si vous voulez ajouter des barres de dfilement un
widget, le plus simple est dutiliser un QScrollArea au lieu dinstancier vos propres
QScrollBar et dimplmenter la fonctionnalit droulante vous-mme.
horizontalScrollBar()
Pour se servir de QScrollArea, il faut appeler setWidget() avec le widget auquel vous
souhaitez ajouter des barres de dfilement. QScrollArea reparente automatiquement le
widget pour quil devienne un enfant de la fentre daffichage (accessible via QScrollArea::viewport()) si ce nest pas encore le cas. Par exemple, si vous voulez des barres de
dfilement autour du widget IconEditor dvelopp au Chapitre 5, vous avez la possibilit
dcrire ceci :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
156
QScrollArea prsente le widget dans sa taille actuelle ou utilise la taille requise si le widget
na pas encore t redimensionn. En appelant setWidgetResizable(true), vous pouvez
dire QScrollArea de redimensionner automatiquement le widget pour profiter de tout
espace supplmentaire au-del de sa taille requise.
Par dfaut, les barres de dfilement ne sont affiches que lorsque la fentre daffichage est
plus petite que le widget enfant. Nous pouvons obliger les barres de dfilement tre toujours
visibles en configurant les stratgies de barre de dfilement :
scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
Figure 6.12
Redimensionner
un QScrollArea
Chapitre 6
157
Dans les versions antrieures de Qt, les barres doutils taient considres comme des widgets
ancrables et partageaient les mmes points dancrage. Avec Qt 4, les barres doutils occupent
leurs propres zones autour du widget central (comme illustr en Figure 6.14) et ne peuvent pas
tre dtaches. Si une barre doutils flottante savre ncessaire, nous pouvons simplement la
placer dans un QDockWindow.
158
Figure 6.14
Les points dancrage et
les zones de barres doutils
de QMainWindow
Titre de la fentre
Barre de menus
Zone suprieure de barre d'outils
Les coins matrialiss avec des lignes pointilles peuvent appartenir lun des deux points
dancrage adjacents. Par exemple, nous pourrions instaurer que le coin suprieur gauche
appartient la zone dancrage gauche en appelant QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea).
Lextrait de code suivant montre comment encadrer un widget existant (dans ce cas,
QTreeWidget) dans un QDockWidget et comment linsrer dans le point dancrage droit :
QDockWidget *shapesDockWidget = new QDockWidget(tr("Shapes"));
shapesDockWidget->setWidget(treeWidget);
shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea
| Qt::RightDockWidgetArea);
addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
Lappel de setAllowedAreas() spcifie les contraintes selon lesquelles les points dancrage
peuvent accepter la fentre ancrable. Ici, nous autorisons uniquement lutilisateur faire glisser le widget ancrable vers les zones gauche et droite, o il y a suffisamment despace vertical
pour quil saffiche convenablement. Si aucune zone autorise nest explicitement spcifie,
lutilisateur a la possibilit de faire glisser ce widget vers lun des quatre points dancrage.
Voici comment crer une barre doutils contenant un QComboBox, un QSpinBox et quelques
QToolButton dans le constructeur dune sous-classe de QMainWindow:
QToolBar *fontToolBar = new QToolBar(tr("Font"));
fontToolBar->addWidget(familyComboBox);
fontToolBar->addWidget(sizeSpinBox);
fontToolBar->addAction(boldAction);
fontToolBar->addAction(italicAction);
Chapitre 6
159
fontToolBar->addAction(underlineAction);
fontToolBar->setAllowedAreas(Qt::TopToolBarArea
| Qt::BottomToolBarArea);
addToolBar(fontToolBar);
Si nous voulons sauvegarder la position de tous les widgets ancrables et barres doutils de
manire pouvoir les restaurer la prochaine fois que lapplication sera excute, nous pouvons
crire un code similaire celui utilis pour enregistrer ltat dun QSplitter laide des
fonctions saveState() et restoreState() de QMainWindow:
void MainWindow::writeSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
settings.setValue("size", size());
settings.setValue("state", saveState());
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
resize(settings.value("size").toSize());
restoreState(settings.value("state").toByteArray());
settings.endGroup();
}
Enfin, QMainWindow propose un menu contextuel qui rpertorie toutes les fentres ancrables et
toutes les barres doutils, comme illustr en Figure 6.15. Lutilisateur peut fermer et restaurer
ces fentres et masquer et restaurer des barres doutils par le biais de ce menu.
Figure 6.15
Le menu contextuel
de QMainWindow
160
Les applications MDI fournissent gnralement un menu Fentre (Window) partir duquel
vous grez les fentres et les listes de fentres. La fentre active est identifie par une
coche. Lutilisateur peut activer nimporte quelle fentre en cliquant sur son entre dans ce
menu.
Dans cette section, nous dvelopperons lapplication MDI Editor prsente en Figure 6.16
pour vous montrer comment crer une application MDI et comment implmenter son menu
Window.
Figure 6.16
Lapplication
MDI Editor
Lapplication comprend deux classes : MainWindow et Editor. Le code se trouve sur le site
www.pearson.fr, la page ddie cet ouvrage, et vu quune grande partie de celui-ci est
identique ou similaire lapplication Spreadsheet de la Partie I, nous ne prsenterons que les
parties nouvelles.
Figure 6.17
Les menus
de lapplication
MDI Editor
Chapitre 6
161
Dans le constructeur MainWindow, nous crons un widget QWorkspace qui devient le widget
central. Nous connectons le signal windowActivated() de QWorkspace au slot que nous
voulons utiliser pour conserver le menu Window jour.
void MainWindow::newFile()
{
Editor *editor = createEditor();
editor->newFile();
editor->show();
}
Le slot newFile() correspond loption File > New. Il dpend de la fonction prive createEditor() pour crer un widget enfant Editor.
Editor *MainWindow::createEditor()
{
Editor *editor = new Editor;
connect(editor, SIGNAL(copyAvailable(bool)),
cutAction, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(copyAvailable(bool)),
copyAction, SLOT(setEnabled(bool)));
workspace->addWindow(editor);
windowMenu->addAction(editor->windowMenuAction());
windowActionGroup->addAction(editor->windowMenuAction());
return editor;
}
162
Avec MDI, il est possible que plusieurs widgets Editor soient utiliss. Cest un souci parce
que nous ne voulons rpondre quau signal copyAvailable(bool) de la fentre Editor
active et pas aux autres. Cependant, ces signaux ne peuvent tre mis que par la fentre active,
ce nest donc pas un problme en pratique.
Lorsque nous avons configur Editor, nous avons ajout un QAction reprsentant la fentre
au menu Window. Laction est fournie par la classe Editor que nous tudierons plus loin.
Nous ajoutons galement laction un objet QActionGroup. Avec QActionGroup, nous
sommes srs quun seul lment du menu Window est coch la fois.
void MainWindow::open()
{
Editor *editor = createEditor();
if (editor->open()) {
editor->show();
} else {
editor->close();
}
}
La fonction open() correspond File > Open. Elle cre un Editor pour le nouveau document
et appelle open() sur ce dernier. Il est prfrable dimplmenter des oprations de fichier dans
la classe Editor que dans la classe MainWindow, parce que chaque Editor a besoin dassurer
le suivi de son propre tat indpendant.
Si open() choue, nous fermons simplement lditeur parce que lutilisateur aura dj t
inform de lerreur. Nous navons pas supprimer explicitement lobjet Editor nous-mmes ;
cest fait automatiquement par Editor par le biais de lattribut Qt::WA_DeleteOnClose, qui
est dfini dans le constructeur de Editor.
void MainWindow::save()
{
if (activeEditor())
activeEditor()->save();
}
Le slot save() invoque Editor::save() sur lditeur actif, sil y en a un. Une fois encore, le
code qui accomplit le vritable travail se situe dans la classe Editor.
Editor *MainWindow::activeEditor()
{
return qobject_cast<Editor *>(workspace->activeWindow());
}
La fonction prive activeEditor() retourne la fentre enfant active sous la forme dun pointeur
de Editor, ou dun pointeur nul sil ny en a pas.
void MainWindow::cut()
{
if (activeEditor())
activeEditor()->cut();
}
Chapitre 6
163
Le slot cut() invoque Editor::cut() sur lditeur actif. Nous ne montrons pas les slots
copy() et paste() puisquils suivent le mme modle.
void MainWindow::updateMenus()
{
bool hasEditor = (activeEditor()!= 0);
bool hasSelection = activeEditor()
&& activeEditor()->textCursor().hasSelection();
saveAction->setEnabled(hasEditor);
saveAsAction->setEnabled(hasEditor);
pasteAction->setEnabled(hasEditor);
cutAction->setEnabled(hasSelection);
copyAction->setEnabled(hasSelection);
closeAction->setEnabled(hasEditor);
closeAllAction->setEnabled(hasEditor);
tileAction->setEnabled(hasEditor);
cascadeAction->setEnabled(hasEditor);
nextAction->setEnabled(hasEditor);
previousAction->setEnabled(hasEditor);
separatorAction->setVisible(hasEditor);
if (activeEditor())
activeEditor()->windowMenuAction()->setChecked(true);
}
Le slot updateMenus() est invoqu ds quune fentre est active (et quand la dernire fentre
est ferme) pour mettre jour le systme de menus, en raison de la connexion signal-slot dans
le constructeur de MainWindow.
La plupart des options de menu ne sont intressantes que si une fentre est active, nous les
dsactivons donc quand ce nest pas le cas. Nous terminons en appelant setChecked() sur
QAction reprsentant la fentre active. Grce QActionGroup, nous navons pas besoin de
dcocher explicitement la fentre active prcdente.
void MainWindow::createMenus()
{
...
windowMenu = menuBar()->addMenu(tr("&Window"));
windowMenu->addAction(closeAction);
windowMenu->addAction(closeAllAction);
windowMenu->addSeparator();
windowMenu->addAction(tileAction);
windowMenu->addAction(cascadeAction);
windowMenu->addSeparator();
windowMenu->addAction(nextAction);
windowMenu->addAction(previousAction);
windowMenu->addAction(separatorAction);
...
}
164
La fonction prive createMenus() introduit des actions dans le menu Window. Les actions
sont toutes typiques de tels menus et sont implmentes facilement laide des slots closeActiveWindow(), closeAllWindows(), tile() et cascade() de QWorkspace. A chaque
fois que lutilisateur ouvre une nouvelle fentre, elle est ajoute la liste dactions du menu
Window. (Ceci est effectu dans la fonction createEditor() tudie prcdemment.) Ds
que lutilisateur ferme une fentre dditeur, son action dans le menu Window est supprime
(tant donn que laction appartient la fentre dditeur), et elle disparat de ce menu.
void MainWindow::closeEvent(QCloseEvent *event)
{
workspace->closeAllWindows();
if (activeEditor()) {
event->ignore();
} else {
event->accept();
}
}
La fonction closeEvent() est rimplmente pour fermer toutes les fentres enfants, chaque
enfant reoit donc un vnement close. Si lun des widgets enfants "ignore" cet vnement
(parce que lutilisateur a annul une bote de message "modifications non enregistres"), nous
ignorons lvnement close pour MainWindow; sinon, nous lacceptons, ce qui a pour consquence de fermer la fentre complte. Si nous navions pas rimplment closeEvent() dans
MainWindow, lutilisateur naurait pas eu la possibilit de sauvegarder des modifications non
enregistres.
Nous avons termin notre analyse de MainWindow, nous pouvons donc passer limplmentation dEditor. La classe Editor reprsente une fentre enfant. Elle hrite de QTextEdit qui
propose une fonctionnalit de modification de texte. Tout comme nimporte quel widget Qt
peut tre employ comme une fentre autonome, tout widget Qt peut tre utilis comme une
fentre enfant dans un espace de travail MDI.
Voici la dfinition de classe :
class Editor: public QTextEdit
{
Q_OBJECT
public:
Editor(QWidget *parent = 0);
void newFile();
bool open();
bool openFile(const QString &fileName);
bool save();
bool saveAs();
QSize sizeHint() const;
QAction *windowMenuAction() const { return action; }
Chapitre 6
165
protected:
void closeEvent(QCloseEvent *event);
private slots:
void documentWasModified();
private:
bool okToContinue();
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QString curFile;
bool isUntitled;
QString fileFilters;
QAction *action;
};
Quatre des fonctions prives qui se trouvaient dans la classe MainWindow de lapplication
Spreadsheet sont galement prsentes dans la classe Editor: okToContinue(), saveFile(), setCurrentFile() et strippedName().
Editor::Editor(QWidget *parent)
: QTextEdit(parent)
{
action = new QAction(this);
action->setCheckable(true);
connect(action, SIGNAL(triggered()), this, SLOT(show()));
connect(action, SIGNAL(triggered()), this, SLOT(setFocus()));
isUntitled = true;
fileFilters = tr("Text files (*.txt)\n"
"All files (*)");
connect(document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
setWindowIcon(QPixmap(":/images/document.png"));
setAttribute(Qt::WA_DeleteOnClose);
}
Nous crons dabord un QAction reprsentant lditeur dans le menu Window de lapplication
et nous connectons cette action aux slots show() et setFocus().
Etant donn que nous autorisons les utilisateurs crer autant de fentres dditeurs quils le
souhaitent, nous devons prendre certaines dispositions concernant leur dnomination, dans le but
de faire une distinction avant le premier enregistrement. Un moyen courant de grer cette situation est dattribuer des noms qui incluent un chiffre (par exemple, document1.txt). Nous utilisons
la variable isUntitled pour faire une distinction entre les noms fournis par lutilisateur et les
noms crs par programme.
166
Nous connectons le signal contentsChanged() du document au slot priv documentWasModified(). Ce slot appelle simplement setWindowModified(true).
Enfin, nous dfinissons lattribut Qt::WA_DeleteOnClose pour viter toute fuite de mmoire
quand lutilisateur ferme une fentre Editor.
Aprs le constructeur, il est logique dappeler newFile() ou open().
void Editor::newFile()
{
static int documentNumber = 1;
curFile = tr("document%1.txt").arg(documentNumber);
setWindowTitle(curFile + "[*]");
action->setText(curFile);
isUntitled = true;
++documentNumber;
}
Chapitre 6
167
La fonction save() sappuie sur la variable isUntitled pour dterminer si elle doit appeler
saveFile() ou saveAs().
void Editor::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
event->accept();
} else {
event->ignore();
}
}
168
Si lutilisateur spcifie des fichiers dans la ligne de commande, nous tentons de les charger.
Sinon, nous dmarrons avec un document vide. Les options de ligne de commande spcifiques
Qt, comme -style et -font, sont automatiquement supprimes de la liste darguments par
le constructeur QApplication. Donc si nous crivons :
mdieditor -style motif readme.txt
dans la ligne de commande, QApplication::arguments() retourne un QStringList contenant deux lments ("mdieditor" et "readme.txt") et lapplication MDI Editor dmarre
avec le document readme.txt.
MDI est un moyen de grer simultanment plusieurs documents. Sous Mac OS X, la meilleure
approche consiste utiliser plusieurs fentres de haut niveau. Cette technique est traite dans la
section "Documents multiples" du Chapitre 3.
7
Traitement
des vnements
Au sommaire de ce chapitre
Rimplmenter les gestionnaires
dvnements
Installer des filtres dvnements
Rester ractif pendant un traitement
intensif
170
Quand nous programmons avec Qt, nous avons rarement besoin de penser aux vnements, parce que les widgets Qt mettent des signaux lorsque quelque chose de significatif
se produit. Les vnements deviennent utiles quand nous crivons nos propres widgets personnaliss ou quand nous voulons modifier le comportement des widgets Qt existants.
Il ne faut pas confondre vnements et signaux. En rgle gnrale, les signaux savrent utiles
lors de lemploi dun widget, alors que les vnements prsentent une utilit au moment de
limplmentation dun widget. Par exemple, quand nous utilisons QPushButton, nous nous
intressons plus son signal clicked() quaux vnements mouse ou key de bas niveau qui
provoquent lmission du signal. Cependant, si nous implmentons une classe telle que QPushButton, nous devons crire un code qui grera les vnements mouse et key et qui mettra le
signal clicked() si ncessaire.
Chapitre 7
171
Les touches de tabulation et de tabulation arrire (Maj+Tab) sont des cas particuliers. Elles
sont gres par QWidget::event() avant lappel de keyPressEvent(), avec la consigne de
transmettre le focus au widget suivant ou prcdent dans lordre de la chane de focus. Ce
comportement correspond habituellement ce que nous recherchons, mais dans un widget
CodeEditor, nous prfrerions que la touche Tab produise le dcalage dune ligne par rapport
la marge. Voici comment event() pourrait tre rimplment :
bool CodeEditor::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
insertAtCurrentPosition(\t);
return true;
}
}
return QWidget::event(event);
}
Si lvnement est li une touche sur laquelle lutilisateur a appuy, nous convertissons
lobjet QEvent en QKeyEvent et nous vrifions quelle touche a t presse. Sil sagit de la
touche Tab, nous effectuons un traitement et nous retournons true pour informer Qt que nous
avons gr lvnement. Si nous avions retourn false, Qt transmettrait lvnement au
widget parent.
Une approche plus intelligente pour implmenter les combinaisons de touches consiste se
servir de QAction. Par exemple, si goToBeginningOfLine() et goToBeginningOfDocument() sont des slots publics dans le widget CodeEditor, et si CodeEditor fait
172
office de widget central dans une classe MainWindow, nous pourrions ajouter des combinaisons
de touches avec le code suivant :
MainWindow::MainWindow()
{
editor = new CodeEditor;
setCentralWidget(editor);
goToBeginningOfLineAction =
new QAction(tr("Go to Beginning of Line"), this);
goToBeginningOfLineAction->setShortcut(tr("Home"));
connect(goToBeginningOfLineAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfLine()));
goToBeginningOfDocumentAction =
new QAction(tr("Go to Beginning of Document"), this);
goToBeginningOfDocumentAction->setShortcut(tr("Ctrl+Home"));
connect(goToBeginningOfDocumentAction, SIGNAL(activated()),
editor, SLOT(goToBeginningOfDocument()));
...
}
Cela permet dajouter facilement des commandes un menu ou une barre doutils, comme
nous lavons vu dans le Chapitre 3. Si les commandes napparaissent pas dans linterface utilisateur, les objets QAction pourraient tre remplacs par un objet QShortcut, la classe
employe par QAction en interne pour prendre en charge les combinaisons de touches.
Par dfaut, les combinaisons de touches dfinies laide de QAction ou QShortcut sur un
widget sont actives ds que la fentre contenant le widget est active. Vous pouvez modifier ce
comportement grce QAction::setShortcutContext() ou QShortcut::setContext().
Lautre type courant dvnement est lvnement timer. Alors que la plupart des autres types
dvnements se dclenchent suite une action utilisateur, les vnements timer permettent
aux applications deffectuer un traitement intervalles rguliers. Les vnements timer
peuvent tre utiliss pour implmenter des curseurs clignotants et dautres animations, ou
simplement pour ractualiser laffichage.
Pour analyser les vnements timer, nous implmenterons un widget Ticker illustr en
Figure 7.1. Ce widget prsente une bannire qui dfile dun pixel vers la gauche toutes les
30 millisecondes. Si le widget est plus large que le texte, le texte est rpt autant de fois que
ncessaire pour remplir toute la largeur du widget.
Figure 7.1
Le widget Ticker
Chapitre 7
173
#include <QWidget>
class Ticker: public QWidget
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText)
public:
Ticker(QWidget *parent = 0);
void setText(const QString &newText);
QString text() const { return myText; }
QSize sizeHint() const;
protected:
void paintEvent(QPaintEvent *event);
void timerEvent(QTimerEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
private:
QString myText;
int offset;
int myTimerId;
};
#endif
Nous rimplmentons quatre gestionnaires dvnements dans Ticker, dont trois que nous
avons dj vus auparavant : timerEvent(), showEvent() et hideEvent().
Analysons prsent limplmentation :
#include <QtGui>
#include "ticker.h"
Ticker::Ticker(QWidget *parent)
: QWidget(parent)
{
offset = 0;
myTimerId = 0;
}
174
La fonction setText() dtermine le texte afficher. Elle invoque update() pour demander le
rafrachissement de laffichage et updateGeometry() pour informer tout gestionnaire de disposition responsable du widget Ticker dun changement de taille requise.
QSize Ticker::sizeHint() const
{
return fontMetrics().size(0, text());
}
La fonction sizeHint() retourne lespace requis par le texte comme tant la taille idale du
widget. QWidget::fontMetrics() renvoie un objet QFontMetrics qui peut tre interrog
pour obtenir des informations lies la police du widget. Dans ce cas, nous demandons la taille
exige par le texte. (Puisque le premier argument de QFontMetrics::size() est un indicateur
qui nest pas ncessaire pour les chanes simples, nous transmettons simplement 0.)
void Ticker::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
int textWidth = fontMetrics().width(text());
if (textWidth < 1)
return;
int x = -offset;
while (x < width()) {
painter.drawText(x, 0, textWidth, height(),
Qt::AlignLeft | Qt::AlignVCenter, text());
x += textWidth;
}
}
Chapitre 7
175
{
if (event->timerId() == myTimerId) {
++offset;
if (offset >= fontMetrics().width(text()))
offset = 0;
scroll(-1, 0);
} else {
QWidget::timerEvent(event);
}
}
La fonction timerEvent() est invoque intervalles rguliers par le systme. Elle incrmente
offset de 1 pour simuler un mouvement, encadrant la largeur du texte. Puis elle fait dfiler le
contenu du widget dun pixel vers la gauche grce QWidget::scroll(). Nous aurions pu
simplement appeler update() au lieu de scroll(), mais scroll() est plus efficace, parce
quelle dplace simplement les pixels existants lcran et ne dclenche un vnement paint
que pour la zone nouvellement affiche du widget (une bande dun pixel de large dans ce cas).
Si lvnement timer ne correspond pas au timer qui nous intresse, nous le transmettons
notre classe de base.
void Ticker::hideEvent(QHideEvent * /* event */)
{
killTimer(myTimerId);
}
176
if (event->key() == Qt::Key_Space) {
focusNextChild();
} else {
QLineEdit::keyPressEvent(event);
}
}
Cette approche prsente un inconvnient de taille : si nous utilisons plusieurs types de widgets
dans le formulaire (par exemple, QComboBoxes et QSpinBoxes), nous devons galement les
driver pour quils affichent le mme comportement. Il existe une meilleure solution : CustomerInfoDialog contrle les vnements "bouton souris enfonc" de ses widgets enfants et
implmente le comportement ncessaire dans le code de contrle. Pour ce faire, vous utiliserez
des filtres dvnements. Dfinir un filtre dvnement implique deux tapes :
1. enregistrer lobjet contrleur avec lobjet cible en appelant installEventFilter() sur la
cible ;
2. grer les vnements de lobjet cible dans la fonction eventFilter() de lobjet contrleur.
Le code du constructeur de CustomerInfoDialog constitue un bon endroit pour enregistrer
lobjet contrleur :
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent)
: QDialog(parent)
{
...
firstNameEdit->installEventFilter(this);
lastNameEdit->installEventFilter(this);
cityEdit->installEventFilter(this);
phoneNumberEdit->installEventFilter(this);
}
Ds que le filtre dvnement est enregistr, les vnements qui sont envoys aux widgets
firstNameEdit, lastNameEdit, cityEdit et phoneNumberEdit sont dabord transmis la
fonction eventFilter() de CustomerInfoDialog avant dtre envoys vers la destination
prvue.
Voici la fonction eventFilter() qui reoit les vnements :
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
{
if (target == firstNameEdit || target == lastNameEdit
|| target == cityEdit || target == phoneNumberEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Space) {
focusNextChild();
return true;
}
}
}
return QDialog::eventFilter(target, event);
}
Chapitre 7
177
Nous vrifions tout dabord que le widget cible est un des QLineEdit. Si lvnement est li
lenfoncement dune touche, nous le convertissons en QKeyEvent et nous vrifions quelle
touche a t presse. Si la touche enfonce correspondait la barre despace, nous invoquons
focusNextChild() pour activer le prochain widget dans la chane de focus, et nous retournons true pour dire Qt que nous avons gr lvnement. Si nous avions renvoy false, Qt
aurait envoy lvnement sa cible prvue, ce qui aurait introduit un espace parasite dans
QLineEdit.
Si le widget cible nest pas un QLineEdit, ou si lvnement ne rsulte pas de lenfoncement
de la barre despace, nous passons le contrle limplmentation de eventFilter() de la
classe de base. Le widget cible aurait aussi pu tre un widget que la classe de base, QDialog,
est en train de contrler. (Dans Qt 4.1, ce nest pas le cas pour QDialog. Cependant, dautres
classes de widgets Qt, comme QScrollArea, surveillent certains de leurs widgets enfants pour
diverses raisons.)
Qt propose cinq niveaux auxquels des vnements peuvent tre traits et filtrs :
1. Nous pouvons rimplmenter un gestionnaire dvnements spcifique.
Rimplmenter des gestionnaires dvnements comme mousePressEvent(), keyPressEvent() et paintEvent() est de loin le moyen le plus commun de traiter des vnements.
Nous en avons dj vu de nombreux exemples.
2. Nous pouvons rimplmenter QObject::event().
En rimplmentant la fonction event(), nous avons la possibilit de traiter des vnements avant quils natteignent les gestionnaires dvnements spcifiques. Cette approche
est surtout employe pour redfinir la signification par dfaut de la touche Tab, comme
expliqu prcdemment. Elle est aussi utilise pour grer des types rares dvnements
pour lesquels il nexiste aucun gestionnaire dvnements spcifique (par exemple,
QEvent::HoverEnter). Quand nous rimplmentons event(), nous devons appeler la
fonction event() de la classe de base pour grer les cas que nous ne grons pas explicitement.
3. Nous pouvons installer un filtre dvnement sur un seul QObject.
Lorsquun objet a t enregistr avec installEventFilter(), tous les vnements pour
lobjet cible sont dabord envoys la fonction eventFilter() de lobjet contrleur. Si
plusieurs filtres dvnements sont installs sur le mme objet, les filtres sont activs tour
de rle, du plus rcemment install au premier install.
4. Nous pouvons installer un filtre dvnement sur lobjet QApplication.
Lorsquun filtre dvnement a t enregistr pour qApp (lunique objet de QApplication), chaque vnement de chaque objet de lapplication est envoy la fonction eventFilter() avant dtre transmis un autre filtre dvnement. Cette technique est trs utile
pour le dbogage. Elle peut aussi tre employe pour grer des vnements mouse transmis
aux widgets dsactivs que QApplication ignore normalement.
178
Window Title
QDialog
QGroupBox
QCheckBox
QCheckBox
QCheckBox
QCheckBox
Chapitre 7
179
Cette approche prsente un risque : lutilisateur peut fermer la fentre principale alors que
lapplication est toujours en train deffectuer la sauvegarde, ou mme cliquer sur File > Save
une seconde fois, ce qui provoque un comportement indtermin. La solution la plus simple
ce problme est de remplacer
qApp->processEvents();
par
qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
180
lutilisateur dannuler lopration. Voici le code permettant denregistrer une feuille de calcul
avec cette approche :
bool Spreadsheet::writeFile(const QString &fileName)
{
QFile file(fileName);
...
QProgressDialog progress(this);
progress.setLabelText(tr("Saving %1").arg(fileName));
progress.setRange(0, RowCount);
progress.setModal(true);
for (int row = 0; row < RowCount; ++row) {
progress.setValue(row);
qApp->processEvents();
if (progress.wasCanceled()) {
file.remove();
return false;
}
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << quint16(row) << quint16(column) << str;
}
}
return true;
}
Nous crons un QProgressDialog avec NumRows comme nombre total dtapes. Puis, pour
chaque ligne, nous appelons setValue() pour mettre jour la barre de progression. QProgressDialog calcule automatiquement un pourcentage en divisant la valeur actuelle davancement par le nombre total dtapes. Nous invoquons QApplication::processEvents()
pour traiter tout vnement de rafrachissement daffichage, tout clic ou toute touche enfonce
par lutilisateur (par exemple pour permettre lutilisateur de cliquer sur Cancel). Si lutilisateur
clique sur Cancel, nous annulons la sauvegarde et nous supprimons le fichier.
Nous nappelons pas show() sur QProgressDialog parce que les botes de dialogue de
progression le font. Si lopration se rvle plus courte, peut-tre parce que le fichier enregistrer est petit ou parce que lordinateur est rapide, QProgressDialog le dtectera et ne
saffichera pas du tout.
En complment du multithread et de lutilisation de QProgressDialog, il existe une manire
totalement diffrente de traiter les longues oprations : au lieu daccomplir le traitement la
demande de lutilisateur, nous pouvons ajourner ce traitement jusqu ce que lapplication soit
inactive. Cette solution peut tre envisage si le traitement peut tre interrompu et repris en
toute scurit, parce que nous ne pouvons pas prdire combien de temps lapplication sera
inactive.
Chapitre 7
181
Dans Qt, cette approche peut tre implmente en utilisant un timer de 0 milliseconde. Ces
timers chronomtrent ds quil ny a pas dvnements en attente. Voici un exemple dimplmentation de timerEvent() qui prsente cette approche :
void Spreadsheet::timerEvent(QTimerEvent *event)
{
if (event->timerId() == myTimerId) {
while (step < MaxStep &&!qApp->hasPendingEvents()) {
performStep(step);
++step;
}
} else {
QTableWidget::timerEvent(event);
}
}
8
Graphiques 2D et 3D
Au sommaire de ce chapitre
Dessiner avec QPainter
Transformations du painter
Affichage de haute qualit avec QImage
Impression
Graphiques avec OpenGL
Les graphiques 2D de Qt se basent sur la classe QPainter. QPainter peut tracer des
formes gomtriques (points, lignes, rectangles, ellipses, arcs, cordes, segments, polygones et courbes de Bzier), de mme que des objets pixmaps, des images et du texte.
De plus, QPainter prend en charge des fonctionnalits avances, telles que lanticrnelage (pour les bords du texte et des formes), le mlange alpha, le remplissage dgrad
et les tracs de vecteur. QPainter supporte aussi les transformations qui permettent de
dessiner des graphiques 2D indpendants de la rsolution.
QPainter peut galement tre employe pour dessiner sur un "priphrique de dessin" tel
quun QWidget, QPixmap ou QImage. Cest utile quand nous crivons des widgets
personnaliss ou des classes dlments personnalises avec leurs propres aspect et apparence. Il est aussi possible dutiliser QPainter en association avec QPrinter pour imprimer et gnrer des fichiers PDF. Cela signifie que nous pouvons souvent nous servir du
mme code pour afficher des donnes lcran et pour produire des rapports imprims.
Il existe une alternative QPainter: OpenGL. OpenGL est une bibliothque standard
permettant de dessiner des graphiques 2D et 3D. Le module QtOpenGL facilite lintgration de code OpenGL dans des applications Qt.
184
Nous avons la possibilit de dessiner diffrentes formes laide des fonctions draw...() de
QPainter. La Figure 8.1 rpertorie les plus importantes. Les paramtres de QPainter influencent
la faon de dessiner.
Figure 8.1
Les fonctions draw...()
de QPainter les plus
frquemment utilises
p3
p2
(x1, y1)
(x, y)
(x2, y2)
drawPoint()
drawLine()
p3
p2
p1
p1
drawPolyline()
p1
drawPoints()
p4
drawLines()
(x, y)
h
w
drawRoundRect()
(x, y)
p4
(x, y)
(x, y)
p1
drawPolygon()
(x, y)
w
drawRect()
w
drawArc()
w
drawEllipse()
w
drawChord()
w
drawPie()
drawPixmap()
drawPath()
(x, y)
(x, y)
Ag
drawText()
(x, y)
p3
p2
p3
p2
p4
p4
Chapitre 8
Graphiques 2D et 3D
185
Certains dentre eux proviennent du priphrique, dautres sont initialiss leurs valeurs par
dfaut. Les trois principaux paramtres sont le crayon, le pinceau et la police :
Le crayon est utilis pour tracer des lignes et les contours des formes. Il est constitu dune
couleur, dune largeur, dun style de trait, de capuchon et de jointure (Figures 8.2 et 8.3).
Le pinceau permet de remplir des formes gomtriques. Il est compos normalement dune
couleur et dun style, mais peut galement appliquer une texture (un pixmap rpt
linfini) ou un dgrad (Voir Figure 8.4).
La police est utilise pour dessiner le texte. Une police possde de nombreux attributs, dont
une famille et une taille.
Figure 8.2
Styles de capuchon
et de jointure
FlatCap
SquareCap
MiterJoin
BevelJoin
Figure 8.3
Styles de crayon
RoundCap
RoundJoin
Largeur de trait
3
2
NoPen
SolidLine
DashLine
DotLine
DashDotLine
DashDotDotLine
Figure 8.4
Styles prdfinis
de pinceau
SolidPattern
Dense1Pattern
Dense2Pattern
Dense3Pattern
Dense4Pattern
Dense5Pattern
Dense6Pattern
Dense7Pattern
HorPattern
VerPattern
CrossPattern
BDiagPattern
FDiagPattern
DiagCrossPat.
NoBrush
186
Ces paramtres peuvent tre modifis tout moment en appelant setPen(), setBrush() et
setFont() avec un objet QPen, QBrush ou QFont.
Figure 8.5
Exemples de formes
gomtriques
(b) Un segment
Analysons quelques exemples pratiques. Voici le code permettant de dessiner lellipse illustre
en Figure 8.5 (a) :
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(QPen(Qt::black, 12, Qt::DashDotLine, Qt::RoundCap));
painter.setBrush(QBrush(Qt::green, Qt::SolidPattern));
painter.drawEllipse(80, 80, 400, 240);
La classe QPainterPath peut spcifier des formes vectorielles arbitraires en regroupant des
lments graphiques de base : droites, ellipses, polygones, arcs, courbes de Bzier cubiques et
Chapitre 8
Graphiques 2D et 3D
187
quadratiques et autres tracs de dessin. Les tracs de dessin constituent la primitive graphique
ultime, dans le sens o on peut dsigner toute forme ou toute combinaison de formes par le
terme de trac.
Un trac spcifie un contour, et la zone dcrite par le contour peut tre remplie laide dun
pinceau. Dans lexemple de la Figure 8.5 (c), nous navons pas utilis de pinceau, seul le contour
est donc dessin.
Les trois exemples prcdents utilisent des modles de pinceau intgrs (Qt::SolidPattern,
Qt::DiagCrossPattern et Qt::NoBrush). Dans les applications modernes, les remplissages
dgrads reprsentent une alternative populaire aux remplissages monochromes. Les dgrads
reposent sur une interpolation de couleur permettant dobtenir des transitions homognes entre
deux ou plusieurs couleurs. Ils sont frquemment utiliss pour produire des effets 3D ; par
exemple, le style Plastique se sert des dgrads pour afficher des QPushButton.
Qt prend en charge trois types de dgrads : linaire, conique et circulaire. Lexemple Oven
Timer dans la section suivante combine les trois types de dgrads dans un seul widget pour le
rendre plus rel.
Les dgrads linaires sont dfinis par deux points de contrle et par une srie "darrts
couleur" sur la ligne qui relie ces deux points. Par exemple, le dgrad linaire de la
Figure 8.6 est cr avec le code suivant :
QLinearGradient gradient(50, 100, 300, 350);
gradient.setColorAt(0.0, Qt::white);
gradient.setColorAt(0.2, Qt::green);
gradient.setColorAt(1.0, Qt::black);
Figure 8.6
Les pinceaux dgrads
de QPainter
(x1, y1)
(xc, yc)
(x2, y2)
QLinearGradient
(xc, yc)
(xf, yf)
QRadialGradient
QRadialGradient
Nous spcifions trois couleurs trois positions diffrentes entre les deux points de contrle.
188
Les positions sont indiques comme des valeurs virgule flottante entre 0 et 1, o 0 correspond au premier point de contrle et 1 au second. Les couleurs situes entre les interruptions
spcifies sont interpoles.
Les dgrads circulaires sont dfinis par un centre (xc, yc), un rayon r et une focale (xf, yf),
en complment des interruptions de dgrad. Le centre et le rayon spcifient un cercle. Les
couleurs se diffusent vers lextrieur partir de la focale, qui peut tre le centre ou tout
autre point dans le cercle.
Les dgrads coniques sont dfinis par un centre (xc, yc) et un angle . Les couleurs se
diffusent autour du point central comme la trajectoire de la petite aiguille dune montre.
Jusqu prsent, nous avons mentionn les paramtres de crayon, de pinceau et de police de
QPainter. En plus de ceux-ci, QPainter propose dautres paramtres qui influencent la faon
dont les formes et le texte sont dessins :
Le pinceau de fond est utilis pour remplir le fond des formes gomtriques (sous le
modle de pinceau), du texte ou des bitmaps quand le mode arrire-plan est configur en
Qt::OpaqueMode (la valeur par dfaut est Qt::TransparentMode).
Lorigine du pinceau correspond au point de dpart des modles de pinceau, normalement
le coin suprieur gauche du widget.
La zone daction est la zone du priphrique de dessin qui peut tre peinte. Dessiner en
dehors de cette zone na aucun effet.
Le viewport, la fentre et la matrice "world" dterminent la manire dont les coordonnes
logiques de QPainter correspondent aux coordonnes physiques du priphrique de dessin.
Par dfaut, celles-ci sont dfinies de sorte que les systmes de coordonnes logiques et
physiques concident. Les systmes de coordonnes sont abords dans la prochaine section.
Le mode de composition spcifie comment les pixels qui viennent dtre dessins doivent
interagir avec les pixels dj prsents sur le priphrique de dessin. La valeur par dfaut est
"source over," o les pixels sont dessins au-dessus des pixels existants. Ceci nest pris en
charge que sur certains priphriques et est trait ultrieurement dans ce chapitre.
Vous pouvez sauvegarder ltat courant dun module de rendu nomm painter tout moment
sur une pile interne en appelant save() et en le restaurant plus tard en invoquant restore().
Cela permet par exemple de changer temporairement certains paramtres, puis de les rinitialiser
leurs valeurs antrieures, comme nous le verrons dans la prochaine section.
Transformations du painter
Avec le systme de coordonnes par dfaut du QPainter, le point (0, 0) se situe dans le coin
suprieur gauche du priphrique de dessin ; les coordonnes x augmentent vers la droite et les
coordonnes y sont orientes vers le bas. Chaque pixel occupe une zone dune taille de 1 1
dans le systme de coordonnes par dfaut.
Chapitre 8
Graphiques 2D et 3D
189
Il est important de comprendre que le centre dun pixel se trouve aux coordonnes dun "demi
pixel".Par exemple, le pixel en haut gauche couvre la zone entre les points (0, 0) et (1, 1) et
son centre se trouve (0,5, 0,5). Si nous demandons QPainter de dessiner un pixel (100,
100) par exemple, il se rapprochera du rsultat en dcalant les coordonnes de +0,5 dans les
deux sens, le pixel sera ainsi centr sur le point (100,5, 100,5).
Cette distinction peut sembler plutt acadmique de prime abord, mais elle prsente des consquences importantes en pratique. Premirement, le dcalage de +0,5 ne se produit que si lanticrnelage est dsactiv (par dfaut) ; si lanticrnelage est activ et si nous essayons de
dessiner un pixel (100, 100) en noir, QPainter coloriera les quatre pixels (99,5, 99,5), (99,5,
100,5), (100,5, 99,5) et (100,5, 100,5) en gris clair pour donner limpression quun pixel se
trouve exactement au point de rencontre de ces quatre pixels. Si cet effet ne vous plat pas,
vous pouvez lviter en spcifiant les coordonnes dun demi pixel, par exemple, (100,5,
100,5).
Lorsque vous tracez des formes comme des lignes, des rectangles et des ellipses, des rgles
similaires sappliquent. La Figure 8.7 vous montre comment le rsultat dun appel de
drawRect(2, 2, 6, 5) varie en fonction de la largeur du crayon quand lanticrnelage est
dsactiv. Il est notamment important de remarquer quun rectangle de 6 5 dessin avec une
largeur de crayon de 1 couvre en fait une zone de 7 6. Cest diffrent des anciens kits
doutils, y compris des versions antrieures de Qt, mais cest essentiel pour pouvoir dessiner
des images vectorielles rellement ajustables et indpendantes de la rsolution.
(0, 0)
(0,
Pas de crayon
Largeur de
crayon 1
Largeur de
crayon 2
Largeur de
crayon 3
Figure 8.7
Dessiner un rectangle de 6 5 sans anticrnelage
Maintenant que nous avons compris le systme de coordonnes par dfaut, nous pouvons nous
concentrer davantage sur la manire de le modifier en utilisant le viewport, la fentre et la
matrice world de QPainter. (Dans ce contexte, le terme de "fentre" ne se rfre pas une
fentre au sens de widget de niveau suprieur, et le "viewport" na rien voir avec le viewport
de QScrollArea.)
Le viewport et la fentre sont troitement lis. Le viewport est un rectangle arbitraire spcifi
en coordonnes physiques. La fentre spcifie le mme rectangle, mais en coordonnes logiques.
190
Au moment du trac, nous indiquons des points en coordonnes logiques qui sont converties en
coordonnes physiques de manire algbrique linaire, en fonction des paramtres actuels de la
fentre et du viewport.
Par dfaut, le viewport et la fentre correspondent au rectangle du priphrique. Par exemple,
si ce dernier est un widget de 320 200, le viewport et la fentre reprsentent le mme rectangle de 320 200 avec son coin suprieur gauche la position (0, 0). Dans ce cas, les systmes
de coordonnes logiques et physiques sont identiques.
Le mcanisme fentre-viewport est utile pour que le code de dessin soit indpendant de la
taille ou de la rsolution du priphrique de dessin. Par exemple, si nous voulons que les coordonnes logiques stendent de (50, 50) (+50, +50) avec (0, 0) au milieu, nous pouvons
configurer la fentre comme suit :
painter.setWindow(-50, -50, 100, 100);
La paire (50, 50) spcifie lorigine et la paire (100, 100) indique la largeur et la hauteur.
Cela signifie que les coordonnes logiques (50, 50) correspondent dsormais aux coordonnes physiques (0, 0), et que les coordonnes logiques (+50, +50) correspondent aux coordonnes physiques (320, 200) (voir Figure 8.8). Dans cet exemple, nous navons pas modifi
le viewport.
(-50, -50)
(0,0)
(-30, -20)
(64, 60)
(+10, +20)
fentre
(192, 140)
(+50, +50)
viewport
(320 ,200)
Figure 8.8
Convertir des coordonnes logiques en coordonnes physiques
Venons-en prsent la matrice world. La matrice world est une matrice de transformation qui
sapplique en plus de la conversion fentre-viewport. Elle nous permet de translater, mettre
lchelle, pivoter et faire glisser les lments que nous dessinons. Par exemple, si nous voulions
dessiner un texte un angle de 45, nous utiliserions ce code :
QMatrix matrix;
matrix.rotate(45.0);
painter.setMatrix(matrix);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));
Les coordonnes logiques transmises drawText() sont transformes par la matrice world,
puis mappes aux coordonnes physiques grce aux paramtres fentre-viewport.
Chapitre 8
Graphiques 2D et 3D
191
Si nous spcifions plusieurs transformations, elles sont appliques dans lordre dans lequel
nous les avons indiques. Par exemple, si nous voulons utiliser le point (10, 20) comme pivot
pour la rotation, nous pouvons translater la fentre, accomplir la rotation, puis translater
nouveau la fentre vers sa position dorigine :
QMatrix matrix;
matrix.translate(-10.0, -20.0);
matrix.rotate(45.0);
matrix.translate(+10.0, +20.0);
painter.setMatrix(matrix);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));
Il existe un moyen plus simple de spcifier des transformations : exploiter les fonctions pratiques
translate(), scale(), rotate() et shear() de QPainter:
painter.translate(-10.0, -20.0);
painter.rotate(45.0);
painter.translate(+10.0, +20.0);
painter.drawText(rect, Qt::AlignCenter, tr("Revenue"));
Cependant, si nous voulons appliquer les mmes transformations de faon rptitive, il est plus
efficace de les stocker dans un objet QMatrix et de configurer la matrice world sur le painter
ds que les transformations sont ncessaires.
Figure 8.9
Le widget OvenTimer
Pour illustrer les transformations du painter, nous allons analyser le code du widget OvenTimer
prsent en Figure 8.9. Ce widget est conu daprs les minuteurs de cuisine que nous utilisions avant que les fours soient quips dhorloges intgres. Lutilisateur peut cliquer sur un cran
pour dfinir la dure. La molette tournera automatiquement dans le sens inverse des aiguilles
dune montre jusqu 0, cest ce moment-l que OvenTimer mettra le signal timeout().
class OvenTimer: public QWidget
{
Q_OBJECT
public:
OvenTimer(QWidget *parent = 0);
void setDuration(int secs);
192
La classe OvenTimer hrite de QWidget et rimplmente deux fonctions virtuelles : paintEvent() et mousePressEvent().
const
const
const
const
const
Nous dfinissons dabord quelques constantes qui contrlent laspect et lapparence du minuteur
de four.
OvenTimer::OvenTimer(QWidget *parent)
: QWidget(parent)
{
finishTime = QDateTime::currentDateTime();
updateTimer = new QTimer(this);
connect(updateTimer, SIGNAL(timeout()), this, SLOT(update()));
finishTimer = new QTimer(this);
finishTimer->setSingleShot(true);
connect(finishTimer, SIGNAL(timeout()), this, SIGNAL(timeout()));
connect(finishTimer, SIGNAL(timeout()), updateTimer, SLOT(stop()));
}
Dans le constructeur, nous crons deux objets QTimer : updateTimer est employ pour actualiser lapparence du widget toutes les secondes, et finishTimer met le signal timeout() du
widget quand le minuteur du four atteint 0. finishTimer ne doit minuter quune seule fois, nous
appelons donc setSingleShot(true) ; par dfaut, les minuteurs se dclenchent de manire rpte jusqu ce quils soient stopps ou dtruits. Le dernier appel de connect() permet darrter
la mise jour du widget chaque seconde quand le minuteur est inactif.
void OvenTimer::setDuration(int secs)
{
if (secs > MaxSeconds) {
Chapitre 8
Graphiques 2D et 3D
193
secs = MaxSeconds;
} else if (secs <= 0) {
secs = 0;
}
finishTime = QDateTime::currentDateTime().addSecs(secs);
if (secs > 0) {
updateTimer->start(UpdateInterval * 1000);
finishTimer->start(secs * 1000);
} else {
updateTimer->stop();
finishTimer->stop();
}
update();
}
Si lutilisateur clique sur le widget, nous recherchons le cran le plus proche grce une
formule mathmatique subtile mais efficace, et nous utilisons le rsultat pour dfinir la
nouvelle dure. Puis nous planifions un raffichage. Le cran sur lequel lutilisateur a cliqu
sera dsormais en haut et se dplacera dans le sens inverse des aiguilles dune montre au fur et
mesure que le temps scoule jusqu atteindre 0.
194
Dans paintEvent(), nous dfinissons le viewport de sorte quil devienne le carr le pus grand
qui peut entrer dans le widget et nous dfinissons la fentre en rectangle (50, 50, 100, 100),
cest--dire le rectangle de 100 _ 100 allant de (50, 50) (+50, +50). La fonction modle
qMin() retourne le plus bas de ses deux arguments. Nous appelons ensuite la fonction draw()
qui se chargera du dessin.
Figure 8.10
Le widget OvenTimer
en trois tailles diffrentes
Chapitre 8
Graphiques 2D et 3D
195
painter->setPen(thinPen);
painter->setBrush(palette().foreground());
painter->drawPolygon(QPolygon(3, &triangle[0][0]));
Nous commenons par dessiner le petit triangle qui symbolise la position 0 en haut du widget.
Le triangle est spcifi par trois coordonnes codes et nous utilisons drawPolygon() pour
lafficher.
Laspect pratique du mcanisme fentre-viewport, cest que nous avons la possibilit de coder
les coordonnes utilises dans les commandes de dessin et de toujours obtenir un bon comportement lors du redimensionnement.
QConicalGradient coneGradient(0, 0, -90.0);
coneGradient.setColorAt(0.0, Qt::darkGray);
coneGradient.setColorAt(0.2, niceBlue);
coneGradient.setColorAt(0.5, Qt::white);
coneGradient.setColorAt(1.0, Qt::darkGray);
painter->setBrush(coneGradient);
painter->drawEllipse(-46, -46, 92, 92);
Nous traons le cercle extrieur et nous le remplissons avec un dgrad conique. Le centre du
dgrad se trouve la position (0, 0) et son angle est de 90.
QRadialGradient haloGradient(0, 0, 20, 0, 0);
haloGradient.setColorAt(0.0, Qt::lightGray);
haloGradient.setColorAt(0.8, Qt::darkGray);
haloGradient.setColorAt(0.9, Qt::white);
haloGradient.setColorAt(1.0, Qt::black);
painter->setPen(Qt::NoPen);
painter->setBrush(haloGradient);
painter->drawEllipse(-20, -20, 40, 40);
Nous nous servons dun dgrad radial pour le cercle intrieur. Le centre et la focale du
dgrad se situent (0, 0). Le rayon du dgrad est gal 20.
QLinearGradient knobGradient(-7, -25, 7, -25);
knobGradient.setColorAt(0.0, Qt::black);
knobGradient.setColorAt(0.2, niceBlue);
knobGradient.setColorAt(0.3, Qt::lightGray);
knobGradient.setColorAt(0.8, Qt::white);
knobGradient.setColorAt(1.0, Qt::black);
painter->rotate(duration() * DegreesPerSecond);
painter->setBrush(knobGradient);
painter->setPen(thinPen);
painter->drawRoundRect(-7, -25, 14, 50, 150, 50);
for (int i = 0; i <= MaxMinutes; ++i) {
if (i % 5 == 0) {
196
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 25,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->rotate(-DegreesPerMinute);
}
}
Nous appelons rotate() pour faire pivoter le systme de coordonnes du painter. Dans
lancien systme de coordonnes, la marque correspondant 0 minute se trouvait en haut ;
maintenant, elle se dplace vers lendroit appropri pour le temps restant. Nous dessinons le
bouton rectangulaire aprs la rotation, parce que son orientation dpend de langle de cette
rotation.
Dans la boucle for, nous dessinons les graduations tout autour du cercle extrieur et les
nombres pour chaque multiple de 5 minutes. Le texte est dessin dans un rectangle invisible en
dessous de la graduation. A la fin de chaque itration, nous faisons pivoter le painter dans le
sens des aiguilles dune montre de 7, ce qui correspond une minute. La prochaine fois que
nous dessinons une graduation, elle sera une position diffrente autour du cercle, mme si les
coordonnes transmises aux appels de drawLine() et drawText() sont toujours les mmes.
Le code de la boucle for souffre dun dfaut mineur qui deviendrait rapidement apparent si
nous effectuions davantage ditrations. A chaque fois que nous appelons rotate(), nous
multiplions la matrice courante par une matrice de rotation, engendrant ainsi une nouvelle
matrice world. Les problmes darrondis associs larithmtique en virgule flottante entranent une matrice world trs imprcise. Voici un moyen de rcrire le code pour viter ce
problme, en excutant save() et restore() pour sauvegarder et recharger la matrice de
transformation originale pour chaque itration :
for (int i = 0; i <= MaxMinutes; ++i) {
painter->save();
painter->rotate(-i * DegreesPerMinute);
if (i % 5 == 0) {
painter->setPen(thickPen);
painter->drawLine(0, -41, 0, -44);
painter->drawText(-15, -41, 30, 25,
Qt::AlignHCenter | Qt::AlignTop,
QString::number(i));
} else {
painter->setPen(thinPen);
painter->drawLine(0, -42, 0, -44);
}
painter->restore();
}
Chapitre 8
Graphiques 2D et 3D
197
Il existe un autre moyen dimplmenter un minuteur de four : calculer les positions (x, y) soimme, en utilisant sin() et cos() pour trouver les positions autour du cercle. Mais nous
aurions encore d employer une translation et une rotation pour dessiner le texte un certain
angle.
198
Voil comment rcrire la fonction paintEvent() du widget pour exploiter le moteur graphique
de Qt indpendant de la plate-forme :
void MyWidget::paintEvent(QPaintEvent *event)
{
QImage image(size(), QImage::Format_ARGB32_Premultiplied);
QPainter imagePainter(&image);
imagePainter.initFrom(this);
imagePainter.setRenderHint(QPainter::Antialiasing, true);
imagePainter.eraseRect(rect());
draw(&imagePainter);
imagePainter.end();
QPainter widgetPainter(this);
widgetPainter.drawImage(0, 0, image);
}
Nous crons un QImage de la mme taille que le widget en format ARGB32 prmultipli, et un
QPainter pour dessiner sur limage. Lappel de initFrom() initialise le crayon, le fond et la
police en fonction du widget. Nous effectuons notre dessin avec QPainter comme dhabitude,
et la fin, nous rutilisons lobjet QPainter pour copier limage sur le widget.
Cette approche produit dexcellents rsultats identiques sur toutes les plates-formes, lexception
de laffichage de la police qui dpend des polices installes.
Une fonctionnalit particulirement puissante du moteur graphique de Qt est sa prise en charge
des modes de composition. Ceux-ci spcifient comment un pixel source et un pixel de destination fusionnent pendant le dessin. Ceci sapplique toutes les oprations de dessin, y compris
le crayon, le pinceau, le dgrad et limage.
Le mode de composition par dfaut est QImage::CompositionMode_SourceOver, ce qui
signifie que le pixel source (celui que nous dessinons) remplace le pixel de destination (le pixel
existant) de telle manire que le composant alpha de la source dfinit sa translucidit. Dans la
Figure 8.11, vous voyez un papillon moiti transparent dessin sur un motif damiers avec
les diffrents modes.
Figure 8.11
Les modes de composition
de QPainter
Source
SourceOver
SourceIn
SourceOut
SourceAtop
Clear
Destination
DestinationOver
DestinationIn
DestinationOut
DestinationAtop
Xor
Chapitre 8
Graphiques 2D et 3D
199
Il faut tre conscient du problme li au fait que lopration QImage::CompositionMode_Xor sapplique au canal alpha. Cela signifie que si nous appliquons XOR (OU
exclusif) la couleur blanche (0xFFFFFFFF), nous obtenons une couleur transparente
(0x00000000), et pas noire (0xFF000000).
Impression
Limpression dans Qt est quivalente au dessin sur QWidget, QPixmap ou QImage. Plusieurs
tapes sont ncessaires :
1. crer un QPrinter qui fera office de priphrique de dessin ;
2. ouvrir un QPrintDialog, qui permet lutilisateur de choisir une imprimante et de configurer certaines options ;
3. crer un QPainter pour agir sur le QPrinter;
4. dessiner une page laide de QPainter;
5. appeler QPrinter::newPage() pour passer la page suivante ;
6. rpter les tapes 4 et 5 jusqu ce que toutes les pages soient imprimes.
Sous Windows et Mac OS X, QPrinter utilise les pilotes dimprimante du systme. Sous
Unix, il gnre un PostScript et lenvoie lp ou lpr (ou au programme dfini en excutant
QPrinter::setPrintProgram()). QPrinter peut aussi servir gnrer des fichiers PDF en
appelant setOutputFormat(QPrinter::PdfFormat).
Commenons par quelques exemples simples qui simpriment tous sur une seule page. Le premier
exemple imprime un QImage(voir Figure 8.12) :
void PrintWindow::printImage(const QImage &image)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec()) {
QPainter painter(&printer);
QRect rect = painter.viewport();
QSize size = image.size();
size.scale(rect.size(), Qt::KeepAspectRatio);
painter.setViewport(rect.x(), rect.y(),
size.width(), size.height());
painter.setWindow(image.rect());
painter.drawImage(0, 0, image);
}
}
200
Figure 8.12
Imprimer un QImage
Nous supposons que la classe PrintWindow possde une variable membre appele printer
de type QPrinter. Nous aurions simplement pu crer QPrinter sur la pile dans printImage(), mais les paramtres de lutilisateur auraient t perdus entre deux impressions.
Nous crons un QPrintDialog et nous invoquons exec() pour lafficher. Il retourne true si
lutilisateur a cliqu sur le bouton OK ; sinon il retourne false. Aprs lappel de exec(),
lobjet QPrinter est prt tre utilis. (Il est aussi possible dimprimer sans utiliser QPrintDialog, en appelant directement des fonctions membres de QPrinter pour configurer les
divers aspects.)
Nous crons ensuite un QPainter pour dessiner sur le QPrinter. Nous dfinissons la fentre
en rectangle de limage et le viewport en un rectangle du mme format dimage, puis nous
dessinons limage la position (0, 0).
Par dfaut, la fentre de QPainter est initialise de sorte que limprimante semble avoir une
rsolution similaire lcran (en gnral entre 72 et 100 points par pouce), ce qui facilite la
rutilisation du code de dessin du widget pour limpression. Ici, ce ntait pas un problme,
parce que nous avons dfini notre propre fentre.
Imprimer des lments qui ne stendent pas sur plus dune page se rvle trs simple, mais
de nombreuses applications ont besoin dimprimer plusieurs pages. Pour celles-ci, nous
devons dessiner une page la fois et appeler newPage() pour passer la page suivante.
Chapitre 8
Graphiques 2D et 3D
201
Ceci soulve un problme : dterminer la quantit dinformations que nous pouvons imprimer sur chaque page. Il existe deux approches principales pour grer les documents multipages
avec Qt :
Nous pouvons convertir nos donnes en format HTML et les afficher avec QTextDocument, le
moteur de texte de Qt.
Nous pouvons effectuer le dessin et la rpartition sur les pages manuellement.
Nous allons analyser les deux approches. En guise dexemple, nous allons imprimer un guide
des fleurs : une liste de noms de fleurs, chacun comprenant une description sous forme de
texte. Chaque entre du guide est stocke sous forme de chane au format "nom: description,"
par exemple :
Vu que les donnes relatives chaque fleur sont reprsentes par une seule chane, nous
pouvons reprsenter toutes les fleurs dans le guide avec un QStringList. Voici la fonction qui
imprime le guide des fleurs au moyen du moteur de texte de Qt :
void PrintWindow::printFlowerGuide(const QStringList &entries)
{
QString html;
foreach (QString entry, entries) {
QStringList fields = entry.split(": ");
QString title = Qt::escape(fields[0]);
QString body = Qt::escape(fields[1]);
html += "<table width=\"100%\" border=1 cellspacing=0>\n"
"<tr><td bgcolor=\"lightgray\"><font size=\"+1\">"
"<b><i>" + title + "</i></b></font>\n<tr><td>" + body
+ "\n</table>\n<br>\n";
}
printHtml(html);
}
La premire tape consiste convertir QStringList en format HTML. Chaque fleur est reprsente par un tableau HTML avec deux cellules. Nous excutons Qt::escape() pour remplacer
les caractres spciaux "&", "<", ">" par les entits HTML correspondantes ("&", "<",
">"). Nous appelons ensuite printHtml() pour imprimer le texte.
void PrintWindow::printHtml(const QString &html)
{
QPrintDialog printDialog(&printer, this);
if (printDialog.exec()) {
QPainter painter(&printer);
QTextDocument textDocument;
textDocument.setHtml(html);
textDocument.print(&printer);
}
}
202
Aponogeton distachyos
Trapa natans
The Cape pondweed (water hawthorn) is a deciduous perennial that has floating, oblong,
dark green leaves which are sometimes splashed purple. The waxy-white flowers have a
characteristic 'forked' appearance, sweet scent and black stamens. They appear from early
spring until fall. They grow in deep or shallow water and spread to 1.2 m.
The Jesuit's nut (or water chestnut) has mid-green diamond-shaped leaves with deeply
toothed edges that grow in neat rosettes. The center of each leaf is often marked with deep
purple blotches. White flowers are produced in summer. Each floating plant can spread to 23
cm.
Cabomba caroliniana
Zantedeschia aethiopica
The Fish grass (or fanwort or Washington grass) is a useful oxygenator for ponds. It is a
deciduous or semi-evergreen submerged perennial that is used by fish as a source of food
and as a place in which to spawn. Plants form spreading hummocks of fan-shaped, coarsly
divided leaves which are bright green. Tiny white flowers appear in the summer.
The Arum lily is a South African native that grows well in shallow water. It flowers throughout
the summer, with the erect funnel-shaped spathes being held well above the arrow-shaped
glossy, deep green leaves. Each spathe surrounds a central yellow spadix. The leaves and
flowering stems arise from a tuber. Plants can reach up to 90 cm in height, spreading to 45
cm.
Caltha palustris
The Marsh marigold (or kingcup) is a deciduous perennial that grows in shallow water around
the edges of ponds. It is equally well suited to a bog garden, moist rock garden or
herbaceous border. The rounded dark green leaves set off its large, cup-shaped
golden-yellow flowers. Plants can grow to 60 cm in height, with a spread of 45 cm. The
double-flowered cultivar 'Flore Plena' only reaches 10 cm.
Ceratophyllum demersum
The Hornwort is a deciduous perennial that produces feathery submerged foliage. It
sometimes floats and spreads over a large area. It is a good oxygenator and grows best in
cool deep water. It has no roots.
Nuphar lutea
The Yellow water lily has small (6 cm diameter) yellow flowers that are bottle-shaped and
sickly smelling. They are held above a mat of broad, oval, mid-green leaves which are about
40 cm wide, giving the plant a spread of up to 1.5 m. The seed heads are rounded and warty.
This hardy deciduous perennial thrives in deep water, in sun or shade, and is useful for a
water-lily effect where Nymphaea will not grow.
Orontium aquaticum
The Golden club's flowers lack the spathe typical of other aroids, leaving the central yellow
and white spadix to provide color. A deciduous perennial, the golden club grows equally well
in shallow or deep water. In spring, the pencil-like flower spikes (spadices) emerge from
among the floating mass of waxy leaves which are a bluish or greyish green. Plants grow to
25 cm high spreading up to 60 cm. Large seeds develop later in the summer and are used to
propagate plants while they are still fresh.
Aprs avoir configur limprimante et construit le painter, nous appelons la fonction paginate() pour dterminer quelle entre doit apparatre sur quelle page. Vous obtenez donc une
liste de QStringList, chacun contenant les entres dune page. Nous transmettons ce rsultat
printPages().
Chapitre 8
Graphiques 2D et 3D
203
Supposons, par exemple, que le guide des fleurs contient 6 entres, que nous appellerons A, B,
C, D, E et F. Imaginons maintenant quil y a suffisamment de place pour A et B sur la premire
page, pour C, D et E sur la deuxime page et pour F sur la troisime page. La liste pages
contiendrait donc la liste [A, B] la position dindex 0, la liste [C, D, E] la position
dindex 1 et la liste [F] la position dindex 2.
void PrintWindow::paginate(QPainter *painter, QList<QStringList> *pages,
const QStringList &entries)
{
QStringList currentPage;
int pageHeight = painter->window().height() - 2 * LargeGap;
int y = 0;
foreach (QString entry, entries) {
int height = entryHeight(painter, entry);
if (y + height > pageHeight &&!currentPage.empty()) {
pages->append(currentPage);
currentPage.clear();
y = 0;
}
currentPage.append(entry);
y += height + MediumGap;
}
if (!currentPage.empty())
pages->append(currentPage);
}
La fonction paginate() rpartit les entres du guide des fleurs sur les pages. Elle se base sur
la fonction entryHeight() qui calcule la hauteur dune entre. Elle tient galement compte
des espaces vides verticaux en haut et en bas de la page, de taille LargeGap.
Nous parcourons les entres et nous les ajoutons la page en cours jusqu ce quune entre
nait plus suffisamment de place sur cette page ; puis nous ajoutons la page en cours la liste
pages et nous commenons une nouvelle page.
int PrintWindow::entryHeight(QPainter *painter, const QString &entry)
{
QStringList fields = entry.split(": ");
QString title = fields[0];
QString body = fields[1];
int textWidth = painter->window().width() - 2 * SmallGap;
int maxHeight = painter->window().height();
painter->setFont(titleFont);
QRect titleRect = painter->boundingRect(0, 0, textWidth, maxHeight,
Qt::TextWordWrap, title);
painter->setFont(bodyFont);
QRect bodyRect = painter->boundingRect(0, 0, textWidth, maxHeight,
Qt::TextWordWrap, body);
return titleRect.height() + bodyRect.height() + 4 * SmallGap;
}
204
SmallGap
SmallGap
SmallGap
Titre
SmallGap
SmallGap
Corps
MediumGap
SmallGap
Chapitre 8
Graphiques 2D et 3D
205
Aponogeton distachyos
Nuphar lutea
The Cape pondweed (water hawthorn) is a deciduous perennial that has floating, oblong, dark green
leaves which are sometimes splashed purple. The waxy-white flowers have a characteristic 'forked'
appearance, sweet scent and black stamens. They appear from early spring until fall. They grow in
deep or shallow water and spread to 1.2 m.
The Yellow water lily has small (6 cm diameter) yellow flowers that are bottle-shaped and sickly
smelling. They are held above a mat of broad, oval, mid-green leaves which are about 40 cm wide,
giving the plant a spread of up to 1.5 m. The seed heads are rounded and warty. This hardy deciduous
perennial thrives in deep water, in sun or shade, and is useful for a water-lily effect where Nymphaea
will not grow.
Cabomba caroliniana
Orontium aquaticum
The Fish grass (or fanwort or Washington grass) is a useful oxygenator for ponds. It is a deciduous or
semi-evergreen submerged perennial that is used by fish as a source of food and as a place in which to
spawn. Plants form spreading hummocks of fan-shaped, coarsly divided leaves which are bright green.
Tiny white flowers appear in the summer.
The Golden club's flowers lack the spathe typical of other aroids, leaving the central yellow and white
spadix to provide color. A deciduous perennial, the golden club grows equally well in shallow or deep
water. In spring, the pencil-like flower spikes (spadices) emerge from among the floating mass of waxy
leaves which are a bluish or greyish green. Plants grow to 25 cm high spreading up to 60 cm. Large
seeds develop later in the summer and are used to propagate plants while they are still fresh.
Caltha palustris
The Marsh marigold (or kingcup) is a deciduous perennial that grows in shallow water around the
edges of ponds. It is equally well suited to a bog garden, moist rock garden or herbaceous border.
The rounded dark green leaves set off its large, cup-shaped golden-yellow flowers. Plants can grow to
60 cm in height, with a spread of 45 cm. The double-flowered cultivar 'Flore Plena' only reaches 10 cm.
Trapa natans
The Jesuit's nut (or water chestnut) has mid-green diamond-shaped leaves with deeply toothed edges
that grow in neat rosettes. The center of each leaf is often marked with deep purple blotches. White
flowers are produced in summer. Each floating plant can spread to 23 cm.
Ceratophyllum demersum
The Hornwort is a deciduous perennial that produces feathery submerged foliage. It sometimes floats
and spreads over a large area. It is a good oxygenator and grows best in cool deep water. It has no
roots.
Zantedeschia aethiopica
The Arum lily is a South African native that grows well in shallow water. It flowers throughout the
summer, with the erect funnel-shaped spathes being held well above the arrow-shaped glossy, deep
green leaves. Each spathe surrounds a central yellow spadix. The leaves and flowering stems arise
from a tuber. Plants can reach up to 90 cm in height, spreading to 45 cm.
La boucle interne for parcourt les pages. Si la page nest pas la premire page, nous appelons
newPage() pour supprimer lancienne page de la mmoire et pour commencer dessiner sur
une nouvelle page. Nous invoquons printPage() pour dessiner chaque page.
void PrintWindow::printPage(QPainter *painter,
const QStringList &entries, int pageNumber)
{
painter->save();
206
painter->translate(0, LargeGap);
foreach (QString entry, entries) {
QStringList fields = entry.split(": ");
QString title = fields[0];
QString body = fields[1];
printBox(painter, title, titleFont, Qt::lightGray);
printBox(painter, body, bodyFont, Qt::white);
painter->translate(0, MediumGap);
}
painter->restore();
painter->setFont(footerFont);
painter->drawText(painter->window(),
Qt::AlignHCenter | Qt::AlignBottom,
QString::number(pageNumber));
}
La fonction printPage() parcourt toutes les entres du guide des fleurs et les imprime grce
deux appels de printBox(): un pour le titre (le nom de la fleur) et un pour le corps (sa
description). Elle dessine galement le numro de page centr au bas de la page (voir
Figure 8.16).
Figure 8.16
La disposition dune page
du guide des fleurs
(0, 0)
LargeGap
page Height
Large Gap
Zone d'impression
des entres de fleur
[Numro de page]
Fentre
Chapitre 8
Graphiques 2D et 3D
207
La fonction printBox() trace les contours dune bote, puis dessine le texte lintrieur.
Dessiner des graphiques avec OpenGL dans une application Qt se rvle trs facile : vous
devez driver QGLWidget, rimplmenter quelques fonctions virtuelles et relier lapplication aux bibliothques QtOpenGL et OpenGL. Etant donn que QGLWidget hrite de
QWidget, la majorit des notions que nous connaissons dj peuvent sappliquer ce cas.
La principale diffrence cest que nous utilisons des fonctions OpenGL standards pour dessiner
en lieu et place de QPainter.
208
Pour vous montrer comment cela fonctionne, nous allons analyser le code de lapplication
Tetrahedron prsente en Figure 8.17. Lapplication prsente un ttradre en 3D, ou une
matrice quatre faces, chaque face ayant une couleur diffrente. Lutilisateur peut faire pivoter
le ttradre en appuyant sur un bouton de la souris et en le faisant glisser. Il peut aussi dfinir la
couleur dune face en double-cliquant dessus et en choisissant une couleur dans le QColorDialog qui souvre.
class Tetrahedron: public QGLWidget
{
Q_OBJECT
public:
Tetrahedron(QWidget *parent = 0);
protected:
void initializeGL();
void resizeGL(int width, int height);
void paintGL();
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
private:
void draw();
int faceAtPosition(const QPoint &pos);
GLfloat rotationX;
GLfloat rotationY;
GLfloat rotationZ;
QColor faceColors[4];
QPoint lastPos;
};
Chapitre 8
Graphiques 2D et 3D
209
La fonction initializeGL() est invoque une seule fois avant que paintGL() soit appele.
Cest donc dans le code de cette fonction que nous allons configurer le contexte daffichage
OpenGL, dfinir les listes daffichage et accomplir dautres initialisations.
Tout le code est en OpenGL standard, sauf lappel de la fonction qglClearColor() de
QGLWidget. Si nous voulions uniquement du code OpenGL standard, nous aurions pu appeler
glClearColor() en mode RGBA et glClearIndex() en mode table des couleurs.
void Tetrahedron::resizeGL(int width, int height)
{
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLfloat x = GLfloat(width) / height;
glFrustum(-x, x, -1.0, 1.0, 4.0, 15.0);
glMatrixMode(GL_MODELVIEW);
}
La fonction resizeGL() est invoque avant le premier appel de paintGL(), mais aprs
lappel de initializeGL(). Elle est aussi invoque ds que le widget est redimensionn.
Cest l que nous avons la possibilit de configurer le viewport OpenGL, la projection et tout
autre paramtre qui dpend de la taille du widget.
void Tetrahedron::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw();
}
La fonction paintGL() est invoque ds que le widget doit tre redessin. Elle ressemble
QWidget::paintEvent(), mais des fonctions OpenGL remplacent les fonctions de QPainter.
Le dessin est effectu par la fonction prive draw().
void Tetrahedron::draw()
{
static const GLfloat P1[3]
static const GLfloat P2[3]
static const GLfloat P3[3]
static const GLfloat P4[3]
=
=
=
=
{
{
{
{
210
Dans draw(), nous dessinons le ttradre, en tenant compte des rotations x, y et z et des
couleurs conserves dans le tableau faceColors. Tout est en code OpenGL standard, sauf
lappel de qglColor(). Nous aurions pu choisir plutt une des fonctions OpenGL
glColor3d() ou glIndex() en fonction du mode.
void Tetrahedron::mousePressEvent(QMouseEvent *event)
{
lastPos = event->pos();
}
void Tetrahedron::mouseMoveEvent(QMouseEvent *event)
{
GLfloat dx = GLfloat(event->x() - lastPos.x()) / width();
GLfloat dy = GLfloat(event->y() - lastPos.y()) / height();
if (event->buttons() & Qt::LeftButton) {
rotationX += 180 * dy;
rotationY += 180 * dx;
updateGL();
} else if (event->buttons() & Qt::RightButton) {
rotationX += 180 * dy;
rotationZ += 180 * dx;
updateGL();
}
lastPos = event->pos();
}
Les fonctions mousePressEvent() et mouseMoveEvent() sont rimplmentes dans QWidget pour permettre lutilisateur de faire pivoter la vue en cliquant dessus et en la faisant glisser.
Chapitre 8
Graphiques 2D et 3D
211
Le bouton gauche de la souris contrle la rotation autour des axes x et y, et le bouton droit
autour des axes x et z.
Aprs avoir modifi la variable rotationX et une des variables rotationY ou rotationZ,
nous appelons updateGL() pour redessiner la scne.
void Tetrahedron::mouseDoubleClickEvent(QMouseEvent *event)
{
int face = faceAtPosition(event->pos());
if (face!= -1) {
QColor color = QColorDialog::getColor(faceColors[face], this);
if (color.isValid()) {
faceColors[face] = color;
updateGL();
}
}
}
212
if (!glRenderMode(GL_RENDER))
return -1;
return buffer[3];
}
Lapplication Tetrahedron est termine. Pour plus dinformations sur le module QtOpenGL,
consultez la documentation de rfrence de QGLWidget, QGLFormat, QGLContext, QGLColormap et QGLPixelBuffer.
9
Glisser-dposer
Au sommaire de ce chapitre
Activer le glisser-dposer
Prendre en charge les types personnaliss
de glisser
Grer le presse-papiers
214
Activer le glisser-dposer
Le glisser-dposer implique deux actions distinctes : glisser et dposer. On peut faire glisser et/ou
dposer des lments sur les widgets Qt.
Notre premier exemple vous prsente comment faire accepter une application Qt un glisser
initi par une autre application. Lapplication Qt est une fentre principale avec un widget
central QTextEdit. Quand lutilisateur fait glisser un fichier texte du bureau ou de lexplorateur
de fichiers vers lapplication, celle-ci charge le fichier dans le QTextEdit.
Voici la dfinition de la classe MainWindow de notre exemple :
class MainWindow: public QMainWindow
{
Q_OBJECT
public:
MainWindow();
protected:
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
private:
bool readFile(const QString &fileName);
QTextEdit *textEdit;
};
Dans le constructeur, nous crons un QTextEdit et nous le dfinissons comme widget central.
Par dfaut, QTextEdit accepte des glisser sous forme de texte provenant dautres applications,
et si lutilisateur y dpose un fichier, le nom de fichier sera intgr dans le texte. Les vnements drop tant transmis de lenfant au parent, nous obtenons les vnements drop pour
toute la fentre dans MainWindow en dsactivant le dposer dans QTextEdit et en lactivant
dans la fentre principale.
Chapitre 9
Glisser-dposer
215
dragEnterEvent() est appele ds que lutilisateur fait glisser un objet sur un widget. Si
nous invoquons acceptProposedAction() sur lvnement, nous indiquons que lutilisateur
est en mesure de dposer cet objet sur ce widget. Par dfaut, le widget naccepterait pas le glisser.
Qt modifie automatiquement le pointeur pour signaler lutilisateur si le widget est en mesure
daccepter le dpt.
Dans notre cas, nous voulons que lutilisateur puisse faire glisser des fichiers, mais rien
dautre. Pour ce faire, nous vrifions le type MIME du glisser. Le type MIME text/uri-list
est utilis pour stocker une liste dURI (universal resource identi?er), qui peuvent tre des
noms de fichiers, des URL (comme des chemins daccs HTTP ou FTP) ou dautres identifiants globaux de ressource. Les types MIME standards sont dfinis par lIANA (Internet Assigned Numbers Authority). Ils sont constitus dun type et dun sous-type spars par un slash.
Les types MIME sont employs par le presse-papiers et par le systme du glisser-dposer pour
identifier les diffrents types de donnes. La liste officielle des types MIME est disponible
ladresse suivante : http://www.iana.org/assignments/media-types/.
void MainWindow::dropEvent(QDropEvent *event)
{
QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty())
return;
QString fileName = urls.first().toLocalFile();
if (fileName.isEmpty())
return;
if (readFile(fileName))
setWindowTitle(tr("%1 - %2").arg(fileName)
.arg(tr("Drag File")));
}
dropEvent() est appele ds que lutilisateur dpose un objet sur un widget. Nous appelons
QMimeData::urls() pour obtenir une liste des QUrl. En gnral, les utilisateurs ne font glisser quun seul fichier la fois, mais il est possible den faire glisser plusieurs en mme temps
grce une slection. Sil y a plusieurs URL ou si lURL ne correspond pas un nom de
fichier local, nous retournons immdiatement.
QWidget propose aussi dragMoveEvent() et dragLeaveEvent(), mais ces fonctions nont
gnralement pas besoin dtre rimplmentes.
Le deuxime exemple illustre la faon dinitier un glisser et daccepter un dposer.
Nous allons crer une sous-classe QListWidget qui prend en charge le glisser-dposer et
216
Lapplication Project Chooser prsente lutilisateur deux widgets liste remplis de noms.
Chaque widget reprsente un projet. Lutilisateur peut faire glisser et dposer les noms dans les
widgets liste pour dplacer une personne dun projet un autre.
Le code du glisser-dposer se situe en globalit dans la sous-classe QListWidget. Voici la
dfinition de classe :
class ProjectListWidget: public QListWidget
{
Q_OBJECT
public:
ProjectListWidget(QWidget *parent = 0);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
private:
void startDrag();
QPoint startPos;
};
Chapitre 9
Glisser-dposer
217
Quand lutilisateur appuie sur le bouton gauche de la souris, nous stockons lemplacement de
cette dernire dans la variable prive startPos. Nous appelons limplmentation de mousePressEvent() du QListWidget pour nous assurer que ce dernier a la possibilit de traiter
des vnements "bouton souris enfonc" comme dhabitude.
void ProjectListWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - startPos).manhattanLength();
if (distance >= QApplication::startDragDistance())
startDrag();
}
QListWidget::mouseMoveEvent(event);
}
Dans startDrag(), nous crons un objet de type QDrag, this tant son parent. Lobjet
QDrag enregistre les donnes dans un objet QMimeData. Dans notre exemple, nous fournissons
les donnes sous forme de chane text/plain au moyen de QMimeData::setText().
QMimeData propose plusieurs fonctions permettant de grer les types les plus courants de glisser
(images, URL, couleurs, etc.) et peut grer des types MIME arbitraires reprsents comme
218
tant des QByteArray. Lappel de QDrag::setPixmap() dfinit licne qui suit le pointeur
pendant le glisser.
Lappel de QDrag::start() dbute le glisser et se bloque jusqu ce que lutilisateur dpose
ou annule le glisser. Elle reoit en argument une combinaison des glisser pris en charge
(Qt::CopyAction, Qt::MoveAction et Qt::LinkAction) et retourne le glisser qui a t
excut (ou Qt::IgnoreAction si aucun glisser na t excut). Laction excute dpend de
ce que le widget source autorise, de ce que la cible supporte et des touches de modification
enfonces au moment de dposer. Aprs lappel de start(), Qt prend possession de lobjet
gliss et le supprimera quand il ne sera plus ncessaire.
void ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source!= this) {
event->setDropAction(Qt::MoveAction);
event->accept();
}
}
Le code dans dragMoveEvent() est identique ce que nous effectu dans dragEnterEvent(). Il est ncessaire parce que nous devons remplacer limplmentation de la fonction
dans QListWidget (en fait dans QAbstractItemView).
void ProjectListWidget::dropEvent(QDropEvent *event)
{
ProjectListWidget *source =
qobject_cast<ProjectListWidget *>(event->source());
if (source && source!= this) {
addItem(event->mimeData()->text());
event->setDropAction(Qt::MoveAction);
Chapitre 9
Glisser-dposer
219
event->accept();
}
}
220
Chapitre 9
Glisser-dposer
221
result.replace("\n", "\n<tr><td>");
result.prepend("<table>\n<tr><td>");
result.append("\n</table>");
return result;
}
Les fonctions toCsv() et toHtml() convertissent une chane "tabulations et sauts de ligne"
en une chane CSV (comma-separated values, valeurs spares par des virgules) ou HTML.
Par exemple, les donnes
Red
Cyan
Green
Yellow
Blue
Magenta
sont converties en
"Red",
"Cyan",
"Green",
"Yellow",
"Blue"
"Magenta"
ou en
<table>
<tr><td>Red<td>Green<td>Blue
<tr><td>Cyan<td>Yellow<td>Magenta
</table>
Mme si nous fournissons les donnes dans trois formats diffrents, nous nacceptons que deux
dentre eux dans dropEvent(). Si lutilisateur fait glisser des cellules depuis un QTableWidget vers un diteur HTML, nous voulons que les cellules soient converties en un tableau
HTML. Mais si lutilisateur fait glisser un code HTML arbitraire vers un QTableWidget, nous
ne voulons pas laccepter.
Pour que cet exemple fonctionne, nous devons galement appeler setAcceptDrops(true) et
setSelectionMode(ContiguousSelection) dans le constructeur de MyTableWidget.
222
Nous allons refaire notre exemple, mais cette fois-ci nous driverons QMimeData pour ajourner
ou viter les conversions (potentiellement onreuses en termes de performances) entre
QTableWidgetItem et QByteArray. Voici la dfinition de notre sous-classe :
class TableMimeData: public QMimeData
{
Q_OBJECT
public:
TableMimeData(const QTableWidget *tableWidget,
const QTableWidgetSelectionRange &range);
const QTableWidget *tableWidget() const { return myTableWidget; }
QTableWidgetSelectionRange range() const { return myRange; }
QStringList formats() const;
protected:
QVariant retrieveData(const QString &format,
QVariant::Type preferredType) const;
private:
static QString toHtml(const QString &plainText);
static QString toCsv(const QString &plainText);
QString text(int row, int column) const;
QString rangeAsPlainText() const;
const QTableWidget *myTableWidget;
QTableWidgetSelectionRange myRange;
QStringList myFormats;
};
Chapitre 9
Glisser-dposer
223
La fonction formats() retourne une liste de types MIME fournie par lobjet MIME. Lordre
prcis des formats nest gnralement pas important, mais il est recommand de placer les
"meilleurs" formats en premier. Il arrive en effet que les applications qui prennent en charge de
nombreux formats choisissent le premier qui convient.
QVariant TableMimeData::retrieveData(const QString &format,
QVariant::Type preferredType) const
{
if (format == "text/plain") {
return rangeAsPlainText();
} else if (format == "text/csv") {
return toCsv(rangeAsPlainText());
} else if (format == "text/html") {
return toHtml(rangeAsPlainText());
} else {
return QMimeData::retrieveData(format, preferredType);
}
}
La fonction retrieveData() retourne les donnes dun type MIME particulier sous forme de
QVariant. La valeur du paramtre de format correspond normalement une des chanes
retournes par formats(), mais nous ne pouvons pas en attester, tant donn que toutes les
applications ne comparent pas le type MIME formats(). Les fonctions daccs text(),
html(), urls(), imageData(), colorData() et data() proposes par QMimeData sont
implmentes en termes de retrieveData().
Le paramtre preferredType est un bon indicateur du type que nous devrions placer dans
QVariant. Ici, nous lignorons et nous faisons confiance QMimeData pour convertir la valeur
de retour dans le type souhait, si ncessaire.
void MyTableWidget::dropEvent(QDropEvent *event)
{
const TableMimeData *tableData =
qobject_cast<const TableMimeData *>(event->mimeData());
if (tableData) {
const QTableWidget *otherTable = tableData->tableWidget();
QTableWidgetSelectionRange otherRange = tableData->range();
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/csv")) {
QByteArray csvData = event->mimeData()->data("text/csv");
QString csvText = QString::fromUtf8(csvData);
...
event->acceptProposedAction();
} else if (event->mimeData()->hasFormat("text/plain")) {
QString plainText = event->mimeData()->text();
...
event->acceptProposedAction();
}
QTableWidget::mouseMoveEvent(event);
}
224
La fonction dropEvent() ressemble celle prsente prcdemment dans cette section, mais
cette fois-ci nous loptimisons en vrifiant dabord si nous pouvons convertir en toute scurit
lobjet QMimeData en TableMimeData. Si qobject_cast<T>() fonctionne, cela signifie
quun MyTableWidget de la mme application tait lorigine du glisser, et nous pouvons
directement accder aux donnes de la table au lieu de passer par lAPI de QMimeData. Si la
conversion choue, nous extrayons les donnes de faon habituelle.
Dans cet exemple, nous avons cod le texte CSV avec le format de codage UTF-8. Si nous
voulions tre srs dutiliser le bon codage, nous aurions pu utiliser le paramtre charset du
type MIME text/plain de sorte spcifier un codage explicite. Voici quelques exemples :
text/plain;charset=US-ASCII
text/plain;charset=ISO-8859-1
text/plain;charset=Shift_JIS
text/plain;charset=UTF-8
Grer le presse-papiers
La plupart des applications emploient la gestion intgre du presse-papiers de Qt dune
manire ou dune autre. Par exemple, la classe QTextEdit propose les slots cut(), copy() et
paste(), de mme que des raccourcis clavier. Vous navez donc pas besoin de code supplmentaire, ou alors trs peu.
Quand vous crivez vos propres classes, vous pouvez accder au presse-papiers par le biais de
QApplication::clipboard(), qui retourne un pointeur vers lobjet QClipboard de lapplication. Grer le presse-papiers savre assez facile : vous appelez setText(), setImage() ou
setPixmap() pour placer des donnes dans le presse-papiers, puis vous appelez text(),
image() ou pixmap() pour y rcuprer les donnes. Nous avons dj analys des exemples
dutilisation du presse-papiers dans lapplication Spreadsheet du Chapitre 4.
Pour certaines applications, la fonctionnalit intgre peut tre insuffisante. Par exemple, nous
voulons pouvoir fournir des donnes qui ne soient pas uniquement du texte ou une image, ou
proposer des donnes en diffrents formats pour un maximum dinteroprabilit avec dautres
applications. Ce problme ressemble beaucoup ce que nous avons rencontr prcdemment
avec le glisser-dposer et la rponse est similaire : nous pouvons driver QMimeData et rimplmenter quelques fonctions virtuelles.
Si notre application prend en charge le glisser-dposer via une sous-classe QMimeData personnalise, nous pouvons simplement rutiliser cette sous-classe et la placer dans le presse-papiers
en employant la fonction setMimeData(). Pour rcuprer les donnes, nous avons la possibilit dinvoquer mimeData() sur le presse-papiers.
Sous X11, il est habituellement possible de coller une slection en cliquant sur le bouton du
milieu dune souris dote de trois boutons. Vous faites alors appel un presse-papiers de
"slection" distinct. Si vous souhaitez que vos widgets supportent ce genre de presse-papiers
en complment du presse-papiers standard, vous devez transmettre QClipboard::Selection
Chapitre 9
Glisser-dposer
225
comme argument supplmentaire aux divers appels du presse-papiers. Voici par exemple comment
nous rimplmenterions mouseReleaseEvent() dans un diteur de texte pour prendre en
charge le collage avec le bouton du milieu de la souris :
void MyTextEditor::mouseReleaseEvent(QMouseEvent *event)
{
QClipboard *clipboard = QApplication::clipboard();
if (event->button() == Qt::MidButton
&& clipboard->supportsSelection()) {
QString text = clipboard->text(QClipboard::Selection);
pasteText(text);
}
}
Sous X11, la fonction supportsSelection() retourne true. Sur les autres plates-formes,
elle renvoie false.
Si vous voulez tre inform ds que le contenu du presse-papiers change, vous pouvez connecter
le signal QClipboard::dataChanged() un slot personnalis.
10
Classes daffichage
dlments
Au sommaire de ce chapitre
Utiliser les classes ddies laffichage dlments
Utiliser des modles prdfinis
Implmenter des modles personnaliss
Implmenter des dlgus personnaliss
228
moment donn, les modifications taient sauvegardes dans la source de donnes. Mme si elle
est simple comprendre et utiliser, cette approche nest pas adapte aux trs grands ensembles de donnes, ni pour laffichage du mme ensemble de donnes dans deux ou plusieurs
widgets diffrents.
Le langage Smalltalk a fait connatre une mthode flexible permettant de visualiser de grands
ensembles de donnes : MVC (Modle-Vue-Contrleur). Dans lapproche MVC, le modle
reprsente lensemble de donnes et il se charge de rcuprer les donnes ncessaires pour afficher et enregistrer toute modification. Chaque type densemble de donnes possde son propre
modle, mais lAPI que les modles proposent aux vues est identique quel que soit lensemble
de donnes sous-jacent. La vue prsente les donnes lutilisateur. Seule une quantit limite
de donnes dun grand ensemble sera visible en mme temps, cest--dire celles demandes
par la vue. Le contrleur sert dintermdiaire entre lutilisateur et la vue ; il convertit les
actions utilisateur en requtes pour rechercher ou modifier des donnes, que la vue transmet
ensuite au modle si ncessaire.
Figure 10.1
Larchitecture
modle/vue de Qt
Dlgu
Source de donnes
Modle
Vue
Qt propose une architecture modle/vue inspire de lapproche MVC (voir Figure 10.1).
Dans Qt, le modle se comporte de la mme manire que pour le MVC classique. Mais la
place du contrleur, Qt utilise une notion lgrement diffrente : le dlgu. Le dlgu offre
un contrle prcis de la manire dont les lments sont affichs et modifis. Qt fournit un dlgu par dfaut pour chaque type de vue. Cest suffisant pour la plupart des applications, cest
pourquoi nous navons gnralement pas besoin de nous en proccuper.
Grce larchitecture modle/vue de Qt, nous avons la possibilit dutiliser des modles qui ne
rcuprent que les donnes ncessaires laffichage de la vue. Nous grons donc de trs grands
ensembles de donnes beaucoup plus rapidement et nous consommons moins de mmoire que
si nous devions lire toutes les donnes. De plus, en enregistrant un modle avec deux vues ou
plus, nous donnons lopportunit lutilisateur dafficher et dinteragir avec les donnes de
diffrentes manires, avec peu de surcharge (voir Figure 10.2). Qt synchronise automatiquement plusieurs vues, refltant les changements apports dans lune delles dans toutes les
autres. Larchitecture modle/vue prsente un autre avantage : si nous dcidons de modifier
la faon dont lensemble de donnes sous-jacent est enregistr, nous navons qu changer le
modle ; les vues continueront se comporter correctement.
En gnral, nous ne devons prsenter quun nombre relativement faible dlments
lutilisateur. Dans ces cas frquents, nous pouvons utiliser les classes daffichage dlments
de Qt (QListWidget, QTableWidget et QTreeWidget) spcialement conues cet effet et
directement y enregistrer des lments. Ces classes se comportent de manire similaire aux
Chapitre 10
229
classes daffichage dlments proposes par les versions antrieures de Qt. Elles stockent
leurs donnes dans des "lments" (par exemple, un QTableWidget contient des QTableWidgetItem). En interne, ces classes sappuient sur des modles personnaliss qui affichent les
lments destins aux vues.
Vue de liste 1
Vue de liste 2
Vue de liste 3
Vue tableau 4
Vue tableau 5
Modle
Source de donnes
Figure 10.2
Un modle peut desservir plusieurs vues
Sagissant des grands ensembles de donnes, la duplication des donnes est souvent peu
recommande. Dans ces cas, nous pouvons utiliser les vues de Qt (QListView, QTableView,
et QTreeView, ), en association avec un modle de donnes, qui peut tre un modle personnalis ou un des modles prdfinis de Qt. Par exemple, si lensemble de donnes se
trouve dans une base de donnes, nous pouvons combiner un QTableView avec un QSqlTableModel.
230
Figure 10.3
Lapplication
Flowchart Symbol Picker
Chapitre 10
231
La fonction done() est rimplmente dans QDialog. Elle est appele quand lutilisateur
appuie sur OK ou Cancel. Si lutilisateur a cliqu sur OK, nous rcuprons llment pertinent
et nous extrayons lID grce la fonction data(). Si nous tions intresss par le texte de
llment, nous aurions pu le rcuprer en invoquant item->data(Qt::DisplayRole).toString() ou, ce qui est plus pratique, item->text().
Par dfaut, QListWidget est en lecture seule. Si nous voulions que lutilisateur puisse modifier les lments, nous aurions pu dfinir les dclencheurs de modification de la vue au moyen
de QAbstractItemView::setEditTriggers(); par exemple, configurer QAbstractItemView::AnyKeyPressed signifie que lutilisateur peut modifier un lment simplement
en commenant taper quelque chose. Nous aurions aussi pu proposer un bouton Edit (ou
peut-tre des boutons Add et Delete) et les connecter aux slots, de sorte dtre en mesure de
grer les oprations de modification par programme.
Maintenant que nous avons vu comment utiliser une classe ddie laffichage dlments
pour afficher et slectionner des donnes, nous allons tudier un exemple o nous pouvons
modifier des donnes. Nous utilisons nouveau une bote de dialogue, mais cette fois-ci, elle
prsente un ensemble de coordonnes (x, y) que lutilisateur peut modifier (voir Figure 10.4).
232
Figure 10.4
Lapplication
Coordinate Setter
Comme pour lexemple prcdent, nous nous concentrerons sur le code daffichage de
llment, en commenant par le constructeur.
CoordinateSetter::CoordinateSetter(QList<QPointF> *coords,
QWidget *parent)
: QDialog(parent)
{
coordinates = coords;
tableWidget = new QTableWidget(0, 2);
tableWidget->setHorizontalHeaderLabels(
QStringList() << tr("X") << tr("Y"));
for (int row = 0; row < coordinates->count(); ++row) {
QPointF point = coordinates->at(row);
addRow();
tableWidget->item(row, 0)->setText(QString::number(point.x()));
tableWidget->item(row, 1)->setText(QString::number(point.y()));
}
...
}
Chapitre 10
233
Par dfaut, QTableWidget autorise les modifications. Lutilisateur peut modifier toute cellule
du tableau en la recherchant puis en appuyant sur F2 ou simplement en saisissant quelque
chose. Tous les changements effectus par lutilisateur dans la vue se reflteront automatiquement dans les QTableWidgetItem. Pour viter les modifications, nous avons la possibilit
dappeler setEditTriggers(QAbstractItemView::NoEditTriggers).
void CoordinateSetter::addRow()
{
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
QTableWidgetItem *item0 = new QTableWidgetItem;
item0->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 0, item0);
QTableWidgetItem *item1 = new QTableWidgetItem;
item1->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 1, item1);
tableWidget->setCurrentItem(item0);
}
Le slot addRow() est appel lorsque lutilisateur clique sur le bouton Add Row. Nous ajoutons
une nouvelle ligne laide de insertRow(). Si lutilisateur essaie de modifier une cellule
dans la nouvelle ligne, QTableWidget crera automatiquement un nouveau QTableWidgetItem.
void CoordinateSetter::done(int result)
{
if (result == QDialog::Accepted) {
coordinates->clear();
for (int row = 0; row < tableWidget->rowCount(); ++row) {
double x = tableWidget->item(row, 0)->text().toDouble();
double y = tableWidget->item(row, 1)->text().toDouble();
coordinates->append(QPointF(x, y));
}
}
QDialog::done(result);
}
Enfin, quand lutilisateur clique sur OK, nous effaons les coordonnes qui avaient t transmises la bote de dialogue et nous crons un nouvel ensemble bas sur les coordonnes des
lments du QTableWidget.
Pour notre troisime et dernier exemple de widget ddi laffichage dlments de Qt, nous
allons analyser quelques extraits de code dune application qui affiche les paramtres dune
application Qt grce un QTreeWidget (voir Figure 10.5). La lecture seule est loption par
dfaut de QTreeWidget.
234
Figure 10.5
Lapplication
Settings Viewer
Pour accder aux paramtres dune application, un objet QSettings doit tre cr avec le nom
de lorganisation et le nom de lapplication comme paramtres. Nous dfinissons des noms par
dfaut ("Designer" par "Trolltech"), puis nous construisons un nouveau QTreeWidget. Pour
terminer, nous appelons la fonction readSettings().
void SettingsViewer::readSettings()
{
QSettings settings(organization, application);
treeWidget->clear();
addChildSettings(settings, 0, "");
treeWidget->sortByColumn(0);
treeWidget->setFocus();
setWindowTitle(tr("Settings Viewer - %1 by %2")
.arg(application).arg(organization));
}
Chapitre 10
235
Les paramtres dapplication sont stocks dans une hirarchie de cls et de valeurs. La fonction prive addChildSettings() reoit un objet settings, un parent QTreeWidgetItem et
le "groupe" en cours. Un groupe est lquivalent QSettings dun rpertoire de systme de
fichiers. La fonction addChildSettings() peut sappeler elle-mme de manire rcursive
pour faire dfiler une arborescence arbitraire. Le premier appel de la fonction readSettings() transmet 0 comme lment parent pour reprsenter la racine.
void SettingsViewer::addChildSettings(QSettings &settings,
QTreeWidgetItem *parent, const QString &group)
{
QTreeWidgetItem *item;
settings.beginGroup(group);
foreach (QString key, settings.childKeys()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, key);
item->setText(1, settings.value(key).toString());
}
foreach (QString group, settings.childGroups()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, group);
addChildSettings(settings, item, group);
}
settings.endGroup();
}
La fonction addChildSettings() est utilise pour crer tous les QTreeWidgetItem. Elle
parcourt toutes les cls au niveau en cours dans la hirarchie des paramtres et cre un
QTableWidgetItem par cl. Si 0 est transmis en tant qulment parent, nous crons
llment comme tant un enfant de QTreeWidget (il devient donc un lment de haut
niveau) ; sinon, nous crons llment comme tant un enfant de parent. La premire colonne
correspond au nom de la cl et la seconde la valeur correspondante.
La fonction parcourt ensuite chaque groupe du niveau en cours. Pour chacun deux, un
nouveau QTreeWidgetItem est cr avec sa premire colonne dfinie en nom du groupe. Puis,
la fonction sappelle elle-mme de manire rcursive avec llment de groupe comme parent
pour alimenter le QTreeWidget avec les lments enfants du groupe.
Les widgets daffichage dlments prsents dans cette section nous permettent dutiliser un
style de programmation trs similaire celui utilis dans les versions antrieures de Qt : lire
tout un ensemble de donnes dans un widget daffichage dlments, utiliser les objets des
236
lments pour reprsenter les lments de donnes, et (si les lments sont modifiables) sauvegarder sur la source de donnes. Dans les sections suivantes, nous irons plus loin que cette
approche simple et nous profiterons pleinement de larchitecture modle/vue de Qt.
QStandardItemModel
QDirModel
QSqlQueryModel
QSqlTableModel
QSqlRelationalTableModel
QSortFilterProxyModel
Chapitre 10
237
: QDialog(parent)
{
model = new QStringListModel(this);
model->setStringList(leaders);
listView = new QListView;
listView->setModel(model);
listView->setEditTriggers(QAbstractItemView::AnyKeyPressed
| QAbstractItemView::DoubleClicked);
...
}
Nous crons et alimentons dabord un QStringListModel. Nous crons ensuite un QListView et nous lui affectons comme modle un de ceux que nous venons de crer. Nous configurons galement des dclencheurs de modification pour permettre lutilisateur de modifier une
chane simplement en commenant taper quelque chose ou en double-cliquant dessus. Par
dfaut, aucun dclencheur de modification nest dfini sur un QListView, la vue est donc
configure en lecture seule.
void TeamLeadersDialog::insert()
{
int row = listView->currentIndex().row();
model->insertRows(row, 1);
QModelIndex index = model->index(row);
listView->setCurrentIndex(index);
listView->edit(index);
}
Le slot insert() est invoqu lorsque lutilisateur clique sur le bouton Insert. Le slot
commence par rcuprer le numro de ligne de llment en cours dans la vue de liste. Chaque
lment de donnes dans un modle possde un "index de modle" correspondant qui est
reprsent par un objet QModelIndex. Nous allons tudier les index de modle plus en dtail
dans la prochaine section, mais pour linstant il suffit de savoir quun index comporte trois
composants principaux : une ligne, une colonne et un pointeur vers le modle auquel il appartient.
Pour un modle liste unidimensionnel, la colonne est toujours 0.
Lorsque nous connaissons le numro de ligne, nous insrons une nouvelle ligne cet endroit.
Linsertion est effectue sur le modle et le modle met automatiquement jour la vue de liste.
Nous dfinissons ensuite lindex en cours de la vue de liste sur la ligne vide que nous venons
dinsrer. Enfin, nous dfinissons la vue de liste en mode de modification sur la nouvelle ligne,
comme si lutilisateur avait appuy sur une touche ou double-cliqu pour initier la modification.
void TeamLeadersDialog::del()
{
model->removeRows(listView->currentIndex().row(), 1);
}
Dans le constructeur, le signal clicked() du bouton Delete est reli au slot del(). Vu que
nous avons supprim la ligne en cours, nous pouvons appeler removeRows() avec la position
238
actuelle dindex et un nombre de lignes de 1. Comme avec linsertion, nous nous basons sur le
modle pour mettre jour la vue de faon approprie.
QStringList TeamLeadersDialog::leaders() const
{
return model->stringList();
}
Enfin, la fonction leaders() procure un moyen de lire les chanes modifies quand la bote de
dialogue est ferme.
TeamLeadersDialog pourrait devenir une bote de dialogue gnrique de modification de
liste de chanes simplement en paramtrant le titre de sa fentre. Une autre bote de dialogue
gnrique souvent demande est une bote qui prsente une liste de fichiers ou de rpertoires
lutilisateur. Le prochain exemple exploite la classe QDirModel, qui encapsule le systme de
fichiers de lordinateur et qui peut afficher (et masquer) les divers attributs de fichiers. Ce
modle peut appliquer un filtre pour limiter les types dentres du systme de fichiers qui sont
affiches et peut organiser les entres de plusieurs manires diffrentes.
Figure 10.7
Lapplication
Directory Viewer
Nous analyserons dabord la cration et nous configurerons le modle et la vue dans le constructeur
de la bote de dialogue Directory Viewer (voir Figure 10.7).
DirectoryViewer::DirectoryViewer(QWidget *parent)
: QDialog(parent)
{
model = new QDirModel;
model->setReadOnly(false);
model->setSorting(QDir::DirsFirst | QDir::IgnoreCase | QDir::Name);
treeView = new QTreeView;
treeView->setModel(model);
treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
treeView->header()->setClickable(true);
Chapitre 10
239
Lorsque le modle a t construit, nous faisons le ncessaire pour quil puisse tre modifi et
nous dfinissons les divers attributs dordre de tri. Nous crons ensuite le QTreeView qui affichera les donnes du modle. Len-tte du QTreeView peut tre utilis pour proposer un tri
contrl par lutilisateur. Si cet en-tte est cliquable, lutilisateur est en mesure de trier
nimporte quelle colonne en cliquant sur ce dernier ; en cliquant plusieurs fois dessus, il choisit
entre les tris croissants et dcroissants. Une fois que len-tte de larborescence a t configur,
nous obtenons lindex de modle du rpertoire en cours et nous sommes srs que ce rpertoire
est visible en dveloppant ses parents si ncessaire laide de expand() et en le localisant
grce scrollTo(). Nous nous assurons galement que la premire colonne est suffisamment
grande pour afficher toutes les entres sans utiliser de points de suspension (...).
Dans la partie du code du constructeur qui nest pas prsente ici, nous avons connect les
boutons Create Directory (Crer un rpertoire) et Remove (Supprimer) aux slots pour effectuer
ces actions. Nous navons pas besoin de bouton Rename parce que les utilisateurs peuvent
renommer directement en appuyant sur F2 et en tapant du texte.
void DirectoryViewer::createDirectory()
{
QModelIndex index = treeView->currentIndex();
if (!index.isValid())
return;
QString dirName = QInputDialog::getText(this,
tr("Create Directory"),
tr("Directory name"));
if (!dirName.isEmpty()) {
if (!model->mkdir(index, dirName).isValid())
QMessageBox::information(this, tr("Create Directory"),
tr("Failed to create the directory"));
}
}
Si lutilisateur entre un nom de rpertoire dans la bote de dialogue, nous essayons de crer un
rpertoire avec ce nom comme enfant du rpertoire en cours. La fonction QDirModel::mkdir()
reoit lindex du rpertoire parent et le nom du nouveau rpertoire, et retourne lindex de
modle du rpertoire quil a cr. Si lopration choue, elle retourne un index de modle
invalide.
void DirectoryViewer::remove()
{
QModelIndex index = treeView->currentIndex();
240
if (!index.isValid())
return;
bool ok;
if (model->fileInfo(index).isDir()) {
ok = model->rmdir(index);
} else {
ok = model->remove(index);
}
if (!ok)
QMessageBox::information(this, tr("Remove"),
tr("Failed to remove %1").arg(model->fileName(index)));
}
Si lutilisateur clique sur Remove, nous tentons de supprimer le fichier ou le rpertoire associ
llment en cours. Pour ce faire, nous pourrions utiliser QDir, mais QDirModel propose des
fonctions pratiques qui fonctionnent avec QModelIndexes.
Le dernier exemple de cette section vous montre comment employer QSortFilterProxyModel. Contrairement aux autres modles prdfinis, ce modle encapsule un modle existant
et manipule les donnes qui sont transmises entre le modle sous-jacent et la vue. Dans notre
exemple, le modle sous-jacent est un QStringListModel initialis avec la liste des noms de
couleur reconnues par Qt (obtenue via QColor::colorNames()). Lutilisateur peut saisir une
chane de filtre dans un QLineEdit et spcifier la manire dont cette chane doit tre interprte (comme une expression rgulire, un modle gnrique ou une chane fixe) grce une
zone de liste droulante (voir Figure 10.8).
Figure 10.8
Lapplication
Color Names
Chapitre 10
241
proxyModel->setSourceModel(sourceModel);
proxyModel->setFilterKeyColumn(0);
listView = new QListView;
listView->setModel(proxyModel);
...
syntaxComboBox = new QComboBox;
syntaxComboBox->addItem(tr("Regular expression"), QRegExp::RegExp);
syntaxComboBox->addItem(tr("Wildcard"), QRegExp::Wildcard);
syntaxComboBox->addItem(tr("Fixed string"), QRegExp::FixedString);
...
}
242
sont Qt::EditRole et Qt::DisplayRole. Dautres rles sont utiliss pour des donnes
supplmentaires (par exemple Qt::ToolTipRole, Qt::StatusTipRole et Qt::WhatsThisRole) et dautres encore pour contrler les attributs daffichage de base (tels que
Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole et Qt::BackgroundColorRole).
Figure 10.9
Vue schmatique des
modles de Qt
Modle liste
racine
ligne
0
1
Modle de tableau
racine
ligne
Modle arborescence
racine
ligne
1
colonne 0
1
2
colonne
0 1 2
Pour un modle liste, le seul composant dindex pertinent est le nombre de lignes, accessible
depuis QModelIndex::row(). Pour un modle de tableau, les composants dindex pertinents
sont les nombres de lignes et de colonnes, accessibles depuis QModelIndex::row() et
QModelIndex::column(). Pour les modles liste et tableau, le parent de chaque lment est
la racine, qui est reprsente par un QModelIndex invalide. Les deux premiers exemples de
cette section vous montrent comment implmenter des modles de tableau personnaliss.
Un modle arborescence ressemble un modle de tableau, quelques diffrences prs.
Comme un modle de tableau, la racine est le parent des lments de haut niveau (un QModelIndex invalide), mais le parent de tout autre lment est un autre lment dans la hirarchie.
Les parents sont accessibles depuis QModelIndex::parent(). Chaque lment possde ses
donnes de rle et aucun ou plusieurs enfants, chacun tant un lment en soi. Vu que les
lments peuvent avoir dautres lments comme enfants, il est possible de reprsenter des
structures de donnes rcursives ( la faon dune arborescence), comme vous le montrera le
dernier exemple de cette section.
Le premier exemple de cette section est un modle de tableau en lecture seule qui affiche des
valeurs montaires en relation les unes avec les autres (voir Figure 10.10).
Lapplication pourrait tre implmente partir dun simple tableau, mais nous voulons nous
servir dun modle personnalis pour profiter de certaines proprits des donnes qui minimisent le stockage. Si nous voulions conserver les 162 devises actuellement cotes dans un
tableau, nous devrions stocker 162 162 = 26 244 valeurs ; avec le modle personnalis
prsent ci-aprs, nous nenregistrons que 162 valeurs (la valeur de chaque devise par rapport
au dollar amricain).
Chapitre 10
243
Figure 10.10
Lapplication Currencies
La classe CurrencyModel sera utilise avec un QTableView standard. Elle est alimente avec
un QMap<QString,double>; chaque cl correspond au code de la devise et chaque valeur
correspond la valeur de la devise en dollars amricains. Voici un extrait de code qui montre
comment le tableau de correspondance est aliment et comment le modle est utilis :
QMap<QString, double> currencyMap;
currencyMap.insert("AUD", 1.3259);
currencyMap.insert("CHF", 1.2970);
...
currencyMap.insert("SGD", 1.6901);
currencyMap.insert("USD", 1.0000);
CurrencyModel currencyModel;
currencyModel.setCurrencyMap(currencyMap);
QTableView tableView;
tableView.setModel(¤cyModel);
tableView.setAlternatingRowColors(true);
244
Nous avons choisi de driver QAbstractTableModel pour notre modle, parce que cela
correspond le plus notre source de donnes. Qt propose plusieurs classes de base de modle,
y compris QAbstractListModel, QAbstractTableModel et QAbstractItemModel (voir
Figure 10.11). La classe QAbstractItemModel est employe pour supporter une grande
varit de modles, dont ceux qui se basent sur des structures de donnes rcursives, alors que
les classes QAbstractListModel et QAbstractTableModel sont proposes pour une question
de commodit lors de lutilisation densembles de donnes une ou deux dimensions.
Figure 10.11
Arbre dhritage
des classes de modle
abstraites
QObject
QAbstractItemModel
QAbstractListModel
QAbstractTableModel
Pour un modle de tableau en lecture seule, nous devons rimplmenter trois fonctions :
rowCount(), columnCount() et data(). Dans ce cas, nous avons aussi rimplment
headerData() et nous fournissons une fonction pour initialiser les donnes (setCurrencyMap()).
CurrencyModel::CurrencyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
Nous navons pas besoin de faire quoi que ce soit dans le constructeur, sauf transmettre le paramtre parent la classe de base.
int CurrencyModel::rowCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}
int CurrencyModel::columnCount(const QModelIndex & /* parent */) const
{
return currencyMap.count();
}
Pour ce modle de tableau, les nombres de lignes et de colonnes correspondent aux nombres
de devises dans le tableau de correspondance des devises. Le paramtre parent na aucune
signification pour un modle de tableau ; il est prsent parce que rowCount() et columnCount() sont hrits de la classe de base QAbstractItemModel plus gnrique, qui prend en
charge les hirarchies.
QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
Chapitre 10
245
if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
QString rowCurrency = currencyAt(index.row());
QString columnCurrency = currencyAt(index.column());
if (currencyMap.value(rowCurrency) == 0.0)
return "####";
double amount = currencyMap.value(columnCurrency)
/ currencyMap.value(rowCurrency);
return QString("%1").arg(amount, 0, f, 4);
}
return QVariant();
}
La fonction data() retourne la valeur de nimporte quel rle dun lment. Llment est
spcifi sous forme de QModelIndex. Pour un modle de tableau, les composants intressants
dun QModelIndex sont ses nombres de lignes et de colonnes, disponibles grce row() et
column().
Si le rle est Qt::TextAlignmentRole, nous retournons un alignement adapt aux nombres.
Si le rle daffichage est Qt::DisplayRole, nous recherchons la valeur de chaque devise et
nous calculons le taux de change.
Nous pourrions retourner la valeur calcule sous forme de type double, mais nous naurions
aucun contrle sur le nombre de chiffres aprs la virgule ( moins dutiliser un dlgu personnalis). Nous retournons donc plutt la valeur sous forme de chane, mise en forme comme
nous le souhaitons.
QVariant CurrencyModel::headerData(int section,
Qt::Orientation /* orientation */,
int role) const
{
if (role!= Qt::DisplayRole)
return QVariant();
return currencyAt(section);
}
La fonction headerData() est appele par la vue pour alimenter ses en-ttes verticaux et horizontaux. Le paramtre section correspond au nombre de lignes ou de colonnes (selon lorientation). Vu que les lignes et les colonnes ont les mmes codes de devise, nous ne nous soucions
pas de lorientation et nous retournons simplement le code de la devise pour le numro de
section donn.
void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
{
currencyMap = map;
reset();
}
246
Lappelant peut modifier le tableau de correspondance des devises en excutant setCurrencyMap(). Lappel de QAbstractItemModel::reset() informe nimporte quelle vue qui
utilise le modle que toutes leurs donnes sont invalides ; ceci les oblige demander des
donnes actualises pour les lments visibles.
QString CurrencyModel::currencyAt(int offset) const
{
return (currencyMap.begin() + offset).key();
}
Chapitre 10
247
Nous devons rimplmenter les mmes fonctions que pour lexemple prcdent. De plus, nous
devons aussi rimplmenter setData() et flags() pour que le modle puisse tre modifi.
Voici la dfinition de classe :
class CityModel: public QAbstractTableModel
{
Q_OBJECT
public:
CityModel(QObject *parent = 0);
void setCities(const QStringList &cityNames);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value,
int role);
QVariant headerData(int section, Qt::Orientation orientation,
int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
private:
int offsetOf(int row, int column) const;
QStringList cities;
QVector<int> distances;
};
Pour ce modle, nous utilisons deux structures de donnes : cities de type QStringList
pour contenir les noms de ville, et distances de type QVector<int> pour enregistrer la
distance entre chaque paire unique de villes.
CityModel::CityModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
248
}
int CityModel::columnCount(const QModelIndex & /* parent */) const
{
return cities.count();
}
Vu que nous avons une grille carre de villes, le nombre de lignes et de colonnes correspond au
nombre de villes de notre liste.
QVariant CityModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == Qt::TextAlignmentRole) {
return int(Qt::AlignRight | Qt::AlignVCenter);
} else if (role == Qt::DisplayRole) {
if (index.row() == index.column())
return 0;
int offset = offsetOf(index.row(), index.column());
return distances[offset];
}
return QVariant();
}
La fonction data() est similaire ce que nous effectu dans CurrencyModel. Elle retourne 0
si la ligne et la colonne sont identiques, parce que cela correspond au cas o les deux villes
sont les mmes ; sinon elle recherche lentre de la ligne et de la colonne dans le vecteur
distances et renvoie la distance pour cette paire de villes particulire.
QVariant CityModel::headerData(int section,
Qt::Orientation /* orientation */,
int role) const
{
if (role == Qt::DisplayRole)
return cities[section];
return QVariant();
}
La fonction headerData() est simple puisque nous avons un tableau carr o chaque ligne
possde un en-tte de colonne identique. Nous retournons simplement le nom de la ville la
position donne dans la liste de chane cities.
bool CityModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && index.row()!= index.column()
&& role == Qt::EditRole) {
int offset = offsetOf(index.row(), index.column());
distances[offset] = value.toInt();
Chapitre 10
249
La fonction setData() est invoque quand lutilisateur modifie un lment. En supposant que
lindex de modle est valide, que les deux villes sont diffrentes et que llment de donnes
modifier est Qt::EditRole, la fonction stocke la valeur que lutilisateur a saisie dans le
vecteur distances.
La fonction createIndex() sert gnrer un index de modle. Nous en avons besoin pour
obtenir lindex de modle de llment symtrique de llment configur par rapport la
diagonale principale, vu que les deux lments doivent afficher les mmes donnes. La fonction createIndex() reoit la ligne avant la colonne ; ici, nous inversons les paramtres pour
obtenir lindex de modle de llment symtrique celui spcifi par index.
Nous mettons le signal dataChanged() avec lindex de modle de llment qui a t modifi.
Ce signal reoit deux index de modle, parce quun changement peut affecter une rgion
rectangulaire constitue de plusieurs lignes et colonnes. Les index transmis reprsentent
llment situ en haut gauche et llment en bas droite de la zone modifie. Nous mettons aussi le signal dataChanged() lattention de lindex transpos afin que la vue actualise
laffichage de llment. Enfin, nous retournons true ou false pour indiquer si la modification a
t effectue avec succs ou non.
Qt::ItemFlags CityModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags flags = QAbstractItemModel::flags(index);
if (index.row()!= index.column())
flags |= Qt::ItemIsEditable;
return flags;
}
Le modle se sert de la fonction flags() pour annoncer les possibilits daction sur llment
(par exemple, sil peut tre modifi ou non). Limplmentation par dfaut de QAbstractTableModel retourne Qt::ItemIsSelectable | Qt::ItemIsEnabled. Nous ajoutons lindicateur
Qt::ItemIsEditable pour tous les lments sauf ceux qui se trouvent sur les diagonales (qui
sont toujours nuls).
void CityModel::setCities(const QStringList &cityNames)
{
cities = cityNames;
distances.resize(cities.count() * (cities.count() - 1) / 2);
distances.fill(0);
reset();
}
250
Si nous recevons une nouvelle liste de villes, nous dfinissons le QStringList priv en
nouvelle liste, nous redimensionnons et nous effaons le vecteur distances puis nous appelons
QAbstractItemModel::reset() pour informer toutes les vues que leurs lments visibles
doivent tre nouveau rcuprs.
int CityModel::offsetOf(int row, int column) const
{
if (row < column)
qSwap(row, column);
return (row * (row - 1) / 2) + column;
}
La fonction prive offsetOf() calcule lindex dune paire de villes donne dans le vecteur
distances. Par exemple, si nous avions les villes A, B, C et D et si lutilisateur avait mis
jour la ligne 3, colonne 1, B D, le dcalage serait de 3 _ (3 1)/2 + 1 = 4. Si lutilisateur avait
mis jour la ligne 1, colonne 3, D B, grce qSwap(), exactement le mme calcul aurait t
accompli et un dcalage identique aurait t retourn.
Figure 10.13
Les structures de donnes
cities et distances
et le modle de tableau
Modle de tableau
Villes
A
D
A
Distances
A B A C A D B C B D C D
A
0
B
C
D
A B A C A D
A B
C
D
0
A C B C
C D
0
A D B D C D
B C B D
Le dernier exemple de cette section est un modle qui prsente larbre danalyse dune expression rgulire donne. Une expression rgulire est constitue dun ou plusieurs termes, spars par des caractres "|". Lexpression rgulire "alpha|bravo|charlie" contient donc trois
termes. Chaque terme est une squence dun ou plusieurs facteurs ; par exemple, le terme
"bravo" est compos de cinq facteurs (chaque lettre est un facteur). Les facteurs peuvent
encore tre dcomposs en atome et en quantificateur facultatif, comme "*", "+" et "?".Vu que
les expressions rgulires peuvent contenir des sous-expressions entre parenthses, les arbres
danalyse correspondants seront rcursifs.
Lexpression rgulire prsente en Figure 10.14, "ab|(cd)?e", correspond un a suivi dun
b, ou dun c suivi dun d puis dun e, ou simplement dun e. Elle correspondra ainsi
"ab" et "cde", mais pas "bc" ou "cd".
Lapplication Regexp Parser se compose de quatre classes :
RegExpWindow est une fentre qui permet lutilisateur de saisir une expression rgulire
et qui affiche larbre danalyse correspondant.
Chapitre 10
251
Figure 10.14
Lapplication
Regexp Parser
Chaque nud possde un type, une chane (qui peut tre vide), un parent (qui peut tre 0) et
une liste de nuds enfants (qui peut tre vide).
Node::Node(Type type, const QString &str)
{
this->type = type;
this->str = str;
parent = 0;
}
Le constructeur initialise simplement le type et la chane du nud. Etant donn que toutes les
donnes sont publiques, le code qui utilise Node peut oprer directement sur le type, la chane,
le parent et les enfants.
Node::~Node()
{
qDeleteAll(children);
}
252
Cette fois-ci nous avons hrit de QAbstractItemModel plutt que de sa sous-classe ddie
QAbstractTableModel, parce que nous voulons crer un modle hirarchique. Les fonctions
essentielles que nous devons rimplmenter sont toujours les mmes, sauf que nous devons
aussi implmenter index() et parent(). Pour dfinir les donnes du modle, une fonction
setRootNode() doit tre invoque avec le nud racine de larbre danalyse.
RegExpModel::RegExpModel(QObject *parent)
: QAbstractItemModel(parent)
{
rootNode = 0;
}
Dans le constructeur du modle, nous navons qu configurer le nud racine en valeur nulle et
transmettre le parent la classe de base.
RegExpModel::~RegExpModel()
{
delete rootNode;
}
Chapitre 10
253
Dans le destructeur, nous supprimons le nud racine. Si le nud racine a des enfants, chacun
deux est supprim par le destructeur Node, et ainsi de suite de manire rcursive.
void RegExpModel::setRootNode(Node *node)
{
delete rootNode;
rootNode = node;
reset();
}
Quand un nouveau nud racine est dfini, nous supprimons dabord tout nud racine prcdent (et tous ses enfants). Nous configurons ensuite le nouveau nud racine et nous appelons
reset() pour informer les vues quelles doivent nouveau rcuprer les donnes des
lments visibles.
QModelIndex RegExpModel::index(int row, int column,
const QModelIndex &parent) const
{
if (!rootNode)
return QModelIndex();
Node *parentNode = nodeFromIndex(parent)
return createIndex(row, column, parentNode->children[row]);
}
La fonction index() est rimplmente dans QAbstractItemModel. Elle est appele ds que
le modle ou la vue doit crer un QModelIndex pour un lment enfant particulier (ou un
lment de haut niveau si parent est un QModelIndex invalide). Pour les modles de tableau
et de liste, nous navons pas besoin de rimplmenter cette fonction, parce que les implmentations par dfaut de QAbstractListModel et QAbstractTableModel sont normalement
suffisantes.
Dans notre implmentation dindex(), si aucun arbre danalyse nest configur, nous retournons un QModelIndex invalide. Sinon, nous crons un QModelIndex avec la ligne et la
colonne donnes et avec un Node * pour lenfant demand. Sagissant des modles hirarchiques, il nest pas suffisant de connatre la ligne et la colonne dun lment par rapport son
parent pour lidentifier ; nous devons aussi savoir qui est son parent. Pour rsoudre ce
problme, nous pouvons stocker un pointeur vers le nud interne dans le QModelIndex.
QModelIndex nous permet de conserver un void * ou un int en plus des nombres de lignes
et de colonnes.
Le Node * de lenfant est obtenu par le biais de la liste children du nud parent. Le nud
parent est extrait de lindex de modle parent grce la fonction prive nodeFromIndex():
Node *RegExpModel::nodeFromIndex(const QModelIndex &index) const
{
if (index.isValid()) {
return static_cast<Node *>(index.internalPointer());
} else {
return rootNode;
}
}
254
Le nombre de colonnes est fix 2. La premire colonne contient les types de nuds ; la
seconde comporte les valeurs des nuds.
QModelIndex RegExpModel::parent(const QModelIndex &child) const
{
Node *node = nodeFromIndex(child);
if (!node)
return QModelIndex();
Node *parentNode = node->parent;
if (!parentNode)
return QModelIndex();
Node *grandparentNode = parentNode->parent;
if (!grandparentNode)
return QModelIndex();
int row = grandparentNode->children.indexOf(parentNode);
return createIndex(row, child.column(), parentNode);
}
Rcuprer le parent QModelIndex dun enfant est un peu plus complexe que de rechercher
lenfant dun parent. Nous pouvons facilement rcuprer le nud parent laide de nodeFromIndex() et poursuivre en utilisant le pointeur du parent de Node, mais pour obtenir le numro
de ligne (la position du parent parmi ses pairs), nous devons remonter jusquau grand-parent et
rechercher la position dindex du parent dans la liste des enfants de son parent (cest--dire
celle du grand-parent de lenfant).
QVariant RegExpModel::data(const QModelIndex &index, int role) const
{
if (role!= Qt::DisplayRole)
return QVariant();
Chapitre 10
255
Dans data(), nous rcuprons le Node * de llment demand et nous nous en servons pour
accder aux donnes sous-jacentes. Si lappelant veut une valeur pour nimporte quel rle
except Qt::DisplayRole ou sil ne peut pas rcuprer un Node pour lindex de modle
donn, nous retournons un QVariant invalide. Si la colonne est 0, nous renvoyons le nom du
type du nud ; si la colonne est 1, nous retournons la valeur du nud (sa chane).
QVariant RegExpModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
if (section == 0) {
return tr("Node");
} else if (section == 1) {
return tr("Value");
}
}
return QVariant();
}
256
Dans notre rimplmentation de headerData(), nous retournons les intituls appropris des
en-ttes horizontaux. La classe QTreeView, qui est employe pour visualiser des modles
hirarchiques, ne possde pas den-tte vertical, nous ignorons donc cette ventualit.
Maintenant que nous avons tudi les classes Node et RegExpModel, voyons comment le nud
racine est cr quand lutilisateur modifie le texte dans lditeur de lignes :
void RegExpWindow::regExpChanged(const QString ®Exp)
{
RegExpParser parser;
Node *rootNode = parser.parse(regExp);
regExpModel->setRootNode(rootNode);
}
Quand lutilisateur change le texte dans lditeur de lignes de lapplication, le slot regExpChanged() de la fentre principale est appel. Dans ce slot, le texte de lutilisateur est analys
et lanalyseur retourne un pointeur vers le nud racine de larbre danalyse.
Nous navons pas tudi la classe RegExpParser parce quelle nest pas pertinente pour les
interfaces graphiques ou la programmation modle/vue. Le code source complet de cet exemple
se trouve sur la page ddie cet ouvrage sur le site web de Pearson, www.pearson.fr.
Dans cette section, nous avons vu comment crer trois modles personnaliss diffrents. De
nombreux modles sont beaucoup plus simples que ceux prsents ici, avec des correspondances uniques entre les lments et les index de modle. Dautres exemples modle/vue sont
fournis avec Qt, accompagns dune documentation dtaille.
Chapitre 10
257
Figure 10.15
La bote de dialogue
Track Editor
La bote de dialogue Track Editor se sert dun QTableWidget, une sous-classe ddie laffichage dlments qui agit sur des QTableWidgetItem. Les donnes sont proposes sous
forme dune liste de Track:
class Track
{
public:
Track(const QString &title = "", int duration = 0);
QString title;
int duration;
};
258
Le constructeur cre un widget tableau et au lieu dutiliser simplement le dlgu par dfaut,
nous dfinissons notre TrackDelegate personnalis, lui transmettant la colonne qui contient
les donnes de temps. Nous configurons dabord les en-ttes des colonnes, puis nous parcourons
les donnes, en alimentant les lignes avec le nom et la dure de chaque piste.
Le reste du constructeur et de la bote de dialogue TrackEditor ne prsentent aucune particularit, nous allons donc analyser maintenant le TrackDelegate qui gre le rendu et la modification
des donnes de la piste.
class TrackDelegate: public QItemDelegate
{
Q_OBJECT
public:
TrackDelegate(int durationColumn, QObject *parent = 0);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QWidget *createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
private slots:
void commitAndCloseEditor();
private:
int durationColumn;
};
Chapitre 10
259
Vu que nous voulons afficher la dure sous la forme "minutes : secondes" nous avons rimplment la fonction paint(). Les appels de arg() reoivent un nombre entier afficher
sous forme de chane, la quantit de caractres que la chane doit contenir, la base de lentier
(10 pour un nombre dcimal) et le caractre de remplissage.
Pour justifier le texte droite, nous copions les options de style en cours et nous remplaons
lalignement par dfaut. Nous appelons ensuite QItemDelegate::drawDisplay() pour
dessiner le texte, suivi de QItemDelegate::drawFocus() qui tracera un rectangle de focus si
llment est actif et ne fera rien dans les autres cas. La fonction drawDisplay() se rvle trs
pratique, notamment avec nos propres options de style. Nous pourrions aussi dessiner directement
en utilisant le painter.
QWidget *TrackDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
QTimeEdit *timeEdit = new QTimeEdit(parent);
timeEdit->setDisplayFormat("mm:ss");
connect(timeEdit, SIGNAL(editingFinished()),
this, SLOT(commitAndCloseEditor()));
return timeEdit;
} else {
return QItemDelegate::createEditor(parent, option, index);
}
}
Nous ne voulons modifier que la dure des pistes, le changement des noms de piste reste la
charge du dlgu par dfaut. Pour ce faire, nous vrifions pour quelle colonne un diteur a t
demand au dlgu. Sil sagit de la colonne de dure, nous crons un QTimeEdit, nous dfinissons le format daffichage de manire approprie et nous relions son signal editingFinished() notre slot commitAndCloseEditor(). Pour toute autre colonne, nous cdons
la gestion des modifications au dlgu par dfaut.
void TrackDelegate::commitAndCloseEditor()
{
260
Si lutilisateur appuie sur Entre ou dplace le focus hors du QTimeEdit (mais pas sil appuie
sur Echap), le signal editingFinished() est mis et le slot commitAndCloseEditor() est
invoqu. Ce slot met le signal commitData() pour informer la vue quil y a des donnes
modifies qui remplacent les donnes existantes. Il met aussi le signal closeEditor() pour
informer la vue que cet diteur nest plus requis, le modle le supprimera donc. Lditeur est
rcupr laide de QObject::sender() qui retourne lobjet qui a mis le signal qui a
dclench le slot. Si lutilisateur annule (en appuyant sur Echap), la vue supprimera simplement
lditeur.
void TrackDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
int secs = index.model()->data(index, Qt::DisplayRole).toInt();
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
timeEdit->setTime(QTime(0, secs / 60, secs % 60));
} else {
QItemDelegate::setEditorData(editor, index);
}
}
Quand lutilisateur initie une modification, la vue appelle createEditor() pour crer un
diteur, puis setEditorData() pour initialiser lditeur avec les donnes en cours de llment.
Si lditeur concerne la colonne de dure, nous extrayons la dure de la piste en secondes et
nous dfinissons le temps de QTimeEdit avec le nombre correspondant de minutes et de secondes ;
sinon nous laissons le dlgu par dfaut soccuper de linitialisation.
void TrackDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const
{
if (index.column() == durationColumn) {
QTimeEdit *timeEdit = qobject_cast<QTimeEdit *>(editor);
QTime time = timeEdit->time();
int secs = (time.minute() * 60) + time.second();
model->setData(index, secs);
} else {
QItemDelegate::setModelData(editor, model, index);
}
}
Chapitre 10
261
Mme si ce nest pas ncessaire dans ce cas, il est tout fait possible de crer un dlgu
personnalis qui contrle troitement la modification et laffichage de nimporte quel lment
dun modle. Nous avons choisi de nous occuper dune colonne particulire, mais vu que
QModelIndex est transmis toutes les fonctions de QItemDelegate que nous rimplmentons, nous pouvons prendre le contrle par colonne, ligne, zone rectangulaire, parent ou toute
combinaison de ceux-ci jusquaux lments individuels si ncessaire.
Dans ce chapitre, nous vous avons prsent un large aperu de larchitecture modle/vue de Qt.
Nous avons vu comment utiliser les sous-classes ddies laffichage et les modles prdfinis
de Qt et comment crer des modles et des dlgus personnaliss. Toutefois, larchitecture
modle/vue est si riche que nous naurions pas suffisamment de place pour traiter tous ses
aspects. Par exemple, nous pourrions crer une vue personnalise qui naffiche pas ses
lments sous forme de liste, de tableau ou darborescence. Cest ce que propose lexemple
Chart situ dans le rpertoire examples/itemviews/chart de Qt, qui prsente une vue personnalise affichant des donnes du modle sous forme de graphique secteurs.
Il est galement possible demployer plusieurs vues pour afficher le mme modle sans mise
en forme. Toute modification effectue via une vue se refltera automatiquement et immdiatement dans les autres vues. Ce type de fonctionnalit est particulirement utile pour afficher de
grands ensembles de donnes o lutilisateur veut voir des sections de donnes qui sont logiquement loignes les unes des autres. Larchitecture prend en charge les slections : quand
deux vues ou plus utilisent le mme modle, chaque vue peut tre dfinie de manire avoir
ses propres slections indpendantes, ou alors les slections peuvent se rpartir entre les vues.
La documentation en ligne de Qt aborde la programmation daffichage dlments et les classes
qui limplmentent. Consultez le site http://doc.trolltech.com/4.1/model-view.html pour
obtenir une liste des classes pertinentes et http://doc.trolltech.com/4.1/model-viewprogramming.html pour des informations supplmentaires et des liens vers les exemples
fournis avec Qt.
11
Classes conteneur
Au sommaire de ce chapitre
Conteneurs squentiels
Conteneurs associatifs
Algorithmes gnriques
Chanes, tableaux doctets et variants
Les classes conteneur sont des classes template polyvalentes qui stockent des lments
dun type donn en mmoire. C++ offre dj de nombreux conteneurs dans la STL
(Standard Template Library), qui est incluse dans la bibliothque C++ standard.
Qt fournissant ses propres classes conteneur, nous pouvons utiliser la fois les conteneurs STL et Qt pour les programmes Qt. Les conteneurs Qt prsentent lavantage de se
comporter de la mme faon sur toutes les plates-formes et dtre partags implicitement. Le partage implicite, ou la technique de "copie lcriture", est une optimisation
qui permet la transmission de conteneurs entiers comme valeurs sans cot significatif
pour les performances. Les conteneurs Qt comportent galement des classes ditrateurs
simple demploi inspires par Java. Elles peuvent tre diffuses au moyen dun
QDataStream et elles ncessitent moins de code dans lexcutable que les conteneurs
STL correspondants. Enfin, sur certaines plates-formes matrielles supportes par
Qtopia Core (la version Qt pour priphriques mobiles), les conteneurs Qt sont les seuls
disponibles.
264
Conteneurs squentiels
Un QVector<T> est une structure de donnes de type tableau qui stocke ses lments des
emplacements adjacents en mmoire. Un vecteur se distingue dun tableau C++ brut par le fait
quil connat sa propre taille et peut tre redimensionn. Lajout dlments supplmentaires
la fin dun vecteur est assez efficace, alors que linsertion dlments devant ou au milieu de
celui-ci peut savrer coteux. (voir Figure 11.1)
Figure 11.1
Un vecteur dlments
de type double
937.81
25.984
308.74
310.92
40.9
Si nous savons lavance combien dlments nous seront ncessaires, nous pouvons attribuer au vecteur une taille initiale lors de sa dfinition et utiliser loprateur [] pour affecter
une valeur aux lments. Dans le cas contraire, nous devons redimensionner le vecteur ultrieurement ou ajouter les lments. Voici un exemple dans lequel nous spcifions la taille
initiale :
QVector<double> vect(3);
vect[0] = 1.0;
vect[1] = 0.540302;
vect[2] = -0.416147;
Chapitre 11
Classes conteneur
265
Voici le mme exemple, commenant cette fois avec un vecteur vide et utilisant la fonction
append() pour ajouter des lments la fin :
QVector<double> vect;
vect.append(1.0);
vect.append(0.540302);
vect.append(-0.416147);
Les entres de vecteur cres sans quune valeur explicite ne leur soit attribue sont initialises
au moyen du constructeur par dfaut de la classe de llment. Les types de base et les types
pointeur sont initialiss en zro.
Linsertion dlments au dbut ou au milieu dun QVector<T>, ou la suppression dlments
ces emplacements, risque de ne pas tre efficace pour de gros vecteurs. Cest pourquoi Qt
offre galement QLinkedList<T>, une structure de donnes qui stocke ses lments des
emplacements non adjacents en mmoire. Contrairement aux vecteurs, les listes chanes ne
prennent pas en charge laccs alatoire, mais elles garantissent les performances des insertions et
des suppressions. (Voir Figure 11.2)
937.81
25.984
308.74
310.92
40.9
Figure 11.2
Une liste chane dlments de type double
Les listes chanes ne fournissent pas loprateur []. Il est donc ncessaire de recourir aux
itrateurs pour parcourir leurs lments. Les itrateurs sont galement utiliss pour spcifier la
position des lments. Par exemple, le code suivant insre la chane "Tote Hosen" entre
"Clash" et "Ramones" :
QLinkedList<QString> list;
list.append("Clash");
list.append("Ramones");
QLinkedList<QString>::iterator i = list.find("Ramones");
list.insert(i, "Tote Hosen");
266
Le conteneur squentiel QList<T> est une "liste-tableau" qui combine les principaux avantages de QVector<T> et de QLinkedList<T> dans une seule classe. Il prend en charge laccs
alatoire et son interface est base sur les index, de la mme faon que celle de QVector.
Lajout ou la suppression dun lment une extrmit dun QList<T> est trs rapide. En
outre, une insertion au sein dune liste contenant jusqu un millier dlments est galement
trs simple. A moins que nous souhaitions raliser des insertions au milieu de listes de taille
trs importante ou que nous ayons besoin que les lments de la liste occupent des adresses
conscutives en mmoire, QList<T> constitue gnralement la classe conteneur polyvalente la
plus approprie. La classe QStringList est une sous-classe de QList<QString> qui est
largement utilise dans lAPI de Qt. En plus des fonctions quelle hrite de sa classe de base,
elle fournit des fonctions supplmentaires qui la rendent plus souple demploi pour la gestion
de chane. QStringList est tudie dans la dernire section de ce chapitre.
En plus des types que nous venons de mentionner, le type de valeur dun conteneur peut tre
toute classe personnalise correspondant aux critres dcrits prcdemment. Voici un exemple
de classe de ce type :
class Movie
{
public:
Movie(const QString &title = "", int duration = 0);
void setTitle(const QString &title) { myTitle = title; }
QString title() const { return myTitle; }
void setDuration(int duration) { myDuration = duration; }
QString duration() const { return myDuration; }
Chapitre 11
Classes conteneur
267
private:
QString myTitle;
int myDuration;
};
La classe possde un constructeur qui nexige aucun argument (bien quil puisse en recevoir
jusqu deux). Elle possde galement un constructeur de copie et un oprateur daffectation,
tous deux tant implicitement fournis par C++. Pour cette classe, la copie au membre par
membre est suffisante. Il nest donc pas ncessaire dimplmenter votre propre constructeur de
copie et votre oprateur daffectation.
Qt fournit deux catgories ditrateurs afin de parcourir les lments stocks dans un conteneur. Les itrateurs de style Java et ceux de style STL. Les itrateurs de style Java sont plus
faciles utiliser, alors que ceux de style STL sont plus puissants et peuvent tre combins avec
les algorithmes gnriques de Qt et de STL.
Pour chaque classe conteneur, il existe deux types ditrateurs de style Java : un itrateur en
lecture seulement et un itrateur en lecture-criture. Les classes ditrateur en lecture seulement sont QVectorIterator<T>, QLinkedListIterator<T> et QListIterator<T>. Les
itrateurs en lecture/criture correspondants comportent le terme Mutable dans leur nom (par
exemple, QMutableVectorIterator<T>). Dans cette discussion, nous allons surtout tudier
les itrateurs de QList; les itrateurs pour les listes chanes et les vecteurs possdent la
mme API. (Voir Figure 11.3)
Figure 11.3
Emplacements valides
pour les itrateurs
de style Java
Le premier point garder lesprit lors de lutilisation ditrateurs de style Java est quils ne
pointent pas directement vers des lments. Ils peuvent tre situs avant le premier lment,
aprs le dernier ou entre deux. Voici la syntaxe dune boucle ditration typique :
QList<double> list;
...
QListIterator<double> i(list);
while (i.hasNext()) {
do_something(i.next());
}
Litrateur est initialis avec le conteneur parcourir. A ce stade, litrateur est situ juste avant
le premier lment. Lappel hasNext() retourne true si un lment se situe sur la droite de
litrateur. La fonction next() retourne llment situ sur la droite de litrateur et avance ce
dernier jusqu la prochaine position valide.
268
Litration vers larrire est similaire, si ce nest que nous devons tout dabord appeler
toBack() pour placer litrateur aprs le dernier lment :
QListIterator<double> i(list);
i.toBack();
while (i.hasPrevious()) {
do_something(i.previous());
}
La fonction hasPrevious() retourne true si un lment se trouve sur la gauche de litrateur ; previous() retourne cet lment et le dplace vers larrire. Les itrateurs next() et
previous() retournent llment que litrateur vient de passer. (Voir Figure 11.4).
Figure 11.4
Effet de previous()
et de next() sur
un itrateur de style Java
A
previous()
next()
Les itrateurs mutables fournissent des fonctions destines insrer, modifier et supprimer des
lments lors de litration. La boucle suivante supprime tous les nombres ngatifs dune liste :
QMutableListIterator<double> i(list);
while (i.hasNext()) {
if (i.next() < 0.0)
i.remove();
}
La fonction remove() opre toujours sur le dernier lment pass. Elle fonctionne galement
lors de litration vers larrire.
QMutableListIterator<double> i(list);
i.toBack();
while (i.hasPrevious()) {
if (i.previous() < 0.0)
i.remove();
}
De la mme faon, les itrateurs mutables de style Java fournissent une fonction setValue()
qui modifie le dernier lment pass. Voici comment nous remplacerions des nombres ngatifs
par leur valeur absolue :
QMutableListIterator<double> i(list);
while (i.hasNext()) {
int val = i.next();
if (val < 0.0)
i.setValue(-val);
}
Chapitre 11
Classes conteneur
269
Il est galement possible dinsrer un lment lemplacement courant de litrateur en appelant insert(). Litrateur est alors avanc lemplacement se situant entre le nouvel lment
et llment suivant.
En plus des itrateurs de style Java, chaque classe conteneur squentiel C<T> possde deux
types ditrateurs de style STL : C<T>::iterator et C<T>::const_iterator. La diffrence
entre les deux est que const_iterator ne nous permet pas de modifier les donnes.
La fonction begin() dun conteneur retourne un itrateur de style STL faisant rfrence au
premier lment du conteneur (par exemple list[0]), alors que end() retourne un itrateur
pointant vers llment suivant le dernier (par exemple, list[5] pour une liste de taille 5). Si
un conteneur est vide, begin() est gal end(). Cette caractristique peut tre utilise pour
dterminer si le conteneur comporte des lments, bien quil soit gnralement plus appropri
dappeler isEmpty() cette fin. (Voir Figure 11.5)
Figure 11.5
Emplacements valides
pour les itrateurs
de style STL
begin()
end()
La syntaxe dun itrateur de style STL est modele sur celle des pointeurs C++ dans un
tableau. Nous pouvons utiliser les oprateurs ++ et -- pour passer llment prcdent ou
suivant et loprateur * unaire pour rcuprer llment en cours. Pour QVector<T>, litrateur
et les types const_iterator sont simplement des typedefs de T* et constT*. (Ceci est possible parce que QVector<T> stocke ses lments dans des emplacements conscutifs en
mmoire.)
Lexemple suivant remplace chaque valeur dun QList<double> par sa valeur absolue :
QList<double>::iterator i = list.begin();
while (i!= list.end()) {
*i = qAbs(*i);
++i;
}
270
La copie dun conteneur tel que celui-ci semble coteuse, mais il nen est rien, grce loptimisation obtenue par le partage implicite. La copie dun conteneur Qt est pratiquement aussi
rapide que celle dun pointeur unique. Les donnes ne sont vritablement copies que si lune
des copies est change et tout ceci est gr automatiquement larrire-plan. Cest pourquoi
le partage implicite est quelquefois nomm "copie lcriture".
Lintrt du partage implicite est quil sagit dune optimisation dont nous bnficions sans
intervention de la part du programmeur. En outre, le partage implicite favorise un style de
programmation clair, o les objets sont retourns par valeur. Considrez la fonction suivante :
QVector<double> sineTable()
{
QVector<double> vect(360);
for (int i = 0; i < 360; ++i)
vect[i] = sin(i / (2 * M_PI));
return vect;
}
STL, nous incite plutt transmettre le vecteur comme rfrence non const pour viter
lexcution de la copie lorsque la valeur de retour de la fonction est stocke dans une variable :
Chapitre 11
Classes conteneur
271
Qt utilise le partage implicite pour tous ses conteneurs ainsi que pour de nombreuses autres
classes, dont QByteArray, QBrush, QFont, QImage, QPixmap et QString .
Le partage implicite est une garantie de la part de Qt que les donnes ne seront pas copies si
nous ne les modifions pas. Pour obtenir le meilleur du partage implicite, nous pouvons adopter
deux nouvelles habitudes de programmation. Lune consiste coder la fonction at() au lieu
de loprateur [] pour un accs en lecture seulement sur une liste ou un vecteur (non const).
Les conteneurs Qt ne pouvant pas dterminer si [] apparat sur le ct gauche dune affectation ou non, le pire est envisag et une copie intgrale est dclenche alors que at() nest
pas autoris sur le ct gauche dune affectation.
Un problme similaire se pose lorsque nous parcourons un conteneur avec des itrateurs de
style STL. Ds que nous appelons begin() ou end() sur un conteneur non const, Qt force une
copie complte si les donnes sont partages. Pour viter ceci, la solution consiste utiliser
const_iterator, constBegin() et constEnd() ds que possible.
Qt fournit une dernire mthode pour parcourir les lments situs dans un conteneur squentiel :
la boucle foreach. Voici sa syntaxe :
QLinkedList<Movie> list;
...
foreach (Movie movie, list) {
if (movie.title() == "Citizen Kane") {
cout << "Found Citizen Kane" << endl;
break;
}
}
Le pseudo mot-cl foreach est implment sous la forme dune boucle for standard. A
chaque itration de la boucle, la variable ditration (movie) est dfinie en un nouvel lment,
commenant au premier lment du conteneur et progressant vers lavant. La boucle foreach
reoit automatiquement une copie du conteneur. Elle ne sera donc pas affecte si le conteneur
est modifi durant litration.
272
Nous dfinissons str1 en "Humpty" et str2 de sorte quil soit gal str1. A ce stade,
les deux objets QString pointent vers la mme structure de donnes interne en mmoire. Avec les
donnes de type caractre, il existe pour une structure de donnes un compteur de rfrence
indiquant le nombre de QString pointant vers celle-ci. str1 et str2 pointant vers la mme
donne, le compteur de rfrence indique 2.
str2[0] = D;
Lorsque nous modifions str2, il ralise tout dabord une copie intgrale des donnes pour
sassurer que str1 et str2 pointent vers des structures de donnes diffrentes, puis il applique
la modification sa propre copie des donnes. Le compteur de rfrence des donnes de str1
("Humpty") indique alors 1 et celui des donnes de str2 ("Dumpty") est dfini en 1. Quand un
compteur de rfrence indique 1, les donnes ne sont pas partages.
str2.truncate(4);
Si nous modifions de nouveau str2, aucune copie ne se produit car le compteur de rfrence
des donnes de str2 indique 1. La fonction truncate() agit directement sur les donnes de
str2, rsultant en la chane "Dump". Le compteur de rfrence reste 1.
str1 = str2;
Lorsque nous affectons str2 str1, le compteur de rfrence des donnes de str1 descend
0, ce qui signifie quaucun QString nutilise plus la donne "Humpty". La donne est alors libre de la mmoire. Les deux QString pointent vers "Dump", dont le compteur de rfrence
indique maintenant 2.
Le partage de donnes est une option souvent ignore dans les programmes multithread,
cause des conditions de comptition dans le dcompte des rfrences. Avec Qt, ceci nest plus
un problme. En interne, les classes conteneur utilisent des instructions du langage dassembly
pour effectuer un dcompte de rfrences atomique. Cette technologie est la porte des
utilisateurs de Qt par le biais des classes QSharedData et QSharedDataPointer.
Chapitre 11
Classes conteneur
273
Les instructions de boucle break et continue sont prises en charge. Si le corps est constitu
dune seule instruction, les accolades ne sont pas ncessaires. Comme pour une instruction
for, la variable ditration peut tre dfinie lextrieur de la boucle, comme suit :
QLinkedList<Movie> list;
Movie movie;
...
foreach (movie, list) {
if (movie.title() == "Citizen Kane") {
cout << "Found Citizen Kane" << endl;
break;
}
}
La dfinition de la variable ditration lextrieur de la boucle est la seule solution pour les conteneurs comportant des types de donnes avec une virgule (par exemple, QPair<QString,int>).
Conteneurs associatifs
Un conteneur associatif comporte un nombre arbitraire dlments du mme type, indexs par
une cl. Qt fournit deux classes de conteneurs associatifs principales : QMap<K,T> et
QHash<K,T>.
Un QMap<K,T> est une structure de donnes qui stocke des paires cl/valeur dans un ordre
croissant des cls. Cette organisation permet dobtenir de bonnes performances en matire de
recherche et dinsertion ainsi quune itration ordonne. En interne, QMap<K,T> est implment sous forme de liste branchement. (Voir Figure 11.6)
Figure 11.6
Un map de QString
vers int
Mexico City
22 350 000
Seoul
22 050 000
Tokyo
34 000 000
Un moyen simple dinsrer des lments dans un map consiste appeler insert():
QMap<QString, int> map;
map.insert("eins", 1);
map.insert("sieben", 7);
map.insert("dreiundzwanzig", 23);
Nous avons aussi la possibilit daffecter simplement une valeur une cl donne comme suit :
map["eins"] = 1;
map["sieben"] = 7;
map["dreiundzwanzig"] = 23;
274
Loprateur [] peut tre utilis la fois pour linsertion et la rcupration. Si [] est utilis pour
rcuprer une valeur dune cl non existante dans un map non const, un nouvel lment sera
cr avec la cl donne et une valeur vide. Lutilisation de la fonction value() la place de []
pour rcuprer les lments permet dviter la cration accidentelle de valeurs vides :
int val = map.value("dreiundzwanzig");
Si la cl nexiste pas, une valeur par dfaut est retourne et aucun nouvel lment nest cr.
Pour les types de base et les types pointeur, la valeur retourne est nulle. Nous pouvons spcifier
une autre valeur par dfaut comme second argument pour value():
int int seconds = map.value("delay", 30);
Les types de donnes K et T dun QMap<K,T> peuvent tre des types de base tels que int et
double, des types pointeur ou de classes possdant un constructeur par dfaut, un constructeur
de copie et un oprateur daffectation. En outre, le type K doit fournir un operator<() car
QMap<K,T> utilise cet oprateur pour stocker les lments dans un ordre de cl croissant.
QMap<K,T> possde deux fonctions utilitaires keys() et values(), qui savrent particulirement intressantes pour travailler avec de petits ensembles de donnes. Elles retournent des
QList des cls et valeurs dun map.
Les maps sont gnralement valeur unique : si une nouvelle valeur est affecte une cl existante, lancienne valeur est remplace par la nouvelle. De cette faon, deux lments ne partagent pas la mme cl. Il est possible davoir des valeurs multiples pour la mme cl en utilisant
la fonction insertMulti() ou la sous-classe utilitaire QMultiMap<K,T>. QMap<K,T>
possde une surcharge values(constK &) qui retourne un QList de toutes les valeurs pour
une cl donne. Par exemple :
QMultiMap<int, QString> multiMap;
multiMap.insert(1, "one");
multiMap.insert(1, "eins");
multiMap.insert(1, "uno");
QList<QString> vals = multiMap.values(1);
Un QHash<K,T> est une structure de donnes qui stocke des paires cl/valeur dans une table de
hachage. Son interface est pratiquement identique celle de QMap<K,T>, mais ses exigences
concernant le type template K sont diffrentes et il offre des oprations de recherche beaucoup
plus rapides que QMap<K,T>. Une autre diffrence est que QHash<K,T> nest pas ordonn.
En complment des conditions standard concernant tout type de valeur stock dans un
conteneur, le type K dun QHash<K,T> doit fournir un operator==() et tre support par une
Chapitre 11
Classes conteneur
275
fonction QHash() globale qui retourne une valeur de hachage pour une cl. Qt fournit dj
des fonctions QHash() pour les types entiers, les types pointeur, QChar, QString et QByteArray.
276
Les itrateurs mutables possdent une fonction setValue() qui modifie la valeur associe
un lment courant :
QMutableMapIterator<QString, int> i(map);
while (i.hasNext()) {
i.next();
if (i.value() < 0.0)
i.setValue(-i.value());
}
Les itrateurs de style STL fournissent galement des fonctions key() et value(). Avec des
types ditrateur non const, value() retourne une rfrence non const, ce qui nous permet
de changer la valeur au cours de litration. Remarquez que, bien que ces itrateurs soient "de
style STL", ils prsentent des diffrences significatives avec les itrateurs map<K,T> de STL,
qui sont bass sur pair<K,T>.
La boucle foreach fonctionne galement avec les conteneurs associatifs, mais uniquement
avec le composant valeur des paires cl/valeur. Si nous avons besoin des deux composants cl
et valeur des lments, nous pouvons appeler les fonctions keys() et values(constK &)
dans des boucles foreach imbriques comme suit :
QMultiMap<QString, int> map;
...
foreach (QString key, map.keys()) {
foreach (int value, map.values(key)) {
do_something(key, value);
}
}
Algorithmes gnriques
Len-tte <QtAlgorithms> dclare un ensemble de fonctions template globales qui implmentent des algorithmes de base sur les conteneurs. La plupart de ces fonctions agissent sur
des itrateurs de style STL.
Len-tte STL <algorithm> fournit un ensemble dalgorithmes gnriques plus complet. Ces
algorithmes peuvent tre employs avec des conteneurs Qt ainsi que des conteneurs STL. Si
les implmentations STL sont disponibles sur toutes vos plates-formes, il ny a probablement
aucune raison de ne pas utiliser les algorithmes STL lorsque Qt ne propose pas lalgorithme
quivalent. Nous prsenterons ici les algorithmes Qt les plus importants.
Lalgorithme qFind() recher286che une valeur particulire dans un conteneur. Il reoit un
itrateur "begin" et un itrateur "end" et retourne un itrateur pointant sur le premier lment
correspondant, ou "end" sil nexiste pas de correspondance. Dans lexemple suivant, i est
dfini en list.begin() + 1 alors que j est dfini en list.end().
QStringList list;
Chapitre 11
Classes conteneur
277
Comme les autres algorithmes bass sur les itrateurs, nous pouvons galement utiliser
qFill() sur une partie du conteneur en variant les arguments. Lextrait de code suivant initialise les cinq premiers lments dun vecteur en 1009 et les cinq derniers lments en 2013 :
QVector<int> vect(10);
qFill(vect.begin(), vect.begin() + 5, 1009);
qFill(vect.end() - 5, vect.end(), 2013);
qCopy() peut galement tre utilis pour copier des valeurs dans le mme conteneur, ceci tant
que la plage source et la plage cible ne se chevauchent pas. Dans lextrait de code suivant, nous
lutilisons pour remplacer les deux derniers lments dune liste par les deux premiers :
qCopy(list.begin(), list.begin() + 2, list.end() - 2);
Par dfaut, qSort() se sert de loprateur < pour comparer les lments. Pour trier les
lments en ordre dcroissant, transmettez qGreater<T>() comme troisime argument (o T
est le type des valeurs du conteneur), comme suit :
qSort(list.begin(), list.end(), qGreater<int>());
Nous pouvons utiliser le troisime paramtre pour dfinir un critre de tri personnalis. Voici,
par exemple, une fonction de comparaison "infrieur " qui compare des QString sans prendre la
casse (majuscule-minuscule) en considration :
bool insensitiveLessThan(const QString &str1, const QString &str2)
{
return str1.toLower() < str2.toLower();
278
Lalgorithme qStableSort() est similaire qSort(), si ce nest quil garantit que les
lments gaux apparaissent dans le mme ordre avant et aprs le tri. Cette caractristique est
utile si le critre de tri ne prend en compte que des parties de la valeur et si les rsultats sont
visibles pour lutilisateur. Nous avons utilis qStableSort() dans le Chapitre 4 pour implmenter le tri dans lapplication Spreadsheet.
Lalgorithme qDeleteAll() appelle delete sur chaque pointeur stock dans un conteneur.
Ceci nest utile que pour les conteneurs dont le type de valeur est un type pointeur. Aprs lappel,
les lments sont toujours prsents, vous excutez clear() sur le conteneur. Par exemple :
qDeleteAll(list);
list.clear();
Enfin, len-tte <QtGlobal> fournit plusieurs dfinitions utiles, dont la fonction qAbs(), qui
retourne la valeur absolue de son argument ainsi que les fonctions qMin() et qMax() qui retournent
le minimum ou le maximum entre deux valeurs.
Chapitre 11
Classes conteneur
279
Lorsque vous utilisez QString, vous navez pas besoin de vous proccuper de dtails comme
lallocation dune mmoire suffisante ou de vrifier que la donne est termine par "0".
Conceptuellement, les objets QString peuvent tre considrs comme des vecteurs de QChar.
Un QString peut intgrer des caractres "0". La fonction length() retourne la taille de la
chane entire, dont les caractres "0" intgrs.
QString fournit un oprateur + binaire destin concatner deux chanes ainsi quun oprateur += dont la fonction est daccoler une chane une autre. Voici un exemple combinant
+ et +=.
QString str = "User: ";
str += userName + "\n";
Il existe galement une fonction QString::append() dont la tche est identique celle de
loprateur +=:
str = "User: ";
str.append(userName);
str.append("\n");
Cette fonction prend en charge les mmes spcificateurs de format que la fonction sprintf()
de la bibliothque C++. Dans lexemple ci-dessus, "perfect competition 100.0 %" est affect
str.
Un moyen supplmentaire de crer une chane partir dautres chanes ou de nombres consiste
utiliser arg():
str = QString("%1%2 (%3s-%4s)")
.arg("permissive").arg("society").arg(1950).arg(1970);
Dans cet exemple, "%1", "%2", "%3" et "%4" sont remplacs par "permissive", "society",
"1950" et "1970", respectivement. On obtient "permissive society (1950s-1970s)". Il existe des
surcharges de arg() destines grer divers types de donnes. Certaines surcharges comportent des paramtres supplmentaires afin de contrler la largeur de champ, la base numrique
ou la prcision de la virgule flottante. En gnral, arg() reprsente une solution bien meilleure
que sprintf(), car elle est de type scuris, prend totalement en charge Unicode et autorise
les convertisseurs rordonner les paramtres "%n".
QString peut convertir des nombres en chanes au moyen de la fonction statique QString:
:number():
str = QString::number(59.6);
280
Ces fonctions peuvent recevoir en option un pointeur facultatif vers une variable bool et elles
dfinissent la variable en true ou false selon le succs de la conversion. Si la conversion
choue, les fonctions retournent zro.
Il est souvent ncessaire dextraire des parties dune chane. La fonction mid() retourne la
sous-chane dbutant un emplacement donn (premier argument) et de longueur donne
(second argument). Par exemple, le code suivant affiche "pays" sur la console1 :
QString str = "polluter pays principle";
qDebug() << str.mid(9, 4);
Si nous omettons le second argument, mid() retourne la sous-chane dbutant lemplacement donn et se terminant la fin de la chane. Par exemple, le code suivant affiche "pays
principle" sur la console :
QString str = "polluter pays principle";
qDebug() << str.mid(9);
Il existe aussi des fonctions left() et right() dont la tche est similaire. Toutes deux reoivent un nombre de caractres, n, et retournent les n premiers ou derniers caractres de la
chane. Par exemple, le code suivant affiche "polluter principle" sur la console :
QString str = "polluter pays principle";
qDebug() << str.left(8) << " " << str.right(9);
Pour rechercher si un chane contient un caractre particulier, une sous-chane ou une expression
rgulire, nous pouvons utiliser lune des fonctions indexOf() de QString :
QString str = "the middle bit";
int i = str.indexOf("middle");
Dans ce cas, i sera dfini en 4. La fonction indexOf() retourne 1 en cas dchec et reoit en
option un emplacement de dpart ainsi quun indicateur de sensibilit la casse.
1. La syntaxe qDebug()<<arg utilise ici ncessite linclusion du fichier den-tte <QtDebug>, alors
que la syntaxe qDebug("",arg) est disponible dans tout fichier incluant au moins un en-tte Qt.
Chapitre 11
Classes conteneur
281
Si nous souhaitons simplement vrifier si une chane commence ou se termine par quelque
chose, nous pouvons utiliser les fonctions startsWith() et endsWith():
if (url.startsWith("http:") && url.endsWith(".png"))
...
Pour remplacer une certaine partie dune chane par une autre chane, nous codons
replace():
QString str = "a cloudy day";
str.replace(2, 6, "sunny");
On obtient "a sunny day". Le code peut tre rcrit de faon excuter remove() et
insert().
str.remove(2, 6);
str.insert(2, "sunny");
Il est trs souvent ncessaire de supprimer les blancs (tels que les espaces, les tabulations et les
retours la lignes) dans une chane. QString possde une fonction qui limine les espaces
situs aux deux extrmits dune chane :
QString str = " BOB \t THE \nDOG \n";
qDebug() << str.trimmed();
282
\t
\n
\n
\t
\n
Lorsque nous grons les entres utilisateur, nous avons souvent besoin de remplacer les
squences internes dun ou de plusieurs caractres despace par un espace unique, ainsi que
dliminer les espaces aux deux extrmits. Voici laction de la fonction simplified():
QString str = " BOB \t THE \nDOG \n";
qDebug() << str.simplified();
Dans lexemple ci-dessus, nous divisons la chane "polluter pays principle" en trois souschanes : "polluter", "pays" et "principle". La fonction split() possde un troisime argument facultatif qui spcifie si les sous-chanes vides doivent tre conserves (option par dfaut)
ou limines.
Les lments dun QStringList peuvent tre unis pour former une chane unique au moyen
de join(). Largument de join() est insr entre chaque paire de chanes jointes. Par exemple, voici comment crer une chane unique qui est compose de toutes les chanes contenues
dans un QStringList tries par ordre alphabtique et spares par des retours la lignes :
words.sort();
str = words.join("\n");
En travaillant avec les chanes, il est souvent ncessaire de dterminer si elles sont vides ou
non. Pour ce faire, appelez isEmpty() ou vrifiez si length() est gal 0.
La conversion de chanes const char* en QString est automatique dans la plupart des cas.
Par exemple :
str += " (1870)";
Chapitre 11
Classes conteneur
283
Ici nous ajoutons un constchar* un QString sans formalit. Pour convertir explicitement
un const char* en un QString, il suffit dutiliser une conversion QString ou encore
dappeler fromAscii() ou fromLatin1(). (Reportez-vous au Chapitre 17 pour obtenir une
explication concernant la gestion des chanes littrales et autres codages.)
Pour convertir un QString en un const char *, excutez toAscii() ou toLatin1(). Ces
fonctions retournent un QByteArray, qui peut tre converti en un const char* en codant
QByteArray::data() ou QByteArray::constData(). Par exemple :
printf("User: %s\n", str.toAscii().data());
Pour des raisons pratiques, Qt fournit la macro qPrintable() dont laction est identique
celle de la squence toAscii().constData():
printf("User: %s\n", qPrintable(str));
284
Les variants sont abondamment utiliss par les classes daffichage dlments, le module de
base de donnes et QSettings, ce qui nous permet de lire et dcrire des donnes dlment,
des donnes de base de donnes et des prfrences utilisateur pour tout type compatible
QVariant. Nous en avons dj rencontr un exemple dans le Chapitre 3, o nous avons transmis un QRect, un QStringList et deux bool en tant que variants QSettings::setValue().
Nous les avons rcuprs ultrieurement comme variants.
Il est possible de crer arbitrairement des structures de donnes complexes utilisant QVariant
en imbriquant des valeurs de types conteneur :
QMap<QString, QVariant> pearMap;
pearMap["Standard"] = 1.95;
pearMap["Organic"] = 2.25;
QMap<QString, QVariant> fruitMap;
fruitMap["Orange"] = 2.10;
fruitMap["Pineapple"] = 3.85;
fruitMap["Pear"] = pearMap;
Ici, nous avons cr un map avec des cls sous forme de chanes (noms de produit) et des
valeurs qui sont soit des nombres virgule flottante (prix), soit des maps. Le map de niveau
suprieur contient trois cls : "Orange", "Pear" et "Pineapple". La valeur associe la cl
"Pear" est un map qui contient deux cls ("Standard" et "Organic"). Lorsque nous parcourons
un map contenant des variants, nous devons utiliser type() pour en contrler le type de faon
pouvoir rpondre de faon approprie.
La cration de telles structures de donnes peut sembler trs sduisante, car nous pouvons ainsi
organiser les donnes exactement comme nous le souhaitons. Mais le caractre pratique de
QVariant est obtenu au dtriment de lefficacit et de la lisibilit. En rgle gnrale, il
convient de dfinir une classe C++ correcte pour stocker les donnes ds que possible.
QVariant est utilis par le systme mtaobjet de Qt et fait donc partie du module QtCore.
Nanmoins, lorsque nous le rattachons au module QtGui, QVariant peut stocker des types
en liaison avec linterface utilisateur graphique tels que QColor, QFont, QIcon, QImage et
QPixmap:
QIcon icon("open.png");
QVariant variant = icon;
Pour rcuprer la valeur dun tel type partir dun QVariant, nous pouvons utiliser la fonction
membre template QVariant::Value<T>() comme suit :
QIcon icon = variant.value<QIcon>();
La fonction value<T>() permet galement deffectuer des conversions entre des types de
donnes non graphiques et QVariant, mais en pratique, nous utilisons habituellement les
fonctions de conversion to() (par exemple, toString()) pour les types non graphiques.
Chapitre 11
Classes conteneur
285
QVariant peut aussi tre utilis pour stocker des types de donnes personnaliss, en
supposant quils fournissent un constructeur par dfaut et un constructeur de copie. Pour
que ceci fonctionne, nous devons tout dabord enregistrer le type au moyen de la macro
Q_DECLARE_METATYPE(), gnralement dans un fichier den-tte en dessous de la dfinition
de classe :
Q_DECLARE_METATYPE(BusinessCard)
Ces fonctions membre template ne sont pas disponibles pour MSVC 6, cause dune limite du
compilateur. Si vous devez employer ce compilateur, utilisez plutt les fonctions globales
qVariantFromValue(), qVariantValue<T>()et qVariantCanConvert<T>().
Si le type de donnes personnalis possde des oprateurs << et >> pour effectuer des oprations
de lecture et dcriture dans un QDataStream, nous pouvons les enregistrer au moyen de
qRegisterMetaTypeStreamOperators<T>(). Il est ainsi possible de stocker les prfrences
des types de donnes personnaliss laide de QSettings, entre autres. Par exemple :
qRegisterMetaTypeStreamOperators<BusinessCard>("BusinessCard");
Dans ce chapitre, nous avons principalement tudi les conteneurs Qt, ainsi que QString,
QByteArray et QVariant. En complment de ces classes, Qt fournit quelques autres conteneurs. QPair<T1,T2> en fait partie, qui stocke simplement deux valeurs et prsente des similitudes avec std::pair<T1,T2>. QBitArray est un autre conteneur que nous utiliserons dans
la premire partie du Chapitre 19. Il existe enfin QVarLengthArray<T,Prealloc>, une alternative de bas niveau QVector<T>. Comme il pralloue de la mmoire sur la pile et nest pas
implicitement partag, sa surcharge est infrieure celle de QVector<T>, ce qui le rend plus
appropri pour les boucles troites.
Les algorithmes de Qt, dont quelques-uns nayant pas t tudis ici tels que qCopyBackward()
et qEqual(), sont dcrits dans la documentation de Qt ladresse http://doc.trolltech.com/
4.1/algorithms.html. Vous trouverez des dtails complmentaires concernant les conteneurs
de Qt ladresse http://doc.trolltech.com/4.1/containers.html.
12
Entres/Sorties
Au sommaire de ce chapitre
Lire et crire des donnes binaires
Lire et crire du texte
Parcourir des rpertoires
Intgrer des ressources
Communication inter-processus
Le besoin deffectuer des oprations de lecture et dcriture dans des fichiers ou sur un
autre support est commun presque toute application. Qt fournit un excellent support
pour ces oprations par le biais de QIODevice, une abstraction puissante qui encapsule
des "priphriques" capables de lire et dcrire des blocs doctets. Qt inclut les sousclasses QIODevice suivantes :
QFile
QProcess
QTcpSocket
QUdpSocket
288
Chapitre 12
Entres/Sorties
289
map.insert("green", Qt::green);
map.insert("blue", Qt::blue);
QFile file("facts.dat");
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Cannot open file for writing: "
<< qPrintable(file.errorString()) << endl;
return;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(0x12345678) << image << map;
Si nous ne pouvons pas ouvrir le fichier, nous en informons lutilisateur et rendons le contrle.
La macro qPrintable() retourne un constchar* pour un QString. (Une autre approche
consiste excuter QString::toStdString(), qui retourne un std::string, pour lequel
<iostream> possde une surcharge <<.)
Si le fichier souvre avec succs, nous crons un qDataStream et dfinissons son numro de
version. Le numro de version est un entier qui influence la faon dont les types de donnes Qt
sont reprsents (les types de donnes C++ de base sont toujours reprsents de la mme
faon). Dans Qt 4.1, le format le plus complet est la version 7. Nous pouvons soit coder en dur
la constante 7, soit utiliser le nom symbolique QDataStream::Qt_4_1.
Pour garantir que le nombre 0x12345678 sera bien enregistr en tant quentier non sign de
32 bits sur toutes les plates-formes, nous le convertissons en quint32, un type de donnes
dont les 32 bits sont garantis. Pour assurer linteroprabilit, QDataStream est bas par dfaut
sur Big-Endian, ce qui peut tre modifi en appelant setByteOrder().
Il est inutile de fermer explicitement le fichier, cette opration tant effectue automatiquement
lorsque la variable QFile sort de la porte. Si nous souhaitons vrifier que les donnes ont bien
t crites, nous appelons flush() et vrifions sa valeur de retour (true en cas de succs).
Le code destin lire les donnes reflte celui que nous avons utilis pour les crire :
quint32 n;
QImage image;
QMap<QString, QColor> map;
QFile file("facts.dat");
if (!file.open(QIODevice::ReadOnly)) {
cerr << "Cannot open file for reading: "
<< qPrintable(file.errorString()) << endl;
return;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_1);
in >> n >> image >> map;
290
La version de QDataStream que nous employons pour la lecture est la mme que celle utilise
pour lcriture, ce qui doit toujours tre le cas. En codant en dur le numro de version, nous
garantissons que lapplication est toujours en mesure de lire et dcrire les donnes.
QDataStream stocke les donnes de telle faon que nous puissions les lire parfaitement. Par
exemple, un QByteArray est reprsent sous la forme dun dcompte doctets, suivi des octets
eux-mmes. QDataStream peut aussi tre utilis pour lire et crire des octets bruts, sans entte de dcompte doctets, au moyen de readRawBytes() et de writeRawBytes().
Lors de la lecture partir dun QDataStream, la gestion des erreurs est assez facile. Le flux
possde une valeur status() qui peut tre QDataStream::Ok, QDataStream::ReadPastEnd ou QDataStream::ReadCorruptData. Quand une erreur se produit, loprateur >>
lit toujours zro ou des valeurs vides. Nous pouvons ainsi lire un fichier entier sans nous proccuper derreurs potentielles et vrifier la valeur de status() la fin pour dterminer si ce que
nous avons lu tait valide.
QDataStream gre plusieurs types de donnes C++ et Qt. La liste complte est disponible
ladresse http://doc.trolltech.com/4.1/datastreamformat.html. Nous pouvons galement
ajouter la prise en charge de nos propres types personnaliss en surchargeant les oprateurs
<< et >>. Voici la dfinition dun type de donnes personnalis susceptible dtre utilis avec
QDataStream:
class Painting
{
public:
Painting() { myYear = 0; }
Painting(const QString &title, const QString &artist, int year) {
myTitle = title;
myArtist = artist;
myYear = year;
}
void setTitle(const QString &title) { myTitle = title; }
QString title() const { return myTitle; }
...
private:
QString myTitle;
QString myArtist;
int myYear;
};
QDataStream &operator<<(QDataStream &out, const Painting &painting);
QDataStream &operator>>(QDataStream &in, Painting &painting);
Voici comment nous implmenterions loprateur <<:
QDataStream &operator<<(QDataStream &out, const Painting &painting)
{
out << painting.title() << painting.artist()
<< quint32(painting.year());
return out;
}
Chapitre 12
Entres/Sorties
291
Il existe plusieurs avantages fournir des oprateurs de flux pour les types de donnes personnaliss. Lun deux est que nous pouvons ainsi transmettre des conteneurs qui utilisent le type
personnalis. Par exemple :
QList<Painting> paintings = ...;
out << paintings;
Ceci provoquerait une erreur de compilateur si Painting ne supportait pas << ou >>. Un autre
avantage des oprateurs de flux pour les types personnaliss est que nous pouvons stocker
les valeurs de ces types en tant que QVariant, ce qui les rend plus largement utilisables,
par exemple par les QSetting. Ceci ne fonctionne que si nous enregistrons pralablement
le type en excutant qRegisterMetaTypeStreamOperators<T>(), comme expliqu dans le
Chapitre 11.
Lorsque nous utilisons QDataStream, Qt se charge de lire et dcrire chaque type, dont les
conteneurs avec un nombre arbitraire dlments. Cette caractristique nous vite de structurer
ce que nous crivons et dappliquer une conversion ce que nous lisons. Notre seule obligation
consiste nous assurer que nous lisons tous les types dans leur ordre dcriture, en laissant
Qt le soin de grer tous les dtails.
QDataStream est utile la fois pour nos formats de fichiers dapplication personnaliss et
pour les formats binaires standard. Nous pouvons lire et crire des formats binaires standard en
utilisant les oprateurs de flux sur les types de base (tels que quint16 ou float) ou au moyen
de readRawBytes() et de writeRawBytes(). Si le QDataStream est purement utilis pour
lire et crire des types de donnes C++ de base, il est inutile dappeler setVersion().
292
Jusqu prsent, nous avons charg et enregistr les donnes avec la version code en dur du
flux sous la forme QDataStream::Qt_4_1. Cette approche est simple et sre, mais elle
prsente un lger inconvnient : nous ne pouvons pas tirer parti des formats nouveaux ou mis
jour. Par exemple, si une version ultrieure de Qt ajoutait un nouvel attribut QFont (en plus
de sa famille, de sa taille, etc.) et que nous ayons cod en dur le numro de version en Qt_4_1,
cet attribut ne serait pas enregistr ou charg. Deux solutions soffrent vous. La premire
approche consiste intgrer le numro de version QDataStream dans le fichier :
QDataStream out(&file);
out << quint32(MagicNumber) << quint16(out.version());
(MagicNumber est une constante qui identifie de faon unique le type de fichier.) Avec cette
approche, nous crivons toujours les donnes en utilisant la version la plus rcente de QDataStream. Lorsque nous en venons lire le fichier, nous lisons la version du flux :
quint32 magic;
quint16 streamVersion;
QDataStream in(&file);
in >> magic >> streamVersion;
if (magic!= MagicNumber) {
cerr << "File is not recognized by this application" << endl;
} else if (streamVersion > in.version()) {
cerr << "File is from a more recent version of the application"
<< endl;
return false;
}
in.setVersion(streamVersion);
Nous pouvons lire les donnes tant que la version du flux est infrieure ou gale la version
utilise par lapplication. Dans le cas contraire, nous signalons une erreur.
Si le format de fichier contient un numro de version personnel, nous pouvons lutiliser pour
dduire le numro de version du flux au lieu de le stocker explicitement. Supposons, par exemple, que le format de fichier est destin la version 1.3 de notre application. Nous pouvons
alors crire les donnes comme suit :
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_1);
out << quint32(MagicNumber) << quint16(0x0103);
Lorsque nous les relisons, nous dterminons quelle version de QDataStream utiliser selon le
numro de version de lapplication :
QDataStream in(&file);
in >> magic >> appVersion;
Chapitre 12
Entres/Sorties
293
if (magic!= MagicNumber) {
cerr << "File is not recognized by this application" << endl;
return false;
} else if (appVersion > 0x0103) {
cerr << "File is from a more recent version of the application"
<< endl;
return false;
}
if (appVersion < 0x0103) {
in.setVersion(QDataStream::Qt_3_0);
} else {
in.setVersion(QDataStream::Qt_4_1);
}
Dans cet exemple, nous spcifions que tout fichier enregistr avec une version antrieure la
version 1.3 de lapplication utilise la version de flux de donnes 4 (Qt_3_0) et que les fichiers
enregistrs avec la version 1.3 de lapplication utilisent la version de flux de donnes 7
(Qt_4_1).
En rsum, il existe trois stratgies pour grer les versions de QDataStream: coder en dur le
numro de version, crire et lire explicitement le numro de version et utiliser diffrents numros de version cods en dur en fonction de la version de lapplication. Toutes peuvent tre
employes afin dassurer que les donnes crites par une ancienne version dune application
peuvent tre lues par une nouvelle version. Une fois cette stratgie de gestion des versions de
QDataStream choisie, la lecture et lcriture de donnes binaires au moyen de Qt est la fois
simple et fiable.
Si nous souhaitons lire ou crire un fichier en une seule fois, nous pouvons viter lutilisation
de QDataStream et recourir la place aux fonctions write() et readAll() de QIODevice.
Par exemple :
bool copyFile(const QString &source, const QString &dest)
{
QFile sourceFile(source);
if (!sourceFile.open(QIODevice::ReadOnly))
return false;
QFile destFile(dest);
if (!destFile.open(QIODevice::WriteOnly))
return false;
destFile.write(sourceFile.readAll());
return sourceFile.error() == QFile::NoError
&& destFile.error() == QFile::NoError;
}
Sur la ligne de lappel de readAll(), le contenu entier du fichier dentre est lu et plac dans
un QByteArray. Il est alors transmis la fonction write() pour tre crit dans le fichier de
sortie. Le fait davoir toutes les donnes dans un QByteArray ncessite plus de mmoire que
294
de les lire lment par lment, mais offre quelques avantages. Nous pouvons excuter, par
exemple, qCompress() et qUncompress() pour les compresser et les dcompresser.
Il existe dautres scnarios o il est plus appropri daccder directement QIODevice que
dutiliser QDataStream. QIODevice fournit une fonction peek() qui retourne les octets de
donne suivants sans changer lemplacement du priphrique ainsi quune fonction ungetChar() qui permet de revenir un octet en arrire. Ceci fonctionne la fois pour les priphriques daccs alatoire (tels que les fichiers) et pour les priphriques squentiels (tels que les
sockets rseau). Il existe galement une fonction seek() qui dfinit la position des priphriques supportant laccs alatoire.
Les formats de fichier binaires offrent les moyens les plus souples et les plus compacts de stockage de donnes. QDataStream facilite laccs aux donnes binaires. En plus des exemples de
cette section, nous avons dj tudi lutilisation de QDataStream au Chapitre 4 pour lire et
crire des fichiers de tableur, et nous lutiliserons de nouveau dans le Chapitre 19 pour lire et crire
des fichiers de curseur Windows.
Chapitre 12
Entres/Sorties
295
Lcriture du texte est trs facile, mais sa lecture peut reprsenter un vritable dfit, car les
donnes textuelles (contrairement aux donnes binaires crites au moyen de QDataStream)
sont fondamentalement ambigus. Considrons lexemple suivant :
out << "Norway" << "Sweden";
Si out est un QTextStream, les donnes vritablement crites sont la chane "NorwaySweden".
Nous ne pouvons pas vraiment nous attendre ce que le code suivant lise les donnes correctement :
in >> str1 >> str2;
En fait, str1 obtient le mot "NorwaySweden" entier et str2 nobtient rien. Ce problme ne
se pose pas avec QDataStream, car la longueur de chaque chane est stocke devant les
donnes.
Pour les formats de fichier complexes, un analyseur risque dtre requis. Un tel analyseur peut
fonctionner en lisant les donnes caractre par caractre en utilisant un >> sur un QChar, ou
ligne par ligne au moyen de QTextStream::readLine(). A la fin de cette section, nous
prsentons deux petits exemples. Le premier lit un fichier dentre ligne par ligne et lautre le
lit caractre par caractre. Pour ce qui est des analyseurs qui traitent le texte entier, nous
pouvons lire le fichier complet en une seule fois laide de QTextStream::readAll() si
nous ne nous proccupons pas de lutilisation de la mmoire, ou si nous savons que le fichier
est petit.
Par dfaut, QTextStream utilise le codage local du systme (par exemple, ISO 8859-1 ou ISO
8859-15 aux Etats-Unis et dans une grande partie de lEurope) pour les oprations de lecture et
dcriture. Vous pouvez changer ceci en excutant setCodec() comme suit :
stream.setCodec("UTF-8");
UTF-8 est un codage populaire compatible ASCII capable de reprsenter la totalit du jeu de
caractres Unicode. Pour plus dinformations concernant Unicode et la prise en charge des
codages de QTextStream, reportez-vous au Chapitre 17 (Internationalisation).
QTextStream possde plusieurs options modeles sur celles offertes par <iostream>. Elles
peuvent tre dfinies en transmettant des objets spciaux, nomms manipulateurs de flux, au
flux pour modifier son tat. Lexemple suivant dfinit les options showbase, uppercasedigits
et hex avant la sortie de lentier 12345678, produisant le texte "0xBC614E" :
out << showbase << uppercasedigits << hex << 12345678;
Les options peuvent galement tre dfinies en utilisant les fonctions membres :
out.setNumberFlags(QTextStream::ShowBase
| QTextStream::UppercaseDigits);
out.setIntegerBase(16);
out << 12345678;
296
setIntegerBase(int)
0
Binaire
Octal
10
Dcimal
16
Hexadcimal
setNumberFlags(NumberFlags)
ShowBase
ForceSign
ForcePoint
UppercaseBase
UppercaseDigits
setRealNumberNotation(RealNumberNotation)
FixedNotation
ScientificNotation
SmartNotation
setRealNumberPrecision(int)
Dfinit le nombre maximum de chiffres devant tre gnrs (6 par dfaut)
setFieldWidth(int)
Dfinit la taille minimum dun champ (0 par dfaut)
setFieldAlignment(FieldAlignment)
AlignLeft
AlignRight
AlignCenter
AlignAccountingStyle
setPadChar(QChar)
Dfini le caractre utiliser pour lalignement (espace par dfaut)
Figure 12.1
Fonctions destines dfinir les options de QTextStream
Chapitre 12
Entres/Sorties
297
Comme QDataStream, QTextStream agit sur une sous-classe de QIODevice, qui peut tre un
QFile, un QTemporaryFile, un QBuffer, un QProcess, un QTcpSocket ou un
QUdpSocket. En outre, il peut tre utilis directement sur un QString. Par exemple :
QString str;
QTextStream(&str) << oct << 31 << " " << dec << 25 << endl;
Le contenu de str est donc "37 25/n", car le nombre dcimal 31 est exprim sous la forme 37
en base huit. Dans ce cas, il nest pas ncessaire de dfinir un codage sur le flux, car QString
est toujours Unicode.
Examinons un exemple simple de format de fichier bas sur du texte. Dans lapplication
Spreadsheet dcrite dans la Partie 1, nous avons utilis un format binaire pour stocker les
donnes. Ces donnes consistaient en une squence de triplets (ligne, colonne, formule), une
pour chaque cellule non vide. Il nest pas difficile dcrire les donnes sous forme de texte.
Voici un extrait dune version rvise de Spreadsheet::writeFile().
QTextStream out(&file);
for (int row = 0; row < RowCount; ++row) {
for (int column = 0; column < ColumnCount; ++column) {
QString str = formula(row, column);
if (!str.isEmpty())
out << row << " " << column << " " << str << endl;
}
}
Nous avons utilis un format simple, chaque ligne reprsentant une cellule avec des espaces
entre la ligne et la colonne ainsi quentre la colonne et la formule. La formule contient des
espaces, mais nous pouvons supposer quelle ne comporte aucun /n (qui insre un retour la
ligne). Examinons maintenant le code de lecture correspondant :
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
QStringList fields = line.split( );
if (fields.size() >= 3) {
int row = fields.takeFirst().toInt();
int column = fields.takeFirst().toInt();
setFormula(row, column, fields.join( ));
}
}
Nous lisons les donnes de Spreadsheet ligne par ligne. La fonction readLine() supprime le
/n de fin. QString::split() retourne une liste de chanes de caractres qui est scinde
lemplacement dapparition du sparateur qui lui est fournit. Par exemple, la ligne "5 19 Total
Value" rsulte en une liste de quatre lments ["5", "19", "Total", "Value"].
Si nous disposons au moins de trois champs, nous sommes prts extraire les donnes. La
fonction QStringList::takeFirst() supprime le premier lment dune liste et retourne
llment supprim. Nous lutilisons pour extraire les nombres de ligne et de colonne.
298
Nous ne ralisons aucune vrification derreur. Si nous lisons une valeur de ligne ou de
colonne non entire, QString::toInt() retourne 0. Lorsque nous appelons setFormula(),
nous devons concatner les champs restants en une seule chane.
Dans notre deuxime exemple de QTextStream, nous utilisons une approche caractre par
caractre pour implmenter un programme qui lit un fichier texte et met en sortie le mme
texte avec les espaces de fin supprims et toutes les tabulations remplaces par des espaces.
Le programme accomplit cette tche dans la fonction tidyFile():
void tidyFile(QIODevice *inDevice, QIODevice *outDevice)
{
QTextStream in(inDevice);
QTextStream out(outDevice);
const int TabSize = 8;
int endlCount = 0;
int spaceCount = 0;
int column = 0;
QChar ch;
while (!in.atEnd()) {
in >> ch;
if (ch == \n) {
++endlCount;
spaceCount = 0;
column = 0;
} else if (ch == \t) {
int size = TabSize - (column % TabSize);
spaceCount += size;
column += size;
} else if (ch == ) {
++spaceCount;
++column;
} else {
while (endlCount > 0) {
out << endl;
--endlCount;
column = 0;
}
while (spaceCount > 0) {
out << ;
--spaceCount;
++column;
}
out << ch;
++column;
}
}
out << endl;
}
Chapitre 12
Entres/Sorties
299
Nous crons un QTextStream dentre et de sortie bas sur les QIODevice transmis la fonction. Nous conservons trois lments dtat : un dcompte des nouvelles lignes, un dcompte
des espaces et lemplacement de colonne courant dans la ligne en cours (pour convertir les
tabulations en un nombre despaces correct).
Lanalyse est faite dans une boucle while qui parcourt chaque caractre du fichier dentre. Le
code prsente quelques subtilits certains endroits. Par exemple, bien que nous dfinissions
tabSize en 8, nous remplaons les tabulations par le nombre despaces qui permet datteindre
le taquet de tabulation suivant, au lieu de remplacer grossirement chaque tabulation par huit
espaces. Dans le cas dune nouvelle ligne, dune nouvelle tabulation ou dun nouvel espace,
nous mettons simplement jour les donnes dtat. Pour les autres types de caractres, nous
produisons une sortie. Avant dcrire le caractre nous introduisons tous les espaces et les
nouvelles lignes ncessaires (pour respecter les lignes vierges et prserver le retrait) et mettons
jour ltat.
int main()
{
QFile inFile;
QFile outFile;
inFile.open(stdin, QFile::ReadOnly);
outFile.open(stdout, QFile::WriteOnly);
tidyFile(&inFile, &outFile);
return 0;
}
Pour cet exemple, nous navons pas besoin dobjet QApplication, car nous nutilisons que
les classes doutils de Qt. Vous trouverez la liste de toutes les classes doutils ladresse
http://doc.trolltech.com/4.1/tools.html. Nous avons suppos que le programme est utilis en
tant que filtre, par exemple :
tidy < cool.cpp > cooler.cpp
Il serait facile de le dvelopper afin de grer les noms de fichier qui seraient transmis sur la
ligne de commande ainsi que pour filtrer cin en cout.
Comme il sagit dune application de console, le fichier .pro diffre lgrement de ceux que
nous avons rencontrs pour les applications GUI :
TEMPLATE
QT
CONFIG
CONFIG
SOURCES
=
=
+=
-=
=
app
core
console
app_bundle
tidy.cpp
Nous ntablissons de liaison quavec QtCore, car nous nutilisons aucune fonctionnalit GUI.
Puis nous spcifions que nous souhaitons activer la sortie de la console sur Windows et que
nous ne voulons pas que lapplication soit hberge dans un package sur Mac OS X.
300
Pour lire et crire des fichiers ASCII ou ISO 8859-1 (Latin-1) bruts, il est possible dutiliser
directement lAPI de QIODevice au lieu de QTextStream. Mais cette mthode est rarement
conseille dans la mesure o la plupart des applications doivent prendre en charge dautres
codages un stade ou un autre, et o seul QTextStream offre une prise en charge parfaite de
ces codages. Si vous souhaitez nanmoins crire le texte directement dans un QIODevice,
vous devez spcifier explicitement la balise QIODevice::Text dans la fonction open().
Par exemple :
file.open(QIODevice::WriteOnly | QIODevice::Text);
Lors de lcriture, cette balise indique QIODevice de convertir les caractres \n en des
squences "\r\n" sur Windows. Lors de la lecture, elle indique au priphrique dignorer les
caractres r sur toutes les plates-formes. Nous pouvons alors supposer que la fin de chaque
ligne est marque par un caractre de nouvelle ligne n quelle que soit la convention de fin de
ligne utilise par le systme dexploitation.
Chapitre 12
Entres/Sorties
301
Nous commenons par crer un objet QDir en utilisant un chemin donn, qui peut tre relatif
au rpertoire courant ou absolu. Nous transmettons deux arguments la fonction
entryList(). Le premier est une liste de filtres de noms de fichiers. Ils peuvent contenir les
caractres gnriques * et ?. Dans cet exemple, nous ralisons un filtrage de faon
ninclure que les formats de fichiers susceptibles dtre lus par QImage. Le second argument
spcifie le type dentre souhait (fichiers normaux, rpertoires, etc.).
Nous parcourons la liste de fichiers, en additionnant leurs tailles. La classe QFileInfo nous
permet daccder aux attributs dun fichier, tels que la taille, les autorisations, le propritaire et
lhorodateur de ce fichier.
Le deuxime appel de entryList() rcupre tous les sous-rpertoires de ce rpertoire. Nous
les parcourons et nous appelons imageSpace() rcursivement pour tablir la taille cumule de
leurs images.
Pour crer le chemin de chaque sous-rpertoire, nous combinons de chemin du rpertoire en
cours avec le nom du sous-rpertoire, en les sparant par un slash (/).
QDir traite / comme un sparateur de rpertoires sur toutes les plates-formes. Pour prsenter
les chemins lutilisateur, nous pouvons appeler la fonction statique QDir::convertSeparators() qui convertit les slash en sparateurs spcifiques la plate-forme.
Ajoutons une fonction main() notre petit programme :
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
QString path = QDir::currentPath();
if (args.count() > 1)
path = args[1];
cout << "Space used by images in " << qPrintable(path)
<< " and its subdirectories is " << (imageSpace(path) / 1024)
<< " KB" << endl;
return 0;
}
302
myresourcefile.qrc est un fichier XML qui rpertorie les fichiers intgrer dans lexcutable.
Imaginons que nous crivions une application destine rpertorier des coordonnes. Pour des
raisons pratiques, nous souhaitons intgrer les indicatifs tlphoniques internationaux dans
lexcutable. Si le fichier se situe dans le rpertoire datafiles de lapplication, le fichier de
ressources serait le suivant :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>datafiles/phone-codes.dat</file>
</qresource>
</RCC>
Depuis lapplication, les ressources sont identifies par le prfixe de chemin :/. Dans cet exemple,
le chemin du fichier contenant les indicatifs tlphoniques est :/datafiles/phonecodes.dat et peut tre lu comme tout autre fichier en utilisant QFile.
Lintgration de donnes dans lexcutable prsente plusieurs avantages : les donnes ne
peuvent tre perdues et cette opration permet la cration dexcutables vritablement autonomes (si une liaison statique est galement utilise). Les inconvnients sont les suivants : si
les donnes intgres doivent tre changes, il est impratif de remplacer lexcutable entier, et
la taille de ce dernier sera plus importante car il doit sadapter aux donnes intgres.
Le systme de ressources de Qt fournit des fonctionnalits supplmentaires, telles que la prise
en charge des alias de noms de fichiers et la localisation. Ces fonctionnalits sont documentes
ladresse http://doc.trolltech.com/4.1/resources.html.
Chapitre 12
Entres/Sorties
303
Communication inter-processus
La classe QProcess nous permet dexcuter des programmes externes et dinteragir avec
ceux-ci. La classe fonctionne de faon asynchrone, effectuant sa tche larrire-plan de sorte
que linterface utilisateur reste ractive. QProcess met des signaux pour nous indiquer quand
le processus externe dtient des donnes ou a termin.
Nous allons rviser le code dune petite application qui fournit une interface utilisateur pour un
programme externe de conversion des images. Pour cet exemple, nous nous reposons sur le
programme ImageMagick convert, qui est disponible gratuitement sur toutes les platesformes principales. (Voir Figure 12.2)
Figure 12.2
Lapplication
Image Converter
Linterface utilisateur a t cre dans Qt Designer. Le fichier .ui se trouve sur le site web de
Pearson, www.pearson.fr, la page ddie ce livre. Ici, nous allons nous concentrer sur
la sous-classe qui hrite de la classe Ui::ConvertDialog
.
gnre par le compilateur
d'interface utilisateur. Commenons par len-tte :
#ifndef CONVERTDIALOG_H
#define CONVERTDIALOG_H
#include <QDialog>
#include <QProcess>
#include "ui_convertdialog.h"
class ConvertDialog: public QDialog, public Ui::ConvertDialog
{
Q_OBJECT
public:
ConvertDialog(QWidget *parent = 0);
304
private
void
void
void
void
void
slots:
on_browseButton_clicked();
on_convertButton_clicked();
updateOutputTextEdit();
processFinished(int exitCode, QProcess::ExitStatus exitStatus);
processError(QProcess::ProcessError error);
private:
QProcess process;
QString targetFile;
};
#endif
Len-tte est conforme celui dune sous-classe de formulaire Qt Designer. Grce au mcanisme de connexion automatique de Qt Designer, les slots on_browseButton_clicked() et
on_convertButton_clicked() sont automatiquement connects aux signaux clicked()
des boutons Browse et Convert.
ConvertDialog::ConvertDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
connect(&process, SIGNAL(readyReadStandardError()),
this, SLOT(updateOutputTextEdit()));
connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)),
this, SLOT(processFinished(int, QProcess::ExitStatus)));
connect(&process, SIGNAL(error(QProcess::ProcessError)),
this, SLOT(processError(QProcess::ProcessError)));
}
Lappel de setupUi() cre et positionne tous les widgets du formulaire, tablit les connexions
signal/slot pour les slots on_objectName_signalName() et connecte le bouton Quit
QDialog::accept(). Nous connectons ensuite manuellement trois signaux de lobjet QProcess trois slots privs. A chaque fois que le processus externe va dtecter des donnes sur
son cerr, il les grera avec updateOutputTextEdit().
void ConvertDialog::on_browseButton_clicked()
{
QString initialName = sourceFileEdit->text();
if (initialName.isEmpty())
initialName = QDir::homePath();
QString fileName =
QFileDialog::getOpenFileName(this, tr("Choose File"),
initialName);
fileName = QDir::convertSeparators(fileName);
if (!fileName.isEmpty()) {
sourceFileEdit->setText(fileName);
convertButton->setEnabled(true);
}
}
Chapitre 12
Entres/Sorties
305
Lorsque lutilisateur clique sur le bouton Convert, nous copions le nom du fichier source et
changeons lextension afin quelle corresponde au format du fichier cible.
Nous utilisons le sparateur de rpertoires spcifique la plate-forme (/ ou \, disponible
sous la forme QDir::separator()) au lieu de coder les slash en dur, car le nom du fichier
sera visible pour lutilisateur.
Nous dsactivons alors le bouton Convert pour viter que lutilisateur ne lance accidentellement plusieurs conversions, et nous effaons lditeur de texte qui nous permet dafficher les
informations dtat.
Pour lancer le processus externe, nous appelons QProcess::start() avec le nom du
programme excuter (convert) et tous les arguments requis. Dans ce cas, nous transmettons
les balises enhance et monochrome si lutilisateur a coch les options appropries, suivies
des noms des fichiers source et cible. Le programme convert dduit la conversion requise
partir des extensions de fichier.
void ConvertDialog::updateOutputTextEdit()
{
QByteArray newData = process.readAllStandardError();
QString text = outputTextEdit->toPlainText()
+ QString::fromLocal8Bit(newData);
outputTextEdit->setPlainText(text);
}
306
Lorsque le processus externe effectue une opration dcriture dans le cerr, le slot updateOutputTextEdit() est appel. Nous lisons le texte derreur et lajoutons au texte existant
de QTextEdit.
void ConvertDialog::processFinished(int exitCode,
QProcess::ExitStatus exitStatus)
{
if (exitStatus == QProcess::CrashExit) {
outputTextEdit->append(tr("Conversion program crashed"));
} else if (exitCode!= 0) {
outputTextEdit->append(tr("Conversion failed"));
} else {
outputTextEdit->append(tr("File %1 created").arg(targetFile));
}
convertButton->setEnabled(true);
}
Une fois le processus termin, nous faisons connatre le rsultat lutilisateur et activons le
bouton Convert.
void ConvertDialog::processError(QProcess::ProcessError error)
{
if (error == QProcess::FailedToStart) {
outputTextEdit->append(tr("Conversion program not found"));
convertButton->setEnabled(true);
}
}
Si le processus ne peut tre lanc, QProcess met error() au lieu de finished(). Nous
rapportons toute erreur et activons le bouton Click.
Dans cet exemple, nous avons effectu les conversions de fichier de faon asynchrone nous
avons demand QProcess dexcuter le programme convert et de rendre immdiatement le
contrle lapplication. Cette mthode laisse linterface utilisateur ractive puisque le processus sexcute larrire-plan. Mais dans certains cas, il est ncessaire que le processus externe
soit termin avant de pouvoir poursuivre avec notre application. Dans de telles situations,
QProcess doit agir de faon synchrone.
Les applications qui prennent en charge ldition de texte brut dans lditeur de texte prfr de
lutilisateur sont typiques de celles dont le comportement doit tre synchrone. Limplmentation sobtient facilement en utilisant QProcess. Supposons, par exemple, que le texte brut se
trouve dans un QTextEdit, et que vous fournissiez un bouton Edit sur lequel lutilisateur peut
cliquer, connect un slot edit().
void ExternalEditor::edit()
{
QTemporaryFile outFile;
if (!outFile.open())
return;
QString fileName = outFile.fileName();
Chapitre 12
Entres/Sorties
307
QTextStream out(&outFile);
out << textEdit->toPlainText();
outFile.close();
QProcess::execute(editor, QStringList() << options << fileName);
QFile inFile(fileName);
if (!inFile.open(QIODevice::ReadOnly))
return;
QTextStream in(&inFile);
textEdit->setPlainText(in.readAll());
}
Nous utilisons QTemporaryFile pour crer un fichier vide avec un nom unique. Nous ne
spcifions aucun argument pour QTemporaryFile::open() puisquelle est judicieusement
dfinie pour ouvrir en mode criture/lecture par dfaut. Nous crivons le contenu de lditeur
de texte dans le fichier temporaire, puis nous fermons ce dernier car certains diteurs de texte
ne peuvent travailler avec des fichiers dj ouverts.
La fonction statique QProcess::execute() excute un processus externe et provoque un
blocage jusqu ce que le processus soit termin. Largument editor est un QString contenant le nom dun excutable diteur ("gvim", par exemple). Largument options est un
QStringList (contenant un lment, "-f", si nous utilisons gvim).
Une fois que lutilisateur a ferm lditeur de texte, le processus se termine ainsi que lappel de
execute(). Nous ouvrons alors le fichier temporaire et lisons son contenu dans le QTextEdit. QTemporaryFile supprime automatiquement le fichier temporaire lorsque lobjet sort
de la porte.
Les connexions signal/slot ne sont pas ncessaires lorsque QProcess est utilis de faon
synchrone. Si un contrle plus fin que celui fourni par la fonction statique execute() est
requis, nous pouvons utiliser une autre approche qui implique de crer un objet QProcess et
dappeler start() sur celui-ci, puis de forcer un blocage en appelant QProcess::waitForStarted(), et en cas de succs, en appelant QProcess::waitForFinished(). Reportez-vous
la documentation de rfrence de QProcess pour trouver un exemple utilisant cette approche.
Dans cette section, nous avons exploit QProcess pour obtenir laccs une fonctionnalit
prexistante. Lutilisation dapplications dj existantes reprsente un gain de temps et vous
vite davoir rsoudre des problmes trs loigns de lobjectif principal de votre application.
Une autre faon daccder une fonctionnalit prexistante consiste tablir une liaison vers
une bibliothque qui la fournit. Si une telle bibliothque nexiste pas, vous pouvez aussi envisager dencapsuler votre application de console dans un QProcess.
QProcess peut galement servir lancer dautres applications GUI, telles quun navigateur
Web ou un client email. Si, cependant, votre objectif est la communication entre applications et
non la simple excution de lune partir de lautre, il serait prfrable de les faire communiquer directement en utilisant les classes de gestion de rseau de Qt ou lextension ActiveQt sur
Windows.
13
Les bases de donnes
Au sommaire de ce chapitre
Connexion et excution de requtes
Prsenter les donnes sous une forme tabulaire
Implmenter des formulaires matre/dtail
Le module QtSql fournit une interface indpendante de la plate-forme pour accder aux
bases de donnes. Cette interface est prise en charge par un ensemble de classes qui
utilisent larchitecture modle/vue de Qt pour intgrer la base de donnes linterface
utilisateur. Ce chapitre suppose une certaine familiarit avec les classes modle/vue de
Qt, traites dans le Chapitre 10.
310
Une connexion de base de donnes est reprsente par un objet QSqlDatabase. Qt utilise des
pilotes pour communiquer avec les diverses API de base de donnes. Qt Desktop Edition inclut
les pilotes suivants :
Pilote
Base de donnes
QDB2
QIBASE
Borland InterBase
QMYSQL
MySQL
QOCI
QODBC
QPSQL
QSQLITE
QSQLITE2
SQLite version 2
QTDS
Tous les pilotes ne sont pas fournis avec Qt Open Source Edition, en raison de restrictions de
licence. Lors de la configuration de Qt, nous pouvons choisir entre inclure directement les pilotes SQL Qt et les crer en tant que plugin. Qt est fourni avec la base de donnes SQLite, une
base de donnes in-process (qui sintgre aux applications) de domaine public.
Pour les utilisateurs familiariss avec la syntaxe SQL, la classe QSqlQuery reprsente un bon
moyen dexcuter directement des instructions SQL arbitraires et de grer leurs rsultats. Pour
les utilisateurs qui prfrent une interface de base de donnes plus volue masquant la syntaxe
SQL, QSqlTableModel et QSqlRelationalTableModel fournissent des abstractions acceptables. Ces classes reprsentent une table SQL de la mme faon que les autres classes modle
de Qt (traites dans le Chapitre 10).Elles peuvent tre utilises de faon autonome pour
parcourir et modifier des donnes dans le code, ou encore tre associes des vues par le biais
desquelles les utilisateurs finaux peuvent afficher et modifier les donnes.
Chapitre 13
311
db.setHostName("mozart.konkordia.edu");
db.setDatabaseName("musicdb");
db.setUserName("gbatstone");
db.setPassword("T17aV44");
if (!db.open()) {
QMessageBox::critical(0, QObject::tr("Database Error"),
db.lastError().text());
return false;
}
return true;
}
return app.exec();
}
Une fois quune connexion est tablie, nous pouvons utiliser QSqlQuery pour excuter toute
instruction SQL supporte par la base de donnes concerne. Par exemple, voici comment
excuter une instruction SELECT:
QSqlQuery query;
query.exec("SELECT title, year FROM cd WHERE year >= 1998");
312
La fonction value() retourne la valeur dun champ en tant que QVariant. Les champs sont
numrots en partant de 0 dans lordre fourni dans linstruction SELECT. La classe QVariant
peut contenir de nombreux types Qt et C++, dont int et QString. Les diffrents types de
donnes susceptibles dtre stocks dans une base de donnes sont transforms en types Qt et
C++ correspondants et stocks en tant que QVariant. Par exemple, un VARCHAR est reprsent
en tant que QString et un DATETIME en tant que QDateTime.
QSqlQuery fournit quelques autres fonctions destines parcourir lensemble de rsultats :
first(), last(), previous() et seek(). Ces fonctions sont pratiques, mais pour certaines
bases de donnes elles peuvent savrer plus lentes et plus gourmandes en mmoire que
next(). Pour optimiser facilement dans le cas de jeux de donnes de taille importante, nous
pouvons appeler QSqlQuery::setForwardOnly(true) avant dappeler exec(), puis
seulement utiliser next() pour parcourir lensemble de rsultats.
Nous avons prcdemment prsent la requte SQL comme un argument de QSqlQuery::exec(), mais il est galement possible de la transmettre directement au constructeur,
qui lexcute immdiatement :
QSqlQuery query("SELECT title, year FROM cd WHERE year >= 1998");
Nous pouvons contrler lexistence dune erreur en appelant isActive() sur la requte :
if (!query.isActive())
QMessageBox::warning(this, tr("Database Error"),
query.lastError().text());
Si aucune erreur napparat, la requte devient "active" et nous pouvons utiliser next() pour
parcourir lensemble de rsultats.
Il est presque aussi facile de raliser un INSERT que deffectuer un SELECT:
QSqlQuery query("INSERT INTO cd (id, artistid, title, year) "
"VALUES (203, 102, Living in America, 2002)");
Chapitre 13
313
query.bindValue(":year", 2002);
query.exec();
Voici le mme exemple utilisant des espaces rservs positionnels de style ODBC :
QSqlQuery query;
query.prepare("INSERT INTO cd (id, artistid, title, year) "
"VALUES (?,?,?,?)");
query.addBindValue(203);
query.addBindValue(102);
query.addBindValue("Living in America");
query.addBindValue(2002);
query.exec();
Aprs lappel exec(), nous pouvons appeler bindValue() ou addBindValue() pour lier
de nouvelles valeurs, puis nous appelons de nouveau exec() pour excuter la requte avec ces
nouvelles valeurs.
Les espaces rservs sont souvent utiliss pour des donnes binaires contenant des caractres
non ASCII ou nappartenant pas au jeu de caractres Latin-1. A larrire-plan, Qt utilise
Unicode avec les bases de donnes qui prennent en charge cette norme. Pour les autres, Qt
convertit de faon transparente les chanes en codage appropri.
Qt supporte les transactions SQL sur les bases de donnes pour lesquelles elles sont disponibles. Pour lancer une transaction, nous appelons transaction() sur lobjet QSqlDatabase
qui reprsente la connexion de base de donnes. Pour mettre fin la transaction, nous appelons
soit commit(), soit rollback(). Voici, par exemple, comment rechercher une cl trangre et
excuter une instruction INSERT dans une transaction :
QSqlDatabase::database().transaction();
QSqlQuery query;
query.exec("SELECT id FROM artist WHERE name = Gluecifer");
if (query.next()) {
int artistId = query.value(0).toInt();
query.exec("INSERT INTO cd (id, artistid, title, year) "
"VALUES (201, " + QString::number(artistId)
+ ", Riding the Tiger, 1997)");
}
QSqlDatabase::database().commit();
314
Plusieurs autres fonctionnalits de bases de donnes peuvent tre testes, notamment la prise
en charge des BLOB (objets binaires volumineux), dUnicode et des requtes prpares.
Dans les exemples fournis jusqu prsent, nous avons suppos que lapplication utilise une
seule connexion de base de donnes. Pour crer plusieurs connexions, il est possible de transmettre un nom en tant que second argument addDatabase(). Par exemple :
QSqlDatabase db = QSqlDatabase::addDatabase("QPSQL", "OTHER");
db.setHostName("saturn.mcmanamy.edu");
db.setDatabaseName("starsdb");
db.setUserName("hilbert");
db.setPassword("ixtapa7");
Nous pouvons alors obtenir un pointeur vers lobjet QSqlDatabase en transmettant le nom
QSqlDatabase::database():
QSqlDatabase db = QSqlDatabase::database("OTHER");
Pour excuter des requtes en utilisant lautre connexion, nous transmettons lobjet QSqlDatabase au constructeur de QSqlQuery:
QSqlQuery query(db);
query.exec("SELECT id FROM artist WHERE name = Mando Diao");
Des connexions multiples peuvent savrer utiles si vous souhaitez effectuer plusieurs transactions la fois, chaque connexion ne pouvant grer quune seule transaction. Lorsque nous utilisons les connexions de base de donnes multiples, nous pouvons toujours avoir une connexion
non nomme qui sera exploite par QSqlQuery si aucune connexion nest spcifie.
En plus de QSqlQuery, Qt fournit la classe QSqlTableModel comme interface de haut niveau,
ce qui permet dviter lemploi du code SQL brut pour raliser les oprations SQL les plus
courantes (SELECT, INSERT, UPDATE et DELETE). La classe peut tre utilise de faon autonome pour manipuler une base de donnes sans aucune implication GUI. Elle peut galement
tre utilise comme source de donnes pour QListView ou QTableView.
Voici un exemple qui utilise QSqlTableModel pour raliser un SELECT:
QSqlTableModel model;
model.setTable("cd");
model.setFilter("year >= 1998");
model.select();
Chapitre 13
315
Pour insrer un enregistrement dans une table de base de donnes, nous utilisons la mme
approche que pour une insertion dans tout modle bidimensionnel : en premier lieu, nous appelons insertRow() pour crer une nouvelle ligne (enregistrement) vide, puis nous faisons appel
setData() pour dfinir les valeurs de chaque colonne (champ).
QSqlTableModel model;
model.setTable("cd");
int row = 0;
model.insertRows(row, 1);
model.setData(model.index(row,
model.setData(model.index(row,
model.setData(model.index(row,
model.setData(model.index(row,
model.submitAll();
0),
1),
2),
3),
113);
"Shanghai My Heart");
224);
2003);
Aprs lappel submitAll(), lenregistrement peut tre dplac vers un emplacement diffrent dans la ligne, selon lorganisation de la table. Lappel de cette fonction retournera false
si linsertion a chou.
Une diffrence importante entre un modle SQL et un modle standard est que dans le premier
cas, nous devons appeler submitAll() pour valider une modification dans la base de donnes.
Pour mettre jour un enregistrement, nous devons tout dabord placer le QSqlTableModel sur
lenregistrement modifier (en excutant select()), par exemple. Nous extrayons ensuite
lenregistrement, mettons jour les champs voulus, puis rcrivons nos modifications dans la
base de donnes :
QSqlTableModel model;
model.setTable("cd");
model.setFilter("id = 125");
model.select();
if (model.rowCount() == 1) {
316
Si un enregistrement correspond au filtre spcifi, nous le rcuprons au moyen de QSqlTableModel::record(). Nous appliquons nos modifications et remplaons lenregistrement initial
par ce dernier.
Comme dans le cas dun modle non SQL, il est galement possible de raliser une mise jour
au moyen de setData(). Les index de modle que nous rcuprons correspondent une ligne
ou une colonne donne :
model.select();
if (model.rowCount() == 1) {
model.setData(model.index(0, 1), "Melody A.M.");
model.setData(model.index(0, 3),
model.data(model.index(0, 3)).toInt() + 1);
model.submitAll();
}
Les classes QSqlQuery et QSqlTableModel fournissent une interface entre Qt et une base de
donnes SQL. En utilisant ces classes, nous pouvons crer des formulaires qui prsentent les
donnes aux utilisateurs et leur permettent dinsrer, de mettre jour et de supprimer des enregistrements.
Chapitre 13
317
318
Certaines bases de donnes ne supportent pas les cls trangres. Dans ce cas, nous devons
supprimer les clauses FOREIGN KEY. Lexemple fonctionnera toujours, mais la base de donnes
nappliquera pas lintgrit rfrentielle. (Voir Figure 13.2)
Figure 13.2
Les tables
de lapplication
CD Collection
artist
id
name
country
1:N
cd
id
title
artistid
year
1:N
track
id
title
duration
cdid
Dans cette section, nous allons crire une bote de dialogue dans laquelle les utilisateurs pourront modifier une liste dartistes en utilisant une forme tabulaire simple. Lutilisateur peut insrer ou supprimer des artistes au moyen des boutons du formulaire. Les mises jour peuvent
tre appliques directement, simplement en modifiant le texte des cellules. Les changements
sont appliqus la base de donnes lorsque lutilisateur appuie sur Entre ou passe un autre
enregistrement. (Voir Figure 13.3)
Figure 13.3
La bote de dialogue
ArtistForm
Chapitre 13
319
Le constructeur est trs similaire celui qui serait utilis pour crer un formulaire bas sur un
modle non SQL :
ArtistForm::ArtistForm(const QString &name, QWidget *parent)
: QDialog(parent)
{
model = new QSqlTableModel(this);
model->setTable("artist");
model->setSort(Artist_Name, Qt::AscendingOrder);
model->setHeaderData(Artist_Name, Qt::Horizontal, tr("Name"));
model->setHeaderData(Artist_Country, Qt::Horizontal, tr("Country"));
model->select();
connect(model, SIGNAL(beforeInsert(QSqlRecord &)),
this, SLOT(beforeInsertArtist(QSqlRecord &)));
tableView = new QTableView;
tableView->setModel(model);
tableView->setColumnHidden(Artist_Id, true);
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->resizeColumnsToContents();
for (int row = 0; row < model->rowCount(); ++row) {
QSqlRecord record = model->record(row);
if (record.value(Artist_Name).toString() == name) {
tableView->selectRow(row);
break;
}
}
320
Nous prfrons les nommer nous-mmes pour garantir une casse et une internationalisation
correctes.
Nous crons ensuite un QTableView pour visualiser le modle. Nous masquons le champ id et
dfinissons les largeurs des colonnes en fonction de leur texte afin de ne pas avoir afficher de
points de suspension.
Le constructeur de ArtistForm reoit le nom de lartiste qui doit tre slectionn louverture de la bote de dialogue. Nous parcourons les enregistrements de la table artist et slectionnons lartiste voulu. Le reste du code du constructeur permet de crer et de connecter les
boutons ainsi que de positionner les widgets enfants.
void ArtistForm::addArtist()
{
int row = model->rowCount();
model->insertRow(row);
QModelIndex index = model->index(row, Artist_Name);
tableView->setCurrentIndex(index);
tableView->edit(index);
}
Pour ajouter un nouvel artiste, nous insrons une ligne vierge dans le bas de QTableView.
Lutilisateur peut maintenant entrer le nom et le pays du nouvel artiste. Sil confirme linsertion en appuyant sur Entre, le signal beforeInsert() est mis puis le nouvel enregistrement
est insr dans la base de donnes.
void ArtistForm::beforeInsertArtist(QSqlRecord &record)
{
record.setValue("id", generateId("artist"));
}
La fonction generateId() nest assure de fonctionner correctement que si elle est excute dans le contexte de la mme transaction que linstruction INSERT correspondante.
Chapitre 13
321
Certaines bases de donnes supportent les champs gnrs automatiquement, et il est gnralement nettement prfrable dutiliser la prise en charge spcifique chaque base de donnes
pour cette opration.
La dernire possibilit offerte par la bote de dialogue ArtistForm est la suppression. Au lieu
deffectuer des suppressions en cascade (que nous avons abordes brivement), nous avons
choisi de nautoriser que les suppressions dartistes ne possdant pas de CD dans la collection.
void ArtistForm::deleteArtist()
{
tableView->setFocus();
QModelIndex index = tableView->currentIndex();
if (!index.isValid())
return;
QSqlRecord record = model->record(index.row());
QSqlTableModel cdModel;
cdModel.setTable("cd");
cdModel.setFilter("artistid = " + record.value("id").toString());
cdModel.select();
if (cdModel.rowCount() == 0) {
model->removeRow(tableView->currentIndex().row());
} else {
QMessageBox::information(this,
tr("Delete Artist"),
tr("Cannot delete %1 because there are CDs associated "
"with this artist in the collection.")
.arg(record.value("name").toString()));
}
}
Si un enregistrement est slectionn, nous dterminons si lartiste possde un CD. Si tel nest
pas le cas, nous le supprimons immdiatement. Sinon, nous affichons une bote de message
expliquant pourquoi la suppression na pas eu lieu. Strictement parlant, nous aurions d utiliser
une transaction, car tel que le code se prsente, il est possible que lartiste que nous supprimons soit associ un CD entre les appels de cdModel.select() et model->removeRow().
Nous prsenterons une transaction dans la prochaine section.
322
public:
MainForm();
private
void
void
void
void
void
void
void
void
void
slots:
addCd();
deleteCd();
addTrack();
deleteTrack();
editArtists();
currentCdChanged(const QModelIndex &index);
beforeInsertCd(QSqlRecord &record);
beforeInsertTrack(QSqlRecord &record);
refreshTrackViewHeader();
private:
enum {
Cd_Id = 0,
Cd_Title = 1,
Cd_ArtistId = 2,
Cd_Year = 3
};
enum {
Track_Id = 0,
Track_Title = 1,
Track_Duration = 2,
Track_CdId = 3
};
QSqlRelationalTableModel *cdModel;
QSqlTableModel *trackModel;
QTableView *cdTableView;
QTableView *trackTableView;
QPushButton *addCdButton;
QPushButton *deleteCdButton;
QPushButton *addTrackButton;
QPushButton *deleteTrackButton;
QPushButton *editArtistsButton;
QPushButton *quitButton;
};
Chapitre 13
323
La dfinition de la vue pour la table cd est similaire ce que nous avons dj vu. La seule
diffrence significative est la suivante : au lieu dutiliser le dlgu par dfaut de la vue, nous
utilisons QSqlRelationalDelegate. Cest ce dlgu qui gre les cls trangres.
trackModel = new QSqlTableModel(this);
trackModel->setTable("track");
trackModel->setHeaderData(Track_Title, Qt::Horizontal, tr("Title"));
trackModel->setHeaderData(Track_Duration, Qt::Horizontal,
tr("Duration"));
trackTableView = new QTableView;
trackTableView->setModel(trackModel);
trackTableView->setItemDelegate(
new TrackDelegate(Track_Duration, this));
trackTableView->setSelectionMode(
QAbstractItemView::SingleSelection);
trackTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
Pour ce qui est des pistes, nous nallons montrer que leurs noms et leurs dures. Cest pourquoi
QSqlTableModel est suffisant. Le seul aspect remarquable de cette partie du code est que nous
utilisons le TrackDelegate dvelopp dans le Chapitre 10 pour afficher les dures des pistes
sous la forme "minutes:secondes" et permettre leur dition en utilisant un QTimeEdit adapt.
La cration, la connexion et la disposition des vues ainsi que des boutons ne prsente pas de
surprise. Cest pourquoi la seule autre partie du constructeur que nous allons prsenter contient
quelques connexions non videntes.
connect(cdTableView->selectionModel(),
324
La premire connexion est inhabituelle, car au lieu de connecter un widget, nous tablissons
une connexion avec un modle de slection. La classe QItemSelectionModel est utilise
pour assurer le suivi des slections dans les vues. En tant connect au modle de slection de
la vue table, notre slot currentCdChanged() sera appel ds que lutilisateur passe dun
enregistrement lautre.
void MainForm::currentCdChanged(const QModelIndex &index)
{
if (index.isValid()) {
QSqlRecord record = cdModel->record(index.row());
int id = record.value("id").toInt();
trackModel->setFilter(QString("cdid = %1").arg(id));
} else {
trackModel->setFilter("cdid = -1");
}
trackModel->select();
refreshTrackViewHeader();
}
Ce slot est appel ds que le CD en cours change, ce qui se produit lorsque lutilisateur passe
un autre CD (en cliquant ou en utilisant les touches flches). Si le CD est invalide (sil
nexiste pas de CD, si un nouveau CD est en cours dinsertion ou encore si celui en cours vient
dtre supprim), nous dfinissons le cdid de la table track en 1 (un ID invalide qui ne
correspondra aucun enregistrement).
Puis, en ayant dfini le filtre, nous slectionnons les enregistrements de piste correspondants.
La fonction refreshTrackViewHeader() sera tudie dans un moment.
void MainForm::addCd()
{
int row = 0;
if (cdTableView->currentIndex().isValid())
row = cdTableView->currentIndex().row();
cdModel->insertRow(row);
cdModel->setData(cdModel->index(row, Cd_Year),
QDate::currentDate().year());
Chapitre 13
325
Lorsque lutilisateur clique sur le bouton Add CD, une nouvelle ligne vierge est insre dans le
cdTableView et nous entrons en mode dition. Nous dfinissons galement une valeur par
dfaut pour le champ year. A ce stade, lutilisateur peut modifier lenregistrement en remplissant les champs vierges et en slectionnant un artiste dans la zone de liste droulante qui est
automatiquement fournie par le QSqlRelationalTableModel grce lappel de setRelation(). Il peut aussi modifier lanne si celle propose par dfaut savre inapproprie.
Si lutilisateur confirme linsertion en appuyant sur Entre, lenregistrement est insr. Lutilisateur peut annuler en appuyant sur Echap.
void MainForm::beforeInsertCd(QSqlRecord &record)
{
record.setValue("id", generateId("cd"));
}
Ce slot est appel lorsque le cdModel met son signal beforeInsert(). Nous lutilisons pour
remplir le champ id de la mme faon que nous lavons fait pour insrer de nouveaux artistes.
Les mmes rgles sappliquent : cette opration doit seffectuer dans la porte dune transaction et avec les mthodes de cration dID spcifiques la base de donnes (par exemple, les
ID gnrs automatiquement).
void MainForm::deleteCd()
{
QModelIndex index = cdTableView->currentIndex();
if (!index.isValid())
return;
QSqlDatabase db = QSqlDatabase::database();
db.transaction();
QSqlRecord record = cdModel->record(index.row());
int id = record.value(Cd_Id).toInt();
int tracks = 0;
QSqlQuery query;
query.exec(QString("SELECT COUNT(*) FROM track WHERE cdid = %1")
.arg(id));
if (query.next())
tracks = query.value(0).toInt();
if (tracks > 0) {
int r = QMessageBox::question(this, tr("Delete CD"),
tr("Delete \"%1\" and all its tracks?")
.arg(record.value(Cd_ArtistId).toString()),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape);
if (r == QMessageBox::No) {
db.rollback();
return;
}
326
Lorsque lutilisateur clique sur le bouton Delete CD, ce slot est appel. Quand un CD est en
cours, nous dterminons son nombre de pistes. Si nous ne trouvons pas de piste, nous supprimons directement lenregistrement du CD. Sil existe au moins une piste, nous demandons
lutilisateur de confirmer la suppression. Sil clique sur Yes, nous supprimons tous les enregistrements de piste, puis lenregistrement du CD. Toutes ces oprations sont effectues dans la
porte dune transaction. Ainsi, soit la suppression en cascade choue en bloc, soit elle russit
dans son ensemble en supposant que la base de donnes en question supporte les transactions.
La gestion des donnes de piste est trs similaire celle des donnes de CD. Les mises jour
peuvent tre effectues simplement via les cellules ddition fournies lutilisateur. Dans le
cas des dures de piste, notre TrackDelegate sassure quelles sont prsentes dans le bon
format et quelles sont facilement modifiables au moyen de QTimeEdit.
void MainForm::addTrack()
{
if (!cdTableView->currentIndex().isValid())
return;
int row = 0;
if (trackTableView->currentIndex().isValid())
row = trackTableView->currentIndex().row();
trackModel->insertRow(row);
QModelIndex index = trackModel->index(row, Track_Title);
trackTableView->setCurrentIndex(index);
trackTableView->edit(index);
}
Le fonctionnement ici est le mme que celui de addCd(), avec une nouvelle ligne vierge insre
dans la vue.
void MainForm::beforeInsertTrack(QSqlRecord &record)
{
QSqlRecord cdRecord = cdModel->record(cdTableView->currentIndex()
.row());
record.setValue("id", generateId("track"));
record.setValue("cdid", cdRecord.value(Cd_Id).toInt());
}
Chapitre 13
327
Si lutilisateur confirme linsertion initie par addTrack(), cette fonction est appele pour
remplir les champs id et cdid. Les rgles mentionnes prcdemment sappliquent bien sr
toujours ici.
void MainForm::deleteTrack()
{
trackModel->removeRow(trackTableView->currentIndex().row());
if (trackModel->rowCount() == 0)
trackTableView->horizontalHeader()->setVisible(false);
}
Si lutilisateur clique sur le bouton Delete Track, nous supprimons la piste sans formalit.
Il serait facile dafficher une bote de message de type Yes/No si nous envisagions de faire
confirmer les suppressions.
void MainForm::refreshTrackViewHeader()
{
trackTableView->horizontalHeader()->setVisible(
trackModel->rowCount() > 0);
trackTableView->setColumnHidden(Track_Id, true);
trackTableView->setColumnHidden(Track_CdId, true);
trackTableView->resizeColumnsToContents();
}
Ce slot est appel si lutilisateur clique sur le bouton Edit Artists. Il affiche les donnes
concernant lartiste du CD en cours, invoquant le ArtistForm trait dans la section prcdente
et slectionnant lartiste appropri. Sil nexiste pas denregistrement en cours, un enregistrement vide est retourn par record(). Il ne correspond naturellement aucun artiste dans le
formulaire. Voici ce qui se produit vritablement : comme nous utilisons un QSqlRelationalTableModel qui tablit une correspondance entre les ID des artistes et leurs noms, la valeur
qui est retourne lorsque nous appelons record.value(Cd_ArtistId) est le nom de lartiste
(qui sera une chane vide si lenregistrement est vide). Nous forons enfin le cdModel slectionner de nouveau ses donnes, ce qui conduit le cdTableView rafrachir ses cellules visibles.
328
Cette opration permet de sassurer que les noms des artistes sont affichs correctement,
certains dentre eux ayant pu tre modifis par lutilisateur dans la bote de dialogue ArtistForm.
Pour les projets qui utilisent les classes SQL, nous devons ajouter la ligne
QT
+= sql
14
Gestion de rseau
Au sommaire de ce chapitre
Programmer les clients FTP
Programmer les clients HTTP
Programmer les applications
client/serveur TCP
Envoyer et recevoir des datagrammes UDP
Qt fournit les classes QFtp et QHttp pour la programmation de FTP et HTTP. Ces
protocoles sont faciles utiliser pour tlcharger des fichiers et, dans le cas de HTTP,
pour envoyer des requtes aux serveurs Web et rcuprer les rsultats.
Qt fournit galement les classes de bas niveau QTcpSocket et QUdpSocket, qui implmentent les protocoles de transport TCP et UDP. TCP est un protocole orient
connexion fiable qui agit en termes de flux de donnes transmis entre les nuds rseau,
alors que UDP est un protocole fonctionnant en mode non connect non fiable qui
permet denvoyer des paquets discrets entre des nuds rseau. Tous deux peuvent tre
utiliss pour crer des applications rseau clientes et serveur. En ce qui concerne les
serveurs, nous avons aussi besoin de la classe QTcpServer pour grer les connexions
TCP entrantes.
330
Nous crons un QCoreApplication plutt que sa sous-classe QApplication pour viter une
liaison dans la bibliothque QtGui. La fonction QCoreApplication::arguments() retourne
les arguments de ligne de commande sous forme de QStringList, le premier lment tant le
nom sous lequel le programme a t invoqu, et tous les arguments propres Qt (tels que -style)
tant supprims. Le cur de la fonction main() est la construction de lobjet FtpGet et
lappel de getFile(). Si lappel russit, nous laissons la boucle dvnement sexcuter
jusqu la fin du tlchargement.
Tout le travail est effectu par la sous-classe FtpGet, qui est dfinie comme suit :
class FtpGet: public QObject
{
Q_OBJECT
Chapitre 14
Gestion de rseau
331
public:
FtpGet(QObject *parent = 0);
bool getFile(const QUrl &url);
signals:
void done();
private slots:
void ftpDone(bool error);
private:
QFtp ftp;
QFile file;
};
La classe possde une fonction publique, getFile(), qui rcupre le fichier spcifi par une
URL. La classe QUrl fournit une interface de haut niveau destine extraire les diffrents
segments dune URL, tels que le nom du fichier, le chemin daccs, le protocole et le port.
FtpGet possde un slot priv, ftpDone(), qui est appel lorsque le transfert de fichier est
termin, et un signal done() qui est mis une fois le fichier tlcharg. La classe contient
galement deux variables prives : la variable ftp, de type QFtp, qui encapsule la connexion
avec un serveur FTP et la variable file qui est utilise pour crire le fichier tlcharg sur le
disque.
FtpGet::FtpGet(QObject *parent)
: QObject(parent)
{
connect(&ftp, SIGNAL(done(bool)), this, SLOT(ftpDone(bool)));
}
332
Chapitre 14
Gestion de rseau
333
Les commandes FTP ayant toutes t excutes, nous fermons le fichier et mettons notre
propre signal done(). Il peut sembler trange de fermer le fichier ici, et non aprs lappel de
ftp.close() la fin de la fonction getFile(), mais souvenez-vous que les commandes FTP
sont excutes de faon asynchrone et peuvent trs bien tre en cours la fin de lexcution de
getFile(). Seule lmission du signal done() de lobjet QFtp nous permet de savoir que le
tlchargement est termin et que le fichier peut tre ferm en toute scurit.
QFtp fournit plusieurs commandes FTP, dont connectToHost(), login(), close(),
list(), cd(), get(), put(), remove(), mkdir(), rmdir() et rename(). Toutes ces fonctions programment une commande FTP et retournent un numro dID qui identifie cette
commande. Il est galement possible de contrler le mode et le type de transfert (loption par
dfaut est un mode passif et un type binaire).
Les commandes FTP arbitraires peuvent tre excutes au moyen de la commande rawCommand(). Voici, par exemple, comment excuter une commande SITE CHMOD:
ftp.rawCommand("SITE CHMOD 755 fortune");
Un autre moyen de fournir un rapport consiste tablir une connexion au signal stateChanged()
de QFtp, qui est mis lorsque la connexion entre dans un nouvel tat (QFtp::Connecting,
QFtp::Connected, QFtp::LoggedIn, etc.)
334
Dans la plupart des applications, nous nous intressons plus au sort de la squence de
commandes dans son ensemble quaux commandes particulires. Dans ce cas, nous pouvons
simplement nous connecter au signal done(bool), qui est mis ds que la file dattente de
commandes est vide.
Quand une erreur se produit, QFtp vide automatiquement la file dattente de commandes.
Ainsi, si la connexion ou louverture de session choue, les commandes qui suivent dans la file
dattente ne sont jamais excutes. Si nous programmons de nouvelles commandes au moyen
de lobjet QFtp aprs que lerreur se soit produite, elles sont places en file dattente et excutes.
Dans le fichier .pro de lapplication, la ligne suivante est ncessaire pour tablir une liaison
avec la bibliothque QtNetwork :
QT
+= network
Le rpertoire de dpart est spcifi en tant que QUrl et est dfini au moyen de la fonction
getDirectory().
Spider::Spider(QObject *parent)
: QObject(parent)
{
Chapitre 14
Gestion de rseau
335
Lorsque la fonction getDirectory() est appele, elle commence par effectuer quelques vrifications de base, et, si tout va bien, tente dtablir une connexion FTP. Elle appelle processNextDirectory() pour lancer le tlchargement du rpertoire racine.
void Spider::processNextDirectory()
{
if (!pendingDirs.isEmpty()) {
currentDir = pendingDirs.takeFirst();
currentLocalDir = "downloads/" + currentDir;
QDir(".").mkpath(currentLocalDir);
ftp.cd(currentDir);
ftp.list();
} else {
emit done();
}
}
336
Chapitre 14
Gestion de rseau
337
qDeleteAll(openedFiles);
openedFiles.clear();
processNextDirectory();
}
Le slot ftpDone() est appel lorsque toutes les commandes FTP sont termines ou si une
erreur se produit. Nous supprimons les objets QFile pour viter les fuites de mmoire et galement pour fermer chaque fichier. Nous appelons enfin processNextDirectory(). Sil reste
des rpertoires, tout le processus recommence pour le rpertoire suivant dans la liste. Dans le
cas contraire, le tlchargement est interrompu une fois done() mis.
Si aucune erreur nintervient, la squence de signaux et de commandes FTP est la suivante :
connectToHost(host, port)
login()
cd(directory_1)
list()
emit listInfo(file_1_1)
get(file_1_1)
emit listInfo(file_1_2)
get(file_1_2)
...
emit done()
...
cd(directory_N)
list()
emit listInfo(file_N_1)
get(file_N_1)
emit listInfo(file_N_2)
get(file_N_2)
...
emit done()
Si un fichier est un rpertoire, il est ajout la liste pendingDirs. Une fois le dernier fichier
de la commande list() tlcharg, une nouvelle commande cd() est mise, suivie dune
commande list() avec le rpertoire suivant en attente. Tout le processus recommence alors
avec ce rpertoire. Ces oprations sont rptes jusqu ce que chaque fichier ait t tlcharg.
A ce moment l, la liste pendingDirs est vide.
Si une erreur rseau se produit lors du tlchargement du cinquime ou, disons, du vingtime
fichier dun rpertoire, les fichiers restants ne sont pas tlchargs. Pour tlcharger autant de
fichiers que possible, une solution consisterait planifier les oprations GET une par une et
attendre le signal done(bool) avant la planification de lopration suivante. Dans
listInfo(), nous accolerions simplement le nom de fichier un QStringList au lieu
338
dappeler get() directement, et dans done(bool) nous appellerions get() sur le fichier
suivant tlcharger dans le QStringList. La squence dexcution serait alors celle-ci :
connectToHost(host, port)
login()
cd(directory_1)
list()
...
cd(directory_N)
list()
emit listInfo(file_1_1)
emit listInfo(file_1_2)
...
emit listInfo(file_N_1)
emit listInfo(file_N_2)
...
emit done()
get(file_1_1)
emit done()
get(file_1_2)
emit done()
...
get(file_N_1)
emit done()
get(file_N_2)
emit done()
...
Une autre solution consisterait utiliser un objet QFtp pour chaque fichier, ce qui nous permettrait
de tlcharger les fichiers en parallle, par le biais de connexions FTP spares.
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
if (args.count()!= 2) {
cerr << "Usage: spider url" << endl
<< "Example:" << endl
<< " spider ftp://ftp.trolltech.com/freebies/leafnode"
<< endl;
return 1;
}
Chapitre 14
Gestion de rseau
339
Spider spider;
if (!spider.getDirectory(QUrl(args[1])))
return 1;
QObject::connect(&spider, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}
La fonction main() achve le programme. Si lutilisateur ne spcifie pas dURL sur la ligne de
commande, nous gnrons un message derreur et terminons le programme.
Dans les deux exemples FTP, les donnes rcupres au moyen de get() ont t crites dans
un QFile. Ceci nest pas obligatoire. Si nous souhaitions enregistrer les donnes en mmoire,
nous pourrions utiliser un QBuffer, la sous-classe QIODevice qui intgre un QByteArray.
Par exemple :
QBuffer *buffer = new QBuffer;
buffer->open(QIODevice::WriteOnly);
ftp.get(urlInfo.name(), buffer);
340
Dans le constructeur, nous connectons le signal done(bool) de lobjet QHttp au slot priv
httpDone(bool).
bool HttpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
cerr << "Error: Invalid URL" << endl;
return false;
}
if (url.scheme()!= "http") {
cerr << "Error: URL must start with http:" << endl;
return false;
}
if (url.path().isEmpty()) {
cerr << "Error: URL has no path" << endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "httpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
cerr << "Error: Cannot open " << qPrintable(file.fileName())
<< " for writing: " << qPrintable(file.errorString())
<< endl;
return false;
}
http.setHost(url.host(), url.port(80));
http.get(url.path(), &file);
http.close();
return true;
}
La fonction getFile() effectue le mme type de contrle derreur que la fonction FtpGet::
getFile() prsente prcdemment et utilise la mme approche pour attribuer au fichier un
nom local. Lors dune rcupration depuis un site Web, aucun nom de connexion nest ncessaire. Nous dfinissons simplement lhte et le port (en utilisant le port HTTP 80 par dfaut sil
nest pas spcifi dans lURL) et tlchargeons les donnes dans le fichier, puisque le
deuxime argument de QHttp::get() spcifie le priphrique dE/S.
Les requtes HTTP sont places en file dattente et excutes de faon asynchrone dans la
boucle dvnement de Qt. Lachvement des requtes est indiqu par le signal done(bool)
de QHttp, que nous avons connect httpDone(bool) dans le constructeur.
void HttpGet::httpDone(bool error)
{
Chapitre 14
Gestion de rseau
341
if (error) {
cerr << "Error: " << qPrintable(http.errorString()) << endl;
} else {
cerr << "File downloaded as " << qPrintable(file.fileName())
<< endl;
}
file.close();
emit done();
}
Une fois les requtes HTTP termines, nous fermons le fichier, en avertissant lutilisateur si
une erreur sest produite.
La fonction main() est trs similaire celle utilise par ftpget:
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = app.arguments();
if (args.count()!= 2) {
cerr << "Usage: httpget url" << endl
<< "Example:" << endl
<< " httpget http://doc.trolltech.com/qq/index.html"
<< endl;
return 1;
}
HttpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}
Nous pouvons transmettre les donnes soit sous la forme dune chane de 8 octets, soit en
transmettant un QIODevice ouvert, tel quun QFile. Pour plus de contrle, il est possible de
recourir la fonction request(), qui accepte des donnes et un en-tte HTTP arbitraire.
342
Par exemple :
QHttpRequestHeader header("POST", "/search.html");
header.setValue("Host", "www.trolltech.com");
header.setContentType("application/x-www-form-urlencoded");
http.setHost("www.trolltech.com");
http.request(header, "qt-interest=on&search=opengl");
QHttp met le signal requestStarted(int) quand il commence excuter une requte, puis
le signal requestFinished(int, bool) une fois la commande termine. Le paramtre int
est le numro dID qui identifie une requte. Si nous nous intressons au sort des requtes individuelles, nous pouvons stocker les numros dID lors de la programmation de ces dernires.
Le suivi de ces identifiants nous permet de fournir un rapport dtaill lutilisateur.
Dans la plupart des applications, nous souhaitons simplement savoir si la squence de requtes
dans son ensemble sest termine avec succs ou non. Dans ce cas, nous tablissons une
connexion au signal done() , qui est mis lorsque la file dattente de la requte est vide.
Quand une erreur se produit, la file dattente de la requte est vide automatiquement. Si nous
programmons de nouvelles requtes au moyen de lobjet QHttp aprs que lerreur sest
produite, elles sont places en file dattente et envoyes.
Comme QFtp, QHttp fournit un signal readyRead() ainsi que les fonctions read() et
readAll(), dont lemploi vite la spcification dun priphrique dE/S.
Les protocoles orients ligne transfrent les donnes sous la forme de lignes de texte,
chacune tant termine par un retour la ligne.
Les protocoles orients bloc transfrent les donne sous la forme de blocs de donnes
binaires. Chaque bloc comprend un champ de taille suivi de la quantit de donnes spcifie.
Chapitre 14
Gestion de rseau
343
Dans cette section, nous allons examiner le code dun client et dun serveur qui utilisent un
protocole personnalis orient bloc. Le client se nomme Trip Planner et permet aux utilisateurs
de planifier leur prochain voyage ferroviaire. Le serveur se nomme Trip Server et fournit les
informations concernant le voyage au client. Nous allons commencer par crire le client Trip
Planner.
Trip Planner fournit les champs From, To, Date et Approximate Time ainsi que deux boutons
doption indiquant si lheure approximative est celle de dpart ou darrive. Lorsque lutilisateur clique sur Search, lapplication expdie une requte au serveur, qui renvoie une liste des
trajets correspondant aux critres de lutilisateur. La liste est prsente sous la forme dun
QTableWidget dans la fentre Trip Planner. Le bas de la fentre est occup par un QProgressBar ainsi que par un QLabel qui affiche le statut de la dernire opration. (Voir Figure 14.1)
Figure 14.1
Lapplication
Trip Planner
slots:
connectToServer();
sendRequest();
updateTableWidget();
stopSearch();
connectionClosedByServer();
error();
344
private:
void closeConnection();
QTcpSocket tcpSocket;
quint16 nextBlockSize;
};
La classe TripPlanner hrite de Ui::TripPlanner (qui est gnr par le uic de tripplanner.ui) en plus de QDialog. La variable membre tcpSocket encapsule la connexion TCP.
La variable nextBlockSize est utilise lors de lanalyse des blocs reus du serveur.
TripPlanner::TripPlanner(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
QDateTime dateTime = QDateTime::currentDateTime();
dateEdit->setDate(dateTime.date());
timeEdit->setTime(QTime(dateTime.time().hour(), 0));
progressBar->hide();
progressBar->setSizePolicy(QSizePolicy::Preferred,
QSizePolicy::Ignored);
tableWidget->verticalHeader()->hide();
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
connect(searchButton, SIGNAL(clicked()),
this, SLOT(connectToServer()));
connect(stopButton, SIGNAL(clicked()), this, SLOT(stopSearch()));
connect(&tcpSocket, SIGNAL(connected()), this, SLOT(sendRequest()));
connect(&tcpSocket, SIGNAL(disconnected()),
this, SLOT(connectionClosedByServer()));
connect(&tcpSocket, SIGNAL(readyRead()),
this, SLOT(updateTableWidget()));
connect(&tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(error()));
}
Dans le constructeur, nous initialisons les diteurs de date et dheure en fonction de la date et
de lheure courantes. Nous masquons galement la barre de progression, car nous souhaitons
ne lafficher que lorsquune connexion est active. Dans Qt Designer, les proprits minimum et
maximum de la barre de progression sont toutes deux dfinies en 0, ce qui indique au QProgressBar de se comporter comme un indicateur dactivit au lieu dune barre de progression
standard base sur les pourcentages.
En outre, dans le constructeur, nous connectons les signaux connected(), disconnected(),
readyRead() et error(QAbstractSocket::SocketError) de QTcpSocket des slots
privs.
Chapitre 14
Gestion de rseau
345
void TripPlanner::connectToServer()
{
tcpSocket.connectToHost("tripserver.zugbahn.de", 6178);
tableWidget->setRowCount(0);
searchButton->setEnabled(false);
stopButton->setEnabled(true);
statusLabel->setText(tr("Connecting to server..."));
progressBar->show();
nextBlockSize = 0;
}
Le slot connectToServer() est excut lorsque lutilisateur clique sur Search pour lancer
une recherche. Nous appelons connectToHost() sur lobjet QTcpSocket pour tablir une
connexion avec le serveur, que nous supposons tre accessible par le biais du port 6178 sur
lhte fictif tripserver.zugbahn.de. (Si vous souhaitez tester lexemple sur votre propre
machine, remplacez le nom de lhte par QHostAddress::LocalHost.) Lappel de connectToHost() est asynchrone. Il rend toujours le contrle immdiatement. La connexion est gnralement tablie ultrieurement. Lobjet QTcpSocket met le signal connected() lorsque la
connexion fonctionne ou error(QAbstractSocket::SocketError) en cas dchec.
Nous mettons ensuite jour linterface utilisateur, en particulier en affichant la barre de
progression.
Nous dfinissons enfin la variable nextBlockSize en 0. Cette variable stocke la longueur du
prochain bloc en provenance du serveur. Nous avons choisi dutiliser la valeur 0 pour indiquer
que nous ne connaissons pas encore la taille du bloc venir.
void TripPlanner::sendRequest()
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
out << quint16(0) << quint8(S) << fromComboBox->currentText()
<< toComboBox->currentText() << dateEdit->date()
<< timeEdit->time();
if (departureRadioButton->isChecked()) {
out << quint8(D);
} else {
out << quint8(A);
}
out.device()->seek(0);
out << quint16(block.size() - sizeof(quint16));
tcpSocket.write(block);
statusLabel->setText(tr("Sending request..."));
}
346
Le slot sendRequest() est excut lorsque lobjet QTcpSocket met le signal connected(),
indiquant quune connexion a t tablie. La tche du slot consiste gnrer une requte
destination du serveur, avec toutes les informations entres par lutilisateur.
La requte est un bloc binaire au format suivant :
quint16
quint8
QString
Ville de dpart
QString
Ville darrive
QDate
Date du voyage
QTime
quint8
Dans un premier temps, nous crivons les donnes dans un QByteArray nomm block. Nous
ne pouvons pas crire les donnes directement dans le QTcpSocket car nous ne connaissons
pas la taille du bloc avant dy avoir plac toutes les donnes.
Nous avons initialement indiqu 0 pour la taille du bloc, suivi du reste des donnes. Nous
appelons ensuite seek(0) sur le priphrique dE/S (un QBuffer cr par QDataStream
larrire-plan) pour revenir au dbut du tableau doctets, et nous remplaons le 0 initial par la
taille des donnes du bloc. Elle est calcule en prenant la taille du bloc et en soustrayant
sizeof(quint16) (cest--dire 2) pour exclure le champ de taille du compte doctets. Nous
appelons alors write() sur le QTcpSocket pour envoyer le bloc au serveur.
void TripPlanner::updateTableWidget()
{
QDataStream in(&tcpSocket);
in.setVersion(QDataStream::Qt_4_1);
forever {
int row = tableWidget->rowCount();
if (nextBlockSize == 0) {
if (tcpSocket.bytesAvailable() < sizeof(quint16))
break;
in >> nextBlockSize;
}
if (nextBlockSize == 0xFFFF) {
closeConnection();
statusLabel->setText(tr("Found %1 trip(s)").arg(row));
break;
}
Chapitre 14
Gestion de rseau
347
51 octets
51
data
48 octets
48
data
53 octets
53
data
0xFFFF
348
Si la taille du bloc nest pas de 0xFFFF, nous essayons de lire le bloc suivant. Dans un premier
temps, nous essayons de dterminer si des octets de taille de bloc sont disponibles la lecture.
Si tel nest pas le cas, nous interrompons cette action un instant. Le signal readyRead() sera
de nouveau mis lorsque des donnes supplmentaires seront disponibles. Nous procderons
alors de nouvelles tentatives.
Lorsque nous sommes certains que le bloc entier est arriv, nous pouvons utiliser loprateur
>> en toute scurit sur le QDataStream pour extraire les informations relatives au voyage, et
nous crons un QTableWidgetItems avec ces informations. Le format dun bloc reu du
serveur est le suivant :
quint16
QDate
Date de dpart
QTime
Heure de dpart
quint16
quint8
Nombre de changements
QString
Type de train
A la fin, nous rinitialisons la variable nextBlockSize en 0 pour indiquer que la taille du bloc
suivant est inconnue et doit tre lue.
void TripPlanner::closeConnection()
{
tcpSocket.close();
searchButton->setEnabled(true);
stopButton->setEnabled(false);
progressBar->hide();
}
La fonction prive closeConnection() ferme la connexion avec le serveur TCP et met jour
linterface utilisateur. Elle est appele depuis updateTableWidget() lorsque 0xFFFF est lu
ainsi que depuis plusieurs autres slots sur lesquels nous reviendrons dans un instant.
void TripPlanner::stopSearch()
{
statusLabel->setText(tr("Search stopped"));
closeConnection();
}
Le slot stopSearch() est connect au signal clicked() du bouton Stop. Il appelle simplement
closeConnection().
void TripPlanner::connectionClosedByServer()
{
Chapitre 14
Gestion de rseau
349
if (nextBlockSize!= 0xFFFF)
statusLabel->setText(tr("Error: Connection closed by server"));
closeConnection();
}
Le slot connectionClosedByServer() est connect au signal disconnected() du QTcpSocket. Si le serveur ferme la connexion sans que nous ayons encore reu le marqueur
0xFFFF de fin de donnes, nous indiquons lutilisateur quune erreur sest produite. Nous
appelons normalement closeConnection() pour mettre jour linterface utilisateur.
void TripPlanner::error()
{
statusLabel->setText(tcpSocket.errorString());
closeConnection();
}
350
TripServer::TripServer(QObject *parent)
: QTcpServer(parent)
{
}
Dans le constructeur, nous tablissons les connexions signal/slot ncessaires, et nous dfinissons la variable nextBlockSize en 0, indiquant ainsi que nous ne connaissons pas encore la
taille du bloc envoy par le client.
Le signal disconnected() est connect deleteLater(), une fonction hrite de QObject
qui supprime lobjet lorsque le contrle retourne la boucle dvnement de Qt. De cette
faon, lobjet ClientSocket est supprim lorsque la connexion du socket est ferme.
Chapitre 14
Gestion de rseau
351
void ClientSocket::readClient()
{
QDataStream in(this);
in.setVersion(QDataStream::Qt_4_1);
if (nextBlockSize == 0) {
if (bytesAvailable() < sizeof(quint16))
return;
in >> nextBlockSize;
}
if (bytesAvailable() < nextBlockSize)
return;
quint8 requestType;
QString from;
QString to;
QDate date;
QTime time;
quint8 flag;
in >> requestType;
if (requestType == S) {
in >> from >> to >> date >> time >> flag;
srand(from.length() * 3600 + to.length() * 60 + time.hour());
int numTrips = rand() % 8;
for (int i = 0; i < numTrips; ++i)
generateRandomTrip(from, to, date, time);
QDataStream out(this);
out << quint16(0xFFFF);
}
close();
}
Le slot readClient() est connect au signal readyRead() du QTcpSocket. Si nextBlockSize est dfini en 0, nous commenons par lire la taille du bloc. Dans le cas contraire, nous
lavons dj lue. Nous poursuivons donc en vrifiant si un bloc entier est arriv, et nous le
lisons dune seule traite. Nous utilisons QDataStream directement sur le QTcpSocket (lobjet
this) et lisons les champs au moyen de loprateur >>.
Une fois la requte du client lue, nous sommes prts gnrer une rponse. Dans le cas dune
application relle, nous rechercherions les informations dans une base de donnes dhoraires et
tenterions de trouver les trajets correspondants. Ici, nous nous contenterons dune fonction
nomme generateRandomTrip() qui gnrera un trajet alatoire. Nous appelons la fonction
un nombre quelconque de fois, puis nous envoyons 0xFFFF pour signaler la fin des donnes.
Enfin, nous fermons la connexion.
void ClientSocket::generateRandomTrip(const QString & /* from */,
const QString & /* to */, const QDate &date, const QTime &time)
352
{
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
quint16 duration = rand() % 200;
out << quint16(0) << date << time << duration << quint8(1)
<< QString("InterCity");
out.device()->seek(0);
out << quint16(block.size() - sizeof(quint16));
write(block);
}
Dans main(), nous crons un objet TripServer et un QPushButton qui permet lutilisateur
darrter le serveur. Nous lanons le serveur en appelant QTcpSocket::listen(), qui reoit
ladresse IP et le numro de port sur lequel nous souhaitons accepter les connexions. Ladresse
spciale 0.0.0.0 (QHostAddress::Any) signifie toute interface IP prsente sur lhte local.
Ceci termine notre exemple client/serveur. Ici, nous avons utilis un protocole orient bloc qui
nous permet de faire appel QDataStream pour la lecture et lcriture. Si nous souhaitions
utiliser un protocole orient ligne, lapproche la plus simple serait de recourir aux fonctions
canReadLine() et readLine() de QTcpSocket dans un slot connect au signal readyRead():
QStringList lines;
while (tcpSocket.canReadLine())
lines.append(tcpSocket.readLine());
Chapitre 14
Gestion de rseau
353
Nous traiterions alors chaque ligne lue. Comme pour lenvoi des donnes, ceci pourrait tre
effectu en utilisant un QTextStream sur le QTcpSocket.
Limplmentation serveur que nous avons utilise nest pas adapte une situation o les
connexions sont nombreuses. En effet, lorsque nous traitons une requte, nous ne grons pas
les autres connexions. Une approche plus souple consisterait dmarrer un nouveau thread pour
chaque connexion. Lexemple Threaded Fortune Server situ dans le rpertoire examples/
network/threadedfortuneserver illustre ce procd.
Les exemples Weather Balloon et Weather Station vous montreront comment utiliser UDP
partir dune application Qt. Lapplication Weather Balloon reproduit un ballon mto qui
envoie un datagramme UDP (au moyen dune connexion sans fil) contenant les conditions
atmosphriques courantes toutes les deux secondes. Lapplication Weather Station reoit
ces datagrammes et les affiche lcran. Nous allons commencer par le code du Weather
Ballon.
class WeatherBalloon: public QPushButton
{
Q_OBJECT
public:
WeatherBalloon(QWidget *parent = 0);
double temperature() const;
double humidity() const;
354
Dans le constructeur, nous lanons un QTimer pour invoquer sendDatagram() toutes les
deux secondes.
void WeatherBalloon::sendDatagram()
{
QByteArray datagram;
QDataStream out(&datagram, QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_1);
out << QDateTime::currentDateTime() << temperature() << humidity()
<< altitude();
udpSocket.writeDatagram(datagram, QHostAddress::LocalHost, 5824);
}
double
Temprature (en C)
double
Humidit (en %)
double
Chapitre 14
Gestion de rseau
355
La fonction main() cre simplement un objet WeatherBalloon, qui sert la fois dhomologue UDP et de QPushButton lcran. En cliquant sur le QPushButton, lutilisateur quitte
lapplication.
Revenons maintenant au code source du client Weather Station.
class WeatherStation: public QDialog
{
Q_OBJECT
public:
WeatherStation(QWidget *parent = 0);
private slots:
void processPendingDatagrams();
private:
QUdpSocket udpSocket;
QLabel *dateLabel;
QLabel *timeLabel;
QLineEdit *altitudeLineEdit;
};
La classe WeatherStation hrite de QDialog. Elle coute un port UDP particulier, analyse
tous les datagrammes entrants (en provenance du Weather Balloon) et affiche leur contenu
dans cinq QLineEdits en lecture seulement. La seule variable prive prsentant un intrt ici
356
est la variable udpSocket du type QUdpSocket, laquelle nous allons faire appel pour recevoir les
datagrammes.
WeatherStation::WeatherStation(QWidget *parent)
: QDialog(parent)
{
udpSocket.bind(5824);
connect(&udpSocket, SIGNAL(readyRead()),
this, SLOT(processPendingDatagrams()));
Dans le constructeur, nous commenons par tablir une liaison entre le QUdpSocket et le port
auquel le Weather Balloon transmet ses donnes. Comme nous navons pas spcifi dadresse
hte, le socket accepte les datagrammes envoys nimporte quelle adresse IP appartenant la
machine sur laquelle sexcute la Weather Station. Puis nous connectons le signal readyRead() du socket au processPendingDatagrams() priv qui extrait les donnes et les affiche.
void WeatherStation::processPendingDatagrams()
{
QByteArray datagram;
do {
datagram.resize(udpSocket.pendingDatagramSize());
udpSocket.readDatagram(datagram.data(), datagram.size());
} while (udpSocket.hasPendingDatagrams());
QDateTime dateTime;
double temperature;
double humidity;
double altitude;
QDataStream in(&datagram, QIODevice::ReadOnly);
in.setVersion(QDataStream::Qt_4_1);
in >> dateTime >> temperature >> humidity >> altitude;
dateLineEdit->setText(dateTime.date().toString());
timeLineEdit->setText(dateTime.time().toString());
temperatureLineEdit->setText(tr("%1 C").arg(temperature));
humidityLineEdit->setText(tr("%1%").arg(humidity));
altitudeLineEdit->setText(tr("%1 m").arg(altitude));
}
Chapitre 14
Gestion de rseau
357
15
XML
Au sommaire de ce chapitre
Lire du code XML avec SAX
Lire du code XML avec DOM
Ecrire du code XML
XML (Extensible Markup Language) est un format de fichier texte polyvalent, populaire pour lchange et le stockage des donnes. Qt fournit deux API distinctes faisant
partie du module QtXml pour la lecture de documents XML :
SAX (Simple API for XML) rapporte des "vnements danalyse" directement
lapplication par le biais de fonctions virtuelles.
DOM (Document Object Model) convertit une documentation XML en une structure
arborescente, que lapplication peut parcourir.
Trois facteurs principaux sont prendre en compte lors du choix entre DOM et SAX
pour une application particulire. SAX est de niveau infrieur et gnralement plus
rapide, ce qui le rend particulirement appropri pour des tches simples (telles que la
recherche de toutes les occurrences dune balise donne dans un document XML) ou
360
pour la lecture de fichiers de trs grande taille pour lesquels la mmoire sera insuffisante. Mais
pour de nombreuses applications, la commodit de DOM prime sur la vitesse potentielle et les
avantages offerts par SAX concernant la mmoire.
Pour crire des fichiers XML, deux options sont disponibles : nous pouvons gnrer le code
XML manuellement, ou reprsenter les donnes sous la forme dun arbre DOM en mmoire et
demander ce dernier de scrire par lui-mme dans un fichier.
Ces fonctions sont toutes dclares dans QXmlContentHandler. Pour des questions de simplicit, nous avons omis certains arguments de startElement() et endElement().
QXmlContentHandler est juste lune des nombreuses classes gestionnaires susceptible dtre
utilise avec QXmlSimpleReader. Les autres sont QXmlEntityResolver, QXmlDTDHandler,
QXmlErrorHandler, QXmlDeclHandler et QXmlLexicalHandler. Ces classes ne dclarent
que des fonctions purement virtuelles et fournissent des informations concernant les diffrents
types dvnements danalyse. Pour la plupart des applications, QXmlContentHandler et
QXmlErrorHandler sont les deux seules ncessaires.
Chapitre 15
XML
361
Pour des raisons de commodit, Qt fournit aussi QXmlDefaultHandler, une classe qui hrite
de toutes les classes gestionnaires et qui fournit des implmentations simples de toutes les
fonctions. Une telle conception, avec de nombreuses classes gestionnaires abstraites et une
sous-classe simple, est inhabituelle pour Qt. Elle a t adopte pour se conformer limplmentation de modle Java.
Nous allons tudier un exemple qui illustre comment utiliser QXmlSimpleReader et QXmlDefaultHandler pour analyser un fichier XML ad hoc et afficher son contenu dans un
QTreeWidget. La sous-classe QXmlDefaultHandler se nomme SaxHandler, et le format
gr par celle-ci est celui dun index de livre, avec les entres et les sous-entres.
Figure 15.1
Arbre dhritage
pour SaxHandler
QXmlContentHandler
QXmlDTDHandler
QXmlErrorHandler
QXmlLexicalHandler
QXmlEntityResolver
QXmlDeclHandler
QXmlDefaultHandler
SaxHandler
Voici le fichier dindex qui est affich dans le QTreeWidget en Figure 15.2 :
<?xml version="1.0"?>
<bookindex>
<entry term="sidebearings">
<page>10</page>
<page>34-35</page>
<page>307-308</page>
</entry>
<entry term="subtraction">
<entry term="of pictures">
<page>115</page>
<page>244</page>
</entry>
<entry term="of vectors">
<page>9</page>
</entry>
</entry>
</bookindex>
Figure 15.2
Un fichier dindex affich
dans un QTreeWidget
362
La classe SaxHandler hrite de QXmlDefaultHandler et rimplmente quatre fonctions : startElement(), endElement(), characters() et fatalError(). Les trois
premires sont dclares dans QXmlContentHandler. La dernire est dclare dans QXmlErrorHandler.
SaxHandler::SaxHandler(QTreeWidget *tree)
{
treeWidget = tree;
currentItem = 0;
}
Le constructeur SaxHandler accepte le QTreeWidget que nous souhaitons remplir avec les
informations stockes dans le fichier XML.
bool SaxHandler::startElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName,
const QXmlAttributes &attributes)
{
if (qName == "entry") {
if (currentItem) {
currentItem = new QTreeWidgetItem(currentItem);
} else {
currentItem = new QTreeWidgetItem(treeWidget);
}
Chapitre 15
XML
363
currentItem->setText(0, attributes.value("term"));
} else if (qName == "page") {
currentText.clear();
}
return true;
}
La fonction startElement() est appele ds que le lecteur rencontre une nouvelle balise
douverture. Le troisime paramtre est le nom de la balise (ou plus prcisment, son "nom
qualifi"). Le quatrime paramtre est la liste des attributs. Dans cet exemple, nous ignorons
les premier et deuxime paramtres. Ils sont utiles pour les fichiers XML qui utilisent le mcanisme despace de noms de XML, un sujet qui est trait en dtail dans la documentation de
rfrence.
Si la balise est <entry>, nous crons un nouvel lment QTreeWidget. Si elle est imbrique
dans une autre balise <entry>, la nouvelle balise dfinit une sous-entre dans lindex, et le
nouveau QTreeWidgetItem est cr en tant quenfant du QTreeWidgetItem qui reprsente
lentre principale. Dans le cas contraire, nous crons le QTreeWidgetItem avec llment
treeWidget en tant que parent, en faisant de celui-ci un lment de haut niveau. Nous appelons setText() pour dfinir le texte prsent en colonne 0 avec la valeur de lattribut term de
la balise <entry>.
Si la balise est <page>, nous dfinissons le currentText en une chane vide. Le currentText sert daccumulateur pour le texte situ entre les balises <page> et </page>.
Nous retournons enfin true pour demander SAX de poursuivre lanalyse du fichier. Si nous
souhaitions signaler les balises inconnues comme des erreurs, nous retournerions false dans
ces situations. Nous rimplmenterions galement errorString() partir de QXmlDefaultHandler pour retourner un message derreur appropri.
bool SaxHandler::characters(const QString &str)
{
currentText += str;
return true;
}
La fonction characters() est appele si des donnes caractres sont rencontres dans le
document XML. Nous accolons simplement les caractres la variable currentText.
bool SaxHandler::endElement(const QString & /* namespaceURI */,
const QString & /* localName */,
const QString &qName)
{
if (qName == "entry") {
currentItem = currentItem->parent();
} else if (qName == "page") {
if (currentItem) {
QString allPages = currentItem->text(1);
364
if (!allPages.isEmpty())
allPages += ", ";
allPages += currentText;
currentItem->setText(1, allPages);
}
}
return true;
}
La fonction endElement() est appele quand le lecteur rencontre une balise de fermeture.
Comme pour startElement(), le troisime paramtre est le nom de la balise.
Si la balise est </entry>, nous mettons jour la variable prive currentItem de faon la
diriger vers le parent de QTreeWidgetItem en cours. De cette faon, la variable currentItem
reprend la valeur qui tait la sienne avant la lecture de la balise <entry> correspondante.
Si la balise est </page>, nous ajoutons le numro de page ou la plage de pages sous la forme
dune liste spare par des virgules au texte de llment courant de la colonne 1.
bool SaxHandler::fatalError(const QXmlParseException &exception)
{
QMessageBox::warning(0, QObject::tr("SAX Handler"),
QObject::tr("Parse error at line %1, column "
"%2:\n%3.")
.arg(exception.lineNumber())
.arg(exception.columnNumber())
.arg(exception.message()));
return false;
}
La fonction fatalError() est appele lorsque le lecteur ne parvient pas analyser le fichier
XML. Dans cette situation, nous affichons simplement une bote de message, en donnant le
numro de ligne, le numro de colonne et le texte derreur de lanalyseur.
Ceci termine limplmentation de la classe SaxHandler. Voyons maintenant comment
lutiliser :
bool parseFile(const QString &fileName)
{
QStringList labels;
labels << QObject::tr("Terms") << QObject::tr("Pages");
QTreeWidget *treeWidget = new QTreeWidget;
treeWidget->setHeaderLabels(labels);
treeWidget->setWindowTitle(QObject::tr("SAX Handler"));
treeWidget->show();
Chapitre 15
XML
365
QFile file(fileName);
QXmlInputSource inputSource(&file);
QXmlSimpleReader reader;
SaxHandler handler(treeWidget);
reader.setContentHandler(&handler);
reader.setErrorHandler(&handler);
return reader.parse(inputSource);
}
Nous dfinissons un QTreeWidget avec deux colonnes. Puis nous crons un objet QFile pour
le fichier devant tre lu et un QXmlSimpleReader pour analyser le fichier. Il nest pas ncessaire douvrir le QFile par nous-mmes. QXmlInputSource sen charge automatiquement.
Nous crons enfin un objet SaxHandler, nous linstallons sur le lecteur la fois en tant que
gestionnaire de contenu et en tant que gestionnaire derreur, et nous appelons parse() sur le
lecteur pour effectuer lanalyse.
Au lieu de transmettre un simple objet de fichier la fonction parse(), nous transmettons un
QXmlInputSource. Cette classe ouvre le fichier qui lui est fourni, le lit (en prenant en considration tout codage de caractres spcifi dans la dclaration <?xml?>), et fournit une interface par le biais de laquelle lanalyseur lit le fichier.
Dans SaxHandler, nous rimplmentons uniquement les fonctions des classes QXmlContentHandler et QXmlErrorHandler. Si nous avions implment des fonctions dautres classes
gestionnaires, nous aurions galement d appeler leurs fonctions de rglage (set) sur le lecteur.
Pour lier lapplication la bibliothque QtXml, nous devons ajouter cette ligne dans le fichier
.pro:
QT
+= xml
366
Larbre DOM contient des nuds de types diffrents. Par exemple, un nud Element correspond une balise douverture et sa balise de fermeture. Le matriau situ entre les balises
apparat sous la forme de nuds enfants de Element.
Dans Qt, les types de nud (comme toutes les autres classes en liaison avec DOM) possdent
un prfixe QDom. Ainsi, QDomElement reprsente un nud Element, et QDomText reprsente
un nud Text.
Chaque nud peut possder diffrents types de nuds enfants. Par exemple, un nud
Element peut contenir dautres nuds Element, ainsi que des nuds EntityReference,
Text, CDATASection, ProcessingInstruction et Comment. La Figure 15.3 prsente les
types de nuds enfants correspondant aux nuds parents. Ceux apparaissant en gris ne
peuvent pas avoir de nud enfant.
Document
Element
Element
Document
Type
Attr
Processing
Instruction
Comment
Entity
Reference
Document
Fragment
Element
Entity
Reference
Entity
Entity
Reference
Text
CDATA
Section
Processing
Instruction
Text
Comment
Figure 15.3
Relations parent/enfant entre les nuds DOM
Nous allons voir comment utiliser DOM pour lire des fichiers XML en crant un analyseur
pour le format de fichier dindex dcrit dans la section prcdente.
Chapitre 15
XML
367
class DomParser
{
public:
DomParser(QIODevice *device, QTreeWidget *tree);
private:
void parseEntry(const QDomElement &element,
QTreeWidgetItem *parent);
QTreeWidget *treeWidget;
};
Nous dfinissons une classe nomme DomParser qui analysera un index de livre se prsentant
sous la forme dun document XML et affichera le rsultat dans un QTreeWidget. Cette classe
nhrite daucune autre classe.
DomParser::DomParser(QIODevice *device, QTreeWidget *tree)
{
treeWidget = tree;
QString errorStr;
int errorLine;
int errorColumn;
QDomDocument doc;
if (!doc.setContent(device, true, &errorStr, &errorLine,
&errorColumn)) {
QMessageBox::warning(0, QObject::tr("DOM Parser"),
QObject::tr("Parse error at line %1, "
"column %2:\n%3")
.arg(errorLine)
.arg(errorColumn)
.arg(errorStr));
return;
}
QDomElement root = doc.documentElement();
if (root.tagName()!= "bookindex")
return;
QDomNode node = root.firstChild();
while (!node.isNull()) {
if (node.toElement().tagName() == "entry")
parseEntry(node.toElement(), 0);
node = node.nextSibling();
}
}
368
unique, et nous vrifions sil sagit bien dun lment <bookindex>. Nous parcourons tous
les nuds enfants, et si le nud est un lment <entry>, nous appelons parseEntry() pour
lanalyser.
La classe QDomNode peut stocker tout type de nud. Si nous souhaitons traiter un nud de
faon plus prcise, nous devons tout dabord le convertir en un type de donne correct. Dans
cet exemple, nous ne nous proccupons que des nuds Element. Nous appelons donc toElement() sur le QDomNode pour le convertir en un QDomElement, puis nous appelons
tagName() afin de rcuprer le nom de balise de llment. Si le nud nest pas du type
Element, la fonction toElement() retourne un objet QDomElement nul, avec un nom de
balise vide.
void DomParser::parseEntry(const QDomElement &element,
QTreeWidgetItem *parent)
{
QTreeWidgetItem *item;
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, element.attribute("term"));
QDomNode node = element.firstChild();
while (!node.isNull()) {
if (node.toElement().tagName() == "entry") {
parseEntry(node.toElement(), item);
} else if (node.toElement().tagName() == "page") {
QDomNode childNode = node.firstChild();
while (!childNode.isNull()) {
if (childNode.nodeType() == QDomNode::TextNode) {
QString page = childNode.toText().data();
QString allPages = item->text(1);
if (!allPages.isEmpty())
allPages += ", ";
allPages += page;
item->setText(1, allPages);
break;
}
childNode = childNode.nextSibling();
}
}
node = node.nextSibling();
}
}
Dans parseEntry(), nous crons un lment QTreeWidget. Si elle est imbrique dans une
autre balise <entry>, la nouvelle balise dfinit une sous-entre dans lindex, et le nouveau
QTreeWidgetItem est cr en tant quenfant du QTreeWidgetItem qui reprsente lentre
Chapitre 15
XML
369
principale. Dans le cas contraire, nous crons le QTreeWidgetItem avec treeWidget en tant
que parent, en faisant de celui-ci un lment de haut niveau. Nous appelons setText() pour
dfinir le texte prsent en colonne 0 en la valeur de lattribut term de la balise <entry>.
Une fois le QTreeWidgetItem initialis, nous parcourons les nuds enfants du QDomElement
correspondant la balise <entry> courante.
Si llment est <entry>, nous appelons parseEntry() avec llment courant en tant que
deuxime argument. Le QTreeWidgetItem de la nouvelle entre sera alors cr avec le
QTreeWidgetItem de lentre principale en tant que parent.
Si llment est <page>, nous parcourons la liste enfant de llment la recherche dun nud
Text. Une fois celui-ci trouv, nous appelons Text() pour le convertir en un objet QDomText,
et data() pour extraire le texte en tant que QString. Puis nous ajoutons le texte la liste de
numros de page dlimite par des virgules dans la colonne 1 du QTreeWidgetItem.
Voyons comment utiliser la classe DomParser pour analyser un fichier :
void parseFile(const QString &fileName)
{
QStringList labels;
labels << QObject::tr("Terms") << QObject::tr("Pages");
QTreeWidget *treeWidget = new QTreeWidget;
treeWidget->setHeaderLabels(labels);
treeWidget->setWindowTitle(QObject::tr("DOM Parser"));
treeWidget->show();
QFile file(fileName);
DomParser(&file, treeWidget);
}
Nous commenons par dfinir un QTreeWidget. Puis nous crons un QFile et un DomParser.
Une fois le DomParser construit, il analyse le fichier et alimente larborescence.
Comme dans lexemple prcdent, nous avons besoin de la ligne suivante dans le fichier .pro
de lapplication pour tablir un lien avec la bibliothque QtXml :
QT
+= xml
Comme le montre lexemple, lopration consistant parcourir un arbre DOM peut savrer
assez lourde. La simple extraction du texte entre les balises <page> et </page> a ncessit le
parcours dune liste de QDomNode au moyen de firstChild() et de nextSibling(). Les
programmeurs qui utilisent beaucoup DOM crivent souvent leurs propres fonctions conteneur
de haut niveau afin de simplifier les oprations courantes, telles que lextraction de texte entre
les balises douverture et de fermeture.
370
Le choix entre ces approches est souvent indpendant du fait que nous utilisions SAX ou DOM
pour la lecture des documents XML.
Voici un extrait de code qui illustre comment crer un arbre DOM et lcrire au moyen dun
QTextStream:
const int Indent = 4;
QDomDocument doc;
QDomElement root = doc.createElement("doc");
QDomElement quote = doc.createElement("quote");
QDomElement translation = doc.createElement("translation");
QDomText latin = doc.createTextNode("Ars longa vita brevis");
QDomText english = doc.createTextNode("Art is long, life is short");
doc.appendChild(root);
root.appendChild(quote);
root.appendChild(translation);
quote.appendChild(latin);
translation.appendChild(english);
QTextStream out(&file);
doc.save(out, Indent);
Le deuxime argument de save() est la taille du retrait utiliser. Une valeur diffrente de zro
facilite la lecture du contenu du fichier. Voici la sortie du fichier XML :
<doc>
<quote>Ars longa vita brevis</quote>
<translation>Art is long, life is short</translation>
</doc>
Un autre scnario se produit dans les applications qui utilisent larbre DOM comme structure
de donnes primaire. Ces applications effectuent gnralement des oprations de lecture dans
des documents XML en utilisant DOM, puis modifient larbre DOM en mmoire et appellent
finalement save() pour convertir de nouveau larbre vers XML.
Par dfaut, QDomDocument::save() utilise lencodage UTF-8 pour le fichier gnr. Nous
pouvons utiliser un autre encodage en ajoutant au dbut de larbre DOM une dclaration XML
telle que :
<?xml version="1.0" encoding="ISO-8859-1"?>
Chapitre 15
XML
371
La gnration manuelle de fichiers XML nest pas beaucoup plus difficile quavec DOM. Nous
pouvons employer QTextStream et crire les chanes comme nous le ferions avec tout autre
fichier texte. La partie la plus dlicate est de neutraliser linterprtation des caractres spciaux
qui apparaissent dans le texte et les valeurs dattribut. La fonction Qt::escape() neutralise
les caractres <, > et &. Voici un extrait de code qui fait appel cette fonction :
QTextStream out(&file);
out.setCodec("UTF-8");
out << "<doc>\n"
<< " <quote>" << Qt::escape(quoteText) << "</quote>\n"
<< " <translation>" << Qt::escape(translationText)
<< "</translation>\n"
<< "</doc>\n";
Larticle "Generating XML" disponible ladresse http://doc.trolltech.com/qq/qq05-generating-xml.html prsente une classe trs simple qui facilite la gnration de fichiers XML. Cette
classe prend en charge les dtails tels que les caractres spciaux, le retrait et les problmes
dencodage, nous permettant de nous concentrer librement sur le code XML gnrer.
La classe a t conue pour fonctionner avec Qt 3, mais il nest pas difficile de ladapter Qt 4.
16
Aide en ligne
Au sommaire de ce chapitre
Infobulles, informations dtat et aide
"Quest-ce que cest ?"
Utiliser QTextBrowser comme moteur
daide simple
Utiliser lAssistant Qt pour une aide
en ligne puissante
La plupart des applications fournissent une aide en ligne leurs utilisateurs. Certaines
indications apparaissent sous une forme brve, telles que les infobulles, les informations
dtat et "Quest-ce que cest ?". Qt prend naturellement en charge toutes ces informations.
Il existe galement un autre type daide, beaucoup plus approfondi, qui implique de
nombreuses pages de texte. Dans ce cas, nous pouvons utiliser QTextBrowser en tant
que navigateur daide simple. Il est galement possible dinvoquer lAssistant Qt ou un
navigateur HTML depuis notre application.
374
Pour dfinir linfobulle dun QAction que vous ajoutez un menu ou une barre doutils,
nous appelons simplement setToolTip() sur laction. Par exemple :
newAction = new QAction(tr("&New"), this);
newAction->setToolTip(tr("New document"));
Figure 16.1
Une application affichant
une infobulle et une information dtat
Chapitre 16
Aide en ligne
375
Dans certaines situations, il est souhaitable de fournir une information plus complte que celle
offerte par les infobulles et les indicateurs dtat. Nous pouvons, par exemple, afficher une
bote de dialogue complexe contenant un texte explicatif concernant chaque champ, ceci sans
forcer lutilisateur invoquer une fentre daide distincte. Le mode "Quest-ce que cest ?"
reprsente une solution idale dans cette situation. Quand une fentre se trouve en mode
"Quest-ce que cest ?", le curseur se transforme en
et lutilisateur peut cliquer sur tout
composant de linterface utilisateur pour obtenir le texte daide le concernant. Pour entrer en
mode "Quest-ce que cest ?", lutilisateur peut soit cliquer sur le bouton ? dans la barre de titre
de la fentre (sous Windows et KDE), soit appuyer sur Maj+F1.
Voici un exemple de texte "Quest-ce que cest ?" :
dialog->setWhatsThis(tr("<img src=\":/images/icon.png\">"
" The meaning of the Source field depends "
"on the Type field:"
"<ul>"
"<li><b>Books</b> have a Publisher"
"<li><b>Articles</b> have a Journal name with "
"volume and issue number"
"<li><b>Theses</b> have an Institution name "
"and a Department name"
"</ul>"));
Nous pouvons utiliser les balises HTML pour mettre en forme le texte dinformation dun
"Quest-ce que cest ?". Dans lexemple fourni, nous incluons une image (qui est rpertorie dans le fichier de ressources de lapplication), une liste puces et du texte en gras.
(Voir Figure 16.2.) Vous trouverez les balises et attributs pris en charge par Qt ladresse
http://doc.trolltech.com/4.1/richtext-html-subset.html.
Figure 16.2
Une bote de dialogue
affichant du texte daide
"Quest-ce que cest ?"
376
Un texte "Quest-ce que cest ?" dfini sur une action apparat quand lutilisateur clique sur
llment de menu ou sur le bouton de la barre doutils, ou encore sil appuie sur la touche
de raccourci alors quil se trouve en mode "Quest-ce que cest ?".Lorsque les composants de
linterface utilisateur de la fentre principale dune application sont associs du texte
"Quest-ce que cest ?", il est habituel de proposer une option de mme nom dans le menu
Aide et un bouton correspondant dans la barre doutils. Pour ce faire, il suffit de crer une
action Quest-ce que cest? avec la fonction statique QWhatThis::createAction() et
dajouter laction retourne un menu Aide et une barre doutils. La classe QWhatsThis
fournit des fonctions statiques destines programmer lentre dans le mode "Quest-ce que
cest ?" et la sortie de ce mode.
Chapitre 16
Aide en ligne
377
private:
QTextBrowser *textBrowser;
QPushButton *homeButton;
QPushButton *backButton;
QPushButton *closeButton;
};
HelpBrowser fournit une fonction statique pouvant tre appele nimporte o dans lapplication.
Cette fonction cre une fentre HelpBrowser et affiche la page donne.
Figure 16.3
Le widget HelpBrowser
378
Nous dfinissons lattribut Qt::WA_GroupLeader car nous souhaitons faire apparatre les
fentres HelpBrowser depuis des botes de dialogue modales en complment de la fentre
principale. Ces botes empchent normalement lutilisateur dinteragir avec toute autre fentre
de lapplication. Cependant, aprs avoir demand de laide, cet utilisateur doit de toute
vidence tre autoris interagir la fois avec la bote de dialogue modale et le navigateur
daide. La dfinition de lattribut Qt::WA_GroupLeader autorise cette interaction.
Nous fournissons deux accs pour la recherche, le premier tant le systme de fichiers contenant la documentation de lapplication et le second tant lemplacement des ressources image.
Le code HTML peut inclure des rfrences aux images dans le systme de fichiers de faon
classique, mais il peut galement faire rfrence aux ressources image en utilisant un
chemin daccs commenant par :/ (deux points, slash). Le paramtre page est le nom du
fichier de documentation, avec une ancre HTML facultative (une ancre HTML est la cible dun
lien).
void HelpBrowser::updateWindowTitle()
{
setWindowTitle(tr("Help: %1").arg(textBrowser->documentTitle()));
}
Ds que la page source change, le slot updateWindowTitle() est appel. La fonction documentTitle() retourne le texte spcifi dans la balise <title> de la page.
void HelpBrowser::showPage(const QString &page)
{
QString path = QApplication::applicationDirPath() + "/doc";
HelpBrowser *browser = new HelpBrowser(path, page);
Chapitre 16
Aide en ligne
379
browser->resize(500, 400);
browser->show();
}
Dans la fonction statique showPage(), nous crons la fentre HelpBrowser, puis nous laffichons. Comme nous avons dfini lattribut Qt::WA_DeleteOnClose dans le constructeur de
HelpBrowser, la fentre sera dtruite automatiquement lors de sa fermeture par lutilisateur.
Pour cet exemple, nous supposons que la documentation est stocke dans le sous-rpertoire
doc du rpertoire contenant lexcutable de lapplication. Toutes les pages transmises la
fonction showPage() seront extraites de ce sous-rpertoire.
Vous tes maintenant prt invoquer le navigateur daide depuis lapplication. Dans la fentre
principale de lapplication, crez une action Aide et connectez-la un slot help() sur le
modle suivant :
void MainWindow::help()
{
HelpBrowser::showPage("index.html");
}
Nous supposons ici que le fichier daide principal se nomme index.html. Dans le cas de
botes de dialogue, vous connecteriez le bouton Aide un slot help() similaire celui-ci :
void EntryDialog::help()
{
HelpBrowser::showPage("forms.html#editing");
}
Ici, nous effectuons la recherche dans un fichier daide diffrent, forms.html et faisons dfiler
le QTextBrowser jusqu lancre editing.
+= assistant
380
Nous allons maintenant examiner le code dune nouvelle classe HelpBrowser qui utilise
lAssistant Qt.
#ifndef HELPBROWSER_H
#define HELPBROWSER_H
class QAssistantClient;
class QString;
class HelpBrowser
{
public:
static void showPage(const QString &page);
private:
static QAssistantClient *assistant;
};
#endif
Voici le nouveau fichier helpbrowser.cpp:
#include <QApplication>
#include <QAssistantClient>
#include "helpbrowser.h"
QAssistantClient *HelpBrowser::assistant = 0;
void HelpBrowser::showPage(const QString &page)
{
QString path = QApplication::applicationDirPath() + "/doc/" + page;
if (!assistant)
assistant = new QAssistantClient("");
assistant->showPage(path);
}
Chapitre 16
Aide en ligne
381
III
Qt : tude avance
17
Internationalisation
18
Environnement multithread
19
20
21
17
Internationalisation
Au sommaire de ce chapitre
Travailler avec Unicode
Crer des applications ouvertes aux
traductions
Passer dynamiquement dune langue
une autre
Traduire les applications
En complment de lalphabet latin utilis pour le franais et de nombreuses langues europennes, Qt offre une large prise en charge des systmes dcriture du reste du monde.
Qt utilise Unicode en interne et par le biais de lAPI. Ainsi, toutes les langues utilises
par linterface utilisateur sont prises en charge de faon identique.
Le moteur de texte de Qt est en mesure de grer tous les systmes dcriture nonlatins majeurs, dont larabe, le chinois, le cyrillique, lhbreu, le japonais, le coren,
le tha et les langues Hindi.
Le moteur de disposition de Qt prend en charge lcriture de la droite vers la gauche
pour les langues telles que larabe et lhbreu.
Certaines langues ncessitent des mthodes spciales dentre de texte. Les widgets
ddition tels que QLineEdit et QTextEdit fonctionnent correctement avec toute
mthode dentre installe sur le systme de lutilisateur.
386
Il faut souvent fournir plus quune simple adaptation du texte saisi par les utilisateurs dans leur
langue native. Linterface utilisateur entire doit galement tre traduite. Qt facilite cette
tche : il suffit de traiter toutes les chanes visibles par lutilisateur avec la fonction tr()
(comme nous lavons fait dans les chapitres prcdents) et dutiliser les outils de prise en
charge de Qt pour prparer la traduction des fichiers dans les langues requises. Qt fournit un
outil GUI nomm Qt Linguist destin tre utilis par les traducteurs. Qt Linguist est complt
par deux programmes de ligne de commande, lupdate et lrelease, qui sont gnralement
excuts par les dveloppeurs de lapplication.
Pour la plupart des applications, un fichier de traduction est charg au dmarrage qui tient
compte des paramtres locaux de lutilisateur. Mais dans certains cas, il est galement ncessaire de
pouvoir basculer dune langue lautre au moment de lexcution. Ceci est parfaitement possible avec Qt, bien que cette opration implique un travail supplmentaire. Et grce au systme
de disposition de Qt, les divers composants de linterface utilisateur sont automatiquement
ajusts pour faire de la place aux textes traduits quand ils sont plus longs que les originaux.
Si le fichier source est cod en Latin-1, il est ais de spcifier des caractres en Latin-1 :
str[0] = ;
1. Les versions rcentes dUnicode affectent des valeurs de caractres au-dessus de 65 535. Ces caractres
peuvent tre reprsents avec des squences de deux valeurs de 16 bits nommes "paires de substitution".
Chapitre 17
Internationalisation
387
Nous pouvons dsigner tout caractre Unicode par sa valeur numrique. Voici, par exemple,
comment spcifier la lettre grecque majuscule sigma ("") et le caractre montaire euro ("").
str[0] = QChar(0x3A3);
str[0] = QChar(0x20AC);
Les valeurs numriques de tous les caractres pris en charge par Unicode sont rpertories
ladresse http://www.unicode.org/standard/. Si votre besoin en caractres Unicode nonLatin-1 est rare et ponctuel, la recherche en ligne est la solution approprie. Qt fournit cependant
des moyens plus pratiques dentrer des chanes Unicode dans un programme, comme nous le
verrons ultrieurement dans cette section.
Le moteur de texte de Qt 4 prend en charge les systmes dcriture suivants sur toutes les
plates-formes : arabe, chinois, cyrillique, grec, hbreu, japonais, coren, lao, latin, tha et vietnamien. Il prend aussi en charge tous les scripts Unicode 4.1 ne ncessitant pas de traitement
spcial. En outre, les systmes dcriture suivants sont pris en charge sur X11 avec Fonconfig
et sur les versions rcentes de Windows : bengali, devanagari, gujarati, gurmukhi, kannada,
khmer, malayalam, syriac, tamil, telugu, thaana (dhivehi) et tibtain. Loriya, enfin, est pris en
charge sur X11 et le mongolien ainsi que le sinhala sont pris en charge par Windows XP.
En supposant que les polices correctes sont installes sur le systme, Qt peut afficher le texte
au moyen de ces systmes dcriture. Et en supposant que les mthodes dentre correctes sont
installes, les utilisateurs pourront entrer du texte correspondant ces systmes dcriture dans
leurs applications Qt.
La programmation avec QChar diffre lgrement de celle avec char. Pour obtenir la valeur
numrique dun QChar, vous devez appeler unicode() sur celui-ci. Pour obtenir la valeur
ASCII ou Latin-1 dun QChar, il vous faut appeler toLatin1(). Pour les caractres nonLatin-1, toLatin1() retourne "\0".
Si nous savons que toutes les chanes dun programme appartiennent au systme ASCII, nous
pouvons utiliser des fonctions <cctype> standard telles que isalpha(), isdigit() et
isspace() sur la valeur de retour de toLatin1(). Il est cependant gnralement prfrable
de faire appel aux fonctions membre de QChar pour raliser ces oprations, car elles fonctionneront pour tout caractre Unicode. Les fonctions fournies par QChar incluent isPrint(),
isPunct(), isSpace(), isMark(), isLetter(), isNumber(), isLetterOrNumber(),
isDigit(), isSymbol(), isLower() et isUpper(). Voici, par exemple, un moyen de tester
si un caractre est un chiffre ou une lettre majuscule :
if (ch.isDigit() || ch.isUpper())
...
Lextrait de code fonctionne pour tout alphabet qui distingue les majuscules des minuscules,
dont lalphabet latin, grec et cyrillique.
Lorsque vous avez une chane Unicode, vous pouvez lutiliser tout emplacement de lAPI de
Qt o est attendu un QString. Qt prend alors la responsabilit de lafficher correctement et
de la convertir en codages adquats pour le systme dexploitation.
388
Nous devons tre particulirement attentifs lorsque nous lisons ou crivons des fichiers texte.
Ces derniers peuvent utiliser plusieurs codages, et il est souvent impossible de deviner le
codage dun fichier de ce type partir de son contenu. Par dfaut, QTextStream utilise le codage
8 bits local du systme pour la lecture et lcriture. Pour les Etats-Unis et lEurope de louest,
il sagit habituellement de Latin-1.
Si nous concevons notre propre format de fichier et souhaitons tre en mesure de lire et dcrire
des caractres Unicode arbitraires, nous pouvons enregistrer les donnes sous la forme Unicode
en appelant
stream.setCodec("UTF-16");
stream.setGenerateByteOrderMark(true);
avant de commencer lcriture dans le QTextStream. Les donnes seront alors enregistres au
format UTF-16, format qui ncessite deux octets par caractre, et seront prfixes par une
valeur spciale de 16 bits (la marque dordre doctet Unicode, 0xFFFE) indiquant si ce fichier
est en Unicode et si les octets se trouvent dans lordre little-endian ou big-endian. Le format
UTF-16 tant identique la reprsentation mmoire dun QString, la lecture et lcriture de
chanes Unicode dans ce format peut tre trs rapide. Il se produit cependant une surcharge lors
de lenregistrement de donnes ASCII pures au format UTF-16, car deux octets sont stocks
pour chaque caractre au lieu dun seul.
Il est possible de mentionner dautres codage en appelant setCodec() avec un QTextCodec
appropri. Un QTextCodec est un objet qui effectue une conversion entre Unicode et un
codage donn. Les QTextCodec sont employs dans diffrents contextes par Qt. En interne, ils
sont utiliss pour la prise en charge des polices, des mthodes dentre, du presse-papiers, des
oprations de glisser-dposer et des noms de fichiers. Mais ils sont galement utiles pour
lcriture dapplications Qt.
Lors de la lecture dun fichier texte, QTextStream dtecte Unicode automatiquement si le
fichier dbute par une marque dordre doctet. Ce comportement peut tre dsactiv en appelant setAutoDetectUnicode(false). Si les donnes ne sont pas supposes commencer par la
marque dordre doctet, il est prfrable dappeler setCodec() avec "UTF-16" avant la lecture.
UTF-8 est un autre codage qui prend en charge la totalit du systme Unicode. Son principal
avantage par rapport UTF-16 est quil sagit dun super ensemble ASCII. Tout caractre se
situant dans la plage 0x00 0x7F est reprsent par un seul octet. Les autres caractres, dont
les caractres Latin-1 au-dessus de 0x7F, sont reprsents par des squences multi-octets. Pour
ce qui est du texte en majorit ASCII, UTF-8 occupe environ la moiti de lespace consomm
par UTF-16. Pour utiliser UTF-8 avec QTextStream, appelez setCodec() avec "UTF-8"
comme nom de codec avant les oprations de lecture et dcriture.
Si nous souhaitons toujours lire et crire en Latin-1 sans tenir compte du systme de codage
local de lutilisateur, nous pouvons dfinir le codec "ISO 8859-1" sur QTextStream.
Par exemple :
QTextStream in(&file);
in.setCodec("ISO 8859-1");
Chapitre 17
Internationalisation
389
Certains formats de fichiers spcifient leur codage dans leur en-tte. Len-tte est gnralement
en ASCII brut pour assurer une lecture correcte quel que soit le codage utilis. Le format de
fichier XML en est un exemple intressant. Les fichiers XML sont normalement encods sous
la forme UTF-8 ou UTF-16. Pour les lire correctement, il faut appeler setCodec() avec
"UTF-8".Si le format est UTF-16, QTextStream le dtectera automatiquement et sadaptera.
Len-tte <?xml?> dun fichier XML contient quelquefois un argument encoding. Par exemple :
<?xml version="1.0" encoding="EUC-KR"?>
Comme QTextStream ne vous permet pas de changer le codage une fois la lecture commence, la meilleure faon dappliquer un codage explicite consiste recommencer lire le fichier,
en utilisant le codec correct (obtenu laide de QTextCodec::codecForName()). Dans le cas
de XML, nous pouvons viter davoir grer le codage nous-mmes en utilisant les classes
XML de Qt dcrites dans le Chapitre 15.
Les QTextCodec peuvent galement tre employs pour spcifier le codage de chanes dans le
code source. Considrons, par exemple, une quipe de programmeurs japonais crivant une
application destine principalement au march des particuliers. Il est probable que ces
programmeurs crivent leur code source dans un diteur de texte qui utilise un codage tel que
EUC-JP ou Shift-JIS. Un diteur de ce type leur permet de saisir des caractres japonais sans
problme. Ils peuvent donc crire le type de code suivant :
QPushButton *button = new QPushButton(tr("
"));
Par dfaut, Qt interprte les arguments de tr() comme Latin-1. Pour les autres cas, nous appelons
la fonction statique QTextCodec::setCodecForTr(). Par exemple :
QTextCodec::setCodecForTr(QTextCodec::codecForName("EUC-JP"));
Cette opration doit tre effectue avant le premier appel tr(). En gnral, elle est ralise
dans main(), immdiatement aprs la cration de lobjet QApplication.
Les autres chanes spcifies dans le programme seront interprtes comme des chanes Latin1. Si les programmeurs souhaitent y entrer galement des caractres japonais, ils peuvent les
convertir explicitement en Unicode au moyen dun QTextCodec:
QString text = japaneseCodec->toUnicode("
");
Les techniques dcrites ci-dessus peuvent tre appliques toute langue non Latin-1, dont le
chinois, le grec, le coren et le russe.
390
cISO 8859-5
cIscii-Mlm
cUTF-8
cBig5
cISO 8859-6
cIscii-Ori
cUTF-16
cBig5-HKSCS
cISO 8859-7
cIscii-Pnj
cUTF-16BE
cEUC-JP
cISO 8859-8
cIscii-Tlg
cUTF-16LE
cEUC-KR
cISO 8859-9
cIscii-Tml
cWindows-1250
cGB18030-0
cISO 8859-10
cJIS X 0201
cWindows-1251
cIBM 850
cISO 8859-13
cJIS X 0208
cWindows-1252
cIBM 866
cISO 8859-14
cKOI8-R
cWindows-1253
cIBM 874
cISO 8859-15
cKOI8-U
cWindows-1254
cISO 2022-JP
cISO 8859-16
cMuleLao-1
cWindows-1255
cISO 8859-1
cIscii-Bng
cROMAN8
cWindows-1256
cISO 8859-2
cIscii-Dev
cShift-JIS
cWindows-1257
cISO 8859-3
cIscii-Gjr
cTIS-620
cWindows-1258
cISO 8859-4
cIscii-Knd
cTSCII
cWINSAMI2
Sassurer que chaque chane visible par lutilisateur passe par tr().
Aucune de ces oprations nest ncessaire pour les applications qui ne seront jamais traduites.
Mais lemploi de tr() ne ncessite pratiquement aucun effort et laisse la porte ouverte toute
traduction ultrieure.
tr() est une fonction statique dfinie dans QObject et remplace dans chaque sous-classe
dfinie avec la macro Q_OBJECT. Lorsque nous crivons du code dans une sous-classe QObject,
nous pouvons appeler tr() sans formalit. Un appel tr() retourne une traduction si elle est
disponible. Dans le cas contraire, le texte original est retourn.
Chapitre 17
Internationalisation
391
Pour prparer les fichiers de traduction, nous devons excuter loutil lupdate de Qt. Cet outil
extrait tous les littraux de chane qui apparaissent dans les appels de tr() et produit des
fichiers de traduction qui contiennent toutes les chanes prtes tre traduites. Les fichiers
peuvent alors tre expdis un traducteur afin quil y ajoute les traductions. Ce processus est
expliqu dans la section "Traduction dapplication" un peu plus loin dans ce chapitre.
La syntaxe dun appel de tr() est la suivante :
Context::tr(sourceText, comment)
La partie Context est le nom dune sous-classe QObject dfinie avec la macro Q_OBJECT.
Il nest pas ncessaire de lui prciser si nous appelons tr() depuis une fonction membre de la
classe en question. La partie sourceText est le littral chane traduire. La partie comment
est facultative. Elle permet de fournir des informations supplmentaires au traducteur.
Voici quelques exemples :
RockyWidget::RockyWidget(QWidget *parent)
: QWidget(parent)
{
QString str1 = tr("Letter");
QString str2 = RockyWidget::tr("Letter");
QString str3 = SnazzyDialog::tr("Letter");
QString str4 = SnazzyDialog::tr("Letter", "US paper size");
}
Le contexte des deux premiers appels tr() est "RockyWidget" et celui des deux derniers
appels est "SnazzyDialog"."Letter" est le texte source des quatre appels. Le dernier dentre eux
comporte galement un commentaire destin aider le traducteur comprendre le sens du
texte source.
Dans des contextes diffrents, les chanes sont traduites indpendamment les unes des autres.
Les traducteurs travaillent gnralement sur un seul contexte la fois, souvent avec lapplication
en cours dexcution et en affichant la bote de dialogue ou le widget soumis la traduction.
Lorsque nous appelons tr() depuis une fonction globale, nous devons spcifier le contexte
explicitement. Toute sous-classe QObject de lapplication peut tre employe en tant que
contexte. Si aucun contexte nest appropri, il est toujours possible de recourir QObject luimme. Par exemple :
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Dans tous les exemples tudis jusqu prsent, le contexte tait celui dun nom de classe.
Cest pratique, car nous pouvons presque toujours lomettre, mais ce nest pas une obligation.
392
La faon la plus courante de traduire une chane en Qt consiste utiliser la fonction QApplication::translate(), qui accepte jusqu trois arguments : le contexte, le texte source et le
commentaire facultatif. Voici, par exemple, une autre faon de traduire "Hello Qt !":
QApplication::translate("Global Stuff", "Hello Qt!")
Le problme ici est que lupdate ne sera pas en mesure dextraire le littral chane "OpenDrawer 2D", car il napparat pas lintrieur dun appel tr(). Le traducteur naura donc pas
la possibilit de traduire la chane. Ce problme se pose souvent avec les chanes dynamiques :
// INCORRECT
statusBar()->showMessage(tr("Host " + hostName + " found"));
Ici, la chane que nous transmettons tr() varie en fonction de la valeur de hostName, de sorte
que nous ne pouvons pas raisonnablement nous attendre ce que tr() la traduise correctement.
La solution consiste excuter QString::arg():
statusBar()->showMessage(tr("Host %1 found").arg(hostName));
Ce code repose sur le principe suivant : le littral chane "Host %1 found" est transmis tr().
En supposant quun fichier de traduction en franais est charg, tr() retournera quelque chose
comme "Hte %1 trouv".Puis le paramtre "%1" est remplac par le contenu de la variable
hostName.
Bien quil soit gnralement dconseill dappeler tr() sur une variable, il est possible de
faire fonctionner cette technique correctement. Nous devons utiliser la macro QT_TR_NOOP()
afin de marquer les littraux chane traduire avant de les affecter une variable. Cette
mthode se rvle particulirement intressante pour les tableaux statiques de chanes. Par
exemple :
void OrderForm::init()
{
static const char * const flowers[] = {
QT_TR_NOOP("Medium Stem Pink Roses"),
QT_TR_NOOP("One Dozen Boxed Roses"),
QT_TR_NOOP("Calypso Orchid"),
QT_TR_NOOP("Dried Red Rose Bouquet"),
QT_TR_NOOP("Mixed Peonies Bouquet"),
0
Chapitre 17
Internationalisation
393
};
for (int i = 0; flowers[i]; ++i)
comboBox->addItem(tr(flowers[i]));
}
La macro QT_TR_NOOP() retourne simplement son argument. Mais lupdate extraira toutes
les chanes encadres par cette dernire afin quelles puissent tre traduites. Par la suite, au
moment dutiliser la variable, nous appellerons normalement tr(). Mme si elle reoit une
variable, cette fonction remplit correctement son rle de traduction.
La macro QT_TRANSLATE_NOOP() fonctionne comme QT_TR_NOOP() la diffrence quelle
reoit aussi un contexte. Cette macro est pratique pour initialiser des variables lextrieur
dune classe :
static const char * const flowers[] = {
QT_TRANSLATE_NOOP("OrderForm", "Medium Stem Pink Roses"),
QT_TRANSLATE_NOOP("OrderForm", "One Dozen Boxed Roses"),
QT_TRANSLATE_NOOP("OrderForm", "Calypso Orchid"),
QT_TRANSLATE_NOOP("OrderForm", "Dried Red Rose Bouquet"),
QT_TRANSLATE_NOOP("OrderForm", "Mixed Peonies Bouquet"),
0
};
+= QT_NO_CAST_FROM_ASCII
Chaque littral chane devra ainsi tre obligatoirement trait par tr() ou QLatin1String(),
selon quil devra tre traduit ou non. Les chanes qui ne seront pas encadres par ces fonctions
vont ainsi gnrer une erreur la compilation, et il ne vous restera plus qu ajouter les appels
de tr() ou QLatin1String() manquants.
Une fois chaque chane visible par lutilisateur insre dans un appel de tr(), il ne reste plus
qu charger un fichier de traduction. Lopration se droule gnralement dans la fonction
main() de lapplication. Voici, par exemple, comment nous chargerions un fichier de traduction
bas sur les paramtres locaux de lutilisateur :
int main(int argc, char *argv[])
{
394
return app.exec();
}
Un objet QTranslator ne peut contenir quun seul fichier de traduction la fois. Cest pourquoi nous utilisons un QTranslator distinct pour la traduction de Qt. Cela ne prsente aucun
problme puisque nous pouvons installer autant de traducteurs que ncessaire. QApplication
les utilisera tous lors de la recherche dune traduction.
Certaines langues, telles que larabe et lhbreu, sont crites de droite gauche au lieu de
gauche droite. Dans cette situation, la mise en forme complte de lapplication doit tre
inverse en appelant QApplication::setLayoutDirection( Qt::RightToLeft). Les
fichiers de traduction de Qt contiennent un marqueur spcial nomm "LTR" qui indique si la
Chapitre 17
Internationalisation
395
langue scrit de gauche droite ou de droite gauche. Nous navons donc gnralement pas
besoin dappeler setLayoutDirection().
Il serait plus pratique pour les utilisateurs de fournir les applications avec les fichiers de traduction
intgrs lexcutable, en utilisant le systme de ressource de Qt. Cette technique permettrait
non seulement de rduire le nombre de fichiers distribus pour constituer le produit, mais aussi
dviter le risque de perte ou de suppression accidentelle de fichiers de traduction.
En supposant que les fichiers .qm sont situs dans un sous-rpertoire translations se trouvant
dans larbre source, nous aurions alors un fichier myapp.qrc avec le contenu suivant :
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>translations/myapp_de.qm</file>
<file>translations/myapp_fr.qm</file>
<file>translations/myapp_zh.qm</file>
<file>translations/qt_de.qm</file>
<file>translations/qt_fr.qm</file>
<file>translations/qt_zh.qm</file>
</qresource>
</RCC>
= myapp.qrc
Nous devons enfin spcifier :/translations comme chemin daccs aux fichiers de traduction
dans main(). Les deux points qui apparaissent en premire position indiquent que le chemin
daccs fait rfrence une ressource et non un fichier situ dans le systme de fichiers.
Nous avons maintenant tudi tous les points ncessaires pour permettre une application de
fonctionner en utilisant des traductions dans dautres langues. Mais la langue et la direction du
systme dcriture ne sont pas les seuls points variables entre diffrents pays et cultures. Un
programme internationalis doit galement prendre en compte les formats de date, dheure,
montaire, numrique et lordre de classement des chanes. Qt inclut une classe QLocale qui
fournit des formats de date/dheure et numrique localiss. Pour obtenir dautres informations
locales, nous pouvons faire appel aux fonctions C++ setlocale() et localeconv().
Certaines classes et fonctions de Qt adaptent leur comportement en fonction des paramtres
locaux :
QString::localeAwareCompare() compare deux chanes en prenant en compte les
paramtres locaux. Elle permet de trier les lments visibles par lutilisateur.
La fonction toString() fournie par QDate, QTime et QDateTime retourne une chane
dans un format local quand elle est appele avec Qt::LocalDate comme argument.
Par dfaut, les widgets QDateEdit et QDateTimeEdit prsentent les dates dans le format
local.
Enfin, il est possible quune application traduite ait besoin dutiliser des icnes diffrentes de
celles fournies initialement. Par exemple, les flches gauche et droite apparaissant sur les
396
boutons Prcdente et Suivante dun navigateur Web doivent tre inverses dans le cas
dune langue scrivant de droite gauche. Voici comment procder :
if (QApplication::isRightToLeft()) {
backAction->setIcon(forwardIcon);
forwardAction->setIcon(backIcon);
} else {
backAction->setIcon(backIcon);
forwardAction->setIcon(forwardIcon);
}
Les icnes contenant des caractres alphabtiques doivent trs souvent tre traduites. Par
exemple, la lettre "I" qui apparat sur un bouton de barre doutils associ une option Italique dun traitement de texte doit tre remplace par un "C" en espagnol (Cursivo) et par un
"K" en danois, nerlandais, allemand, norvgien et sudois (Kursiv). Voici un moyen simple
dy parvenir :
if (tr("Italic")[0] == C) {
italicAction->setIcon(iconC);
} else if (tr("Italic")[0] == K) {
italicAction->setIcon(iconK);
} else {
italicAction->setIcon(iconI);
}
Une alternative consiste utiliser la prise en charge de multiples paramtres locaux de la part
du systme de ressource. Dans le fichier .qrc, il est possible de spcifier un paramtre rgional
pour une ressource au moyen de lattribut lang. Par exemple :
<qresource>
<file>italic.png</file>
</qresource>
<qresource lang="es">
<file alias="italic.png">cursivo.png</file>
</qresource>
<qresource lang="sv">
<file alias="italic.png">kursiv.png</file>
</qresource>
Chapitre 17
Internationalisation
397
langue lautre. Un changement de langue sans redmarrage peut tre ncessaire pour une
application employe en permanence par diffrentes personnes. Par exemple, les applications
employes par les oprateurs de centres dappels, par des traducteurs simultans et par des
oprateurs de caisses enregistreuses informatises requirent souvent cette possibilit.
Le passage dynamique dune langue une autre est un peu plus complexe programmer que le
chargement dune traduction unique au lancement de lapplication, mais ce nest pas trs difficile. Voici comment procder :
Vous devez offrir lutilisateur le moyen de passer une autre langue.
Pour chaque widget ou bote de dialogue, vous devez dfinir toutes les chanes susceptibles
dtre traduites dans une fonction distincte (souvent nomme retranslateUi()) et appeler
cette fonction lors des changements de langue.
Examinons les segments les plus importants du code source dune application "centre
dappel".Lapplication fournit un menu Language permettant lutilisateur de dfinir la
langue au moment de lexcution. La langue par dfaut est langlais. (Voir Figure 17.1)
Figure 17.1
Un menu Language
dynamique
Comme nous ne savons pas quelle langue lutilisateur souhaite employer au lancement de
lapplication, nous ne chargeons pas de traduction dans la fonction main(). Nous les chargeons plutt dynamiquement quand elles savrent ncessaires. Ainsi, tout le code dont nous
avons besoin pour grer les traductions doit tre plac dans les classes des botes de dialogue et
de la fentre principale.
Examinons la sous-classe QMainWindow de lapplication.
MainWindow::MainWindow()
{
journalView = new JournalView;
setCentralWidget(journalView);
qApp->installTranslator(&appTranslator);
qApp->installTranslator(&qtTranslator);
qmPath = qApp->applicationDirPath() + "/translations";
createActions();
createMenus();
retranslateUi();
}
398
Dans le constructeur, nous dfinissons le widget central en tant que JournalView, une sousclasse de QTableWidget. Puis nous dfinissons quelques variables membre prives en liaison
avec la traduction :
La variable appTranslator est un objet QTranslator utilis pour stocker la traduction
de lapplication en cours.
La variable qtTranslator est un objet QTranslator utilis pour stocker la traduction de Qt.
La variable qmPath est un QString qui spcifie le chemin daccs au rpertoire contenant
les fichiers de traduction de lapplication.
Nous appelons enfin les fonctions prives createActions() et createMenus() pour crer le
systme de menus, et nous appelons retranslateUi(), galement une fonction prive, pour
dfinir les chanes initialement visibles par lutilisateur :
void MainWindow::createActions()
{
newAction = new QAction(this);
connect(newAction, SIGNAL(triggered()), this, SLOT(newFile()));
La fonction createActions() cre normalement ses objets QAction, mais sans dfinir aucun
texte ou touche de raccourci. Ces oprations seront effectues dans retranslateUi().
void MainWindow::createMenus()
{
fileMenu = new QMenu(this);
fileMenu->addAction(newAction);
fileMenu->addAction(openAction);
fileMenu->addAction(saveAction);
fileMenu->addAction(exitAction);
createLanguageMenu();
helpMenu = new QMenu(this);
helpMenu->addAction(aboutAction);
helpMenu->addAction(aboutQtAction);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(editMenu);
menuBar()->addMenu(reportsMenu);
menuBar()->addMenu(languageMenu);
menuBar()->addMenu(helpMenu);
}
La fonction createMenus() cre des menus, mais ne leur affecte aucun titre. Une fois encore,
cette opration sera effectue dans retranslateUi().
Chapitre 17
Internationalisation
399
aboutQtAction->setText(tr("About &Qt"));
aboutQtAction->setStatusTip(tr("Show the Qt librarys About box"));
fileMenu->setTitle(tr("&File"));
editMenu->setTitle(tr("&Edit"));
reportsMenu->setTitle(tr("&Reports"));
languageMenu->setTitle(tr("&Language"));
helpMenu->setTitle(tr("&Help"));
setWindowTitle(tr("Call Center"));
}
Cest dans la fonction retranslateUi() que se produisent tous les appels de tr() pour la
classe MainWindow. Ces appels ont lieu la fin du constructeur de MainWindow et chaque
fois quun utilisateur change la langue de lapplication par le biais du menu Language.
Nous dfinissons chaque texte, touche de raccourci et information dtat de QAction. Nous
dfinissons galement chaque titre de QMenu ainsi que le titre de fentre.
La fonction createMenus() prsente prcdemment a appel createLanguageMenu()
pour remplir le menu Language avec une liste de langues :
void MainWindow::createLanguageMenu()
{
languageMenu = new QMenu(this);
languageActionGroup = new QActionGroup(this);
connect(languageActionGroup, SIGNAL(triggered(QAction *)),
this, SLOT(switchLanguage(QAction *)));
QDir dir(qmPath);
QStringList fileNames =
dir.entryList(QStringList("callcenter_*.qm"));
for (int i = 0; i < fileNames.size(); ++i) {
QString locale = fileNames[i];
locale.remove(0, locale.indexOf(_) + 1);
locale.truncate(locale.lastIndexOf(.));
QTranslator translator;
translator.load(fileNames[i], qmPath);
QString language = translator.translate("MainWindow",
"English");
400
Au lieu de coder en dur les langues prises en charge par lapplication, nous crons une entre
de menu pour chaque fichier .qm situ dans le rpertoire translations de lapplication. Pour des
raisons de simplicit, nous supposons que la langue anglaise (English) possde aussi un fichier
.qm. Une alternative consisterait appeler clear() sur les objets QTranslator lorsque
lutilisateur choisit English.
La difficult consiste trouver un nom adquat pour la langue fournie par chaque fichier .qm.
Le fait dafficher simplement "en" pour "English" ou "de" pour "Deutsch" semble primaire et
risque de semer la confusion dans lesprit de certains utilisateurs. La solution retenue dans
createLanguageMenu() est de contrler la traduction de la chane "English" dans le contexte
"MainWindow".Cette chane doit tre transforme en "Deutsch" pour une traduction allemande et en "
Nous crons un QAction cocher pour chaque langue et stockons le nom local dans llment
"data" de laction. Nous les ajoutons un objet QActionGroup afin de nous assurer quun seul
lment du menu Language est coch la fois. Quand une action du groupe est choisie par
lutilisateur, le QActionGroup met le signal triggered(QAction *), qui est connect
switchLanguage().
void MainWindow::switchLanguage(QAction *action)
{
QString locale = action->data().toString();
appTranslator.load("callcenter_" + locale, qmPath);
qtTranslator.load("qt_" + locale, qmPath);
retranslateUi();
}
Le slot switchLanguage() est appel lorsque lutilisateur choisit une langue dans le menu
Language. Nous chargeons les fichiers de traduction de lapplication et de Qt, et nous appelons
retranslateUi() pour traduire toutes les chanes de la fentre principale.
Sur Windows, une solution alternative au menu Language consisterait rpondre des vnements LocaleChange, un type dvnement mis par Qt quand un changement dans les paramtres locaux de lenvironnement est dtect. Ce type existe sur toutes les plates-formes prises
en charge par Qt, mais il nest en fait gnr que sur Windows, lorsque lutilisateur change les
paramtres locaux du systme (dans les Options rgionales et linguistiques du Panneau de
Chapitre 17
Internationalisation
401
Si lutilisateur change les paramtres locaux pendant que lapplication est en cours dexcution,
nous tentons de charger les fichiers de traduction corrects pour les nouveaux paramtres et
appelons retranslateUi() pour mettre jour linterface utilisateur. Dans tous les cas, nous
transmettons lvnement la fonction changeEvent() de la classe de base, cette dernire
pouvant aussi tre intresse par LocaleChange ou dautres vnements de changement.
Nous en avons maintenant termin avec lexamen du code de MainWindow. Nous allons
prsent observer le code de lune des classes de widget de lapplication, la classe JournalView, afin de dterminer quelles modifications sont ncessaires pour lui permettre de prendre
en charge la traduction dynamique.
JournalView::JournalView(QWidget *parent)
: QTableWidget(parent)
{
retranslateUi();
}
Nous rimplmentons galement la fonction changeEvent() pour appeler retranslateUi() sur les vnements LanguageChange. Qt gnre un vnement LanguageChange
lorsque le contenu dun QTranslator install sur QApplication change. Dans notre application, ceci se produit lorsque nous appelons load() sur appTranslator ou qtTranslator
depuis MainWindow::switchLanguage() ou MainWindow::changeEvent().
402
Les vnements LanguageChange ne doivent pas tre confondus avec les vnements LocaleChange. Ces derniers sont gnrs par le systme et demandent lapplication de charger une
nouvelle traduction. Les vnements LanguageChange sont gnrs par Qt et demandent aux
widgets de lapplication de traduire toutes leurs chanes.
Lorsque nous avons implment MainWindow, il ne nous a pas t ncessaire de rpondre
LanguageChange. Nous avons simplement appel retranslateUi() chaque appel de
load() sur un QTranslator.
void JournalView::retranslateUi()
{
QStringList labels;
labels << tr("Time") << tr("Priority") << tr("Phone Number")
<< tr("Subject");
setHorizontalHeaderLabels(labels);
}
La fonction retranslateUi() met jour les en-ttes de colonnes avec les textes nouvellement traduits, compltant ainsi la partie traduction du code dun widget crit la main. Pour
ce qui est des widgets et des botes de dialogue dvelopps avec Qt Designer, loutil uic
gnre automatiquement une fonction similaire retranslateUi(). Celle-ci est automatiquement appele en rponse aux vnements LanguageChange.
Chapitre 17
Internationalisation
403
Ici, nous mentionnons deux fichiers de traduction : un pour lallemand et un pour le franais.
Ces fichiers seront crs lors de la premire excution de lupdate et seront chargs chacune
de ses excutions ultrieures.
Ces fichiers possdent normalement une extension .ts. Ils se trouvent dans un format XML
simple et ne sont pas aussi compacts que les fichiers binaires .qm compris par QTranslator.
La tche de conversion des fichiers .ts lisibles par lhomme en fichiers .qm efficaces pour la
machine revient lrelease. Pour les esprits curieux, .ts et .qm signifient fichier "translation
source" et fichier "Qt message", respectivement.
En supposant que nous nous trouvions dans le rpertoire contenant le code source de lapplication Spreadsheet, nous pouvons excuter lupdate sur spreadsheet partir de la ligne de
commande comme suit :
lupdate -verbose spreadsheet.pro
Loption verbose invite lupdate fournir plus de commentaires que dhabitude. Voici la
sortie attendue :
Updating
Found
Updating
Found
spreadsheet_de.ts...
98 source texts (98 new and 0 already existing)
spreadsheet_fr.ts...
98 source texts (98 new and 0 already existing)
Chaque chane qui apparat dans un appel de tr() dans le code source de lapplication est
stocke dans les fichiers .ts, avec une traduction vide. Les chanes qui apparaissent dans les
fichiers .ui de lapplication sont galement incluses.
Loutil lupdate suppose par dfaut que les arguments de tr() sont des chanes Latin-1. Si tel
nest pas le cas, nous devons ajouter une entre CODECFORTR au fichier .pro. Par exemple :
CODECFORTR
= EUC-JP
404
dentrer une traduction pour llment source activ. Dans la zone infrieure droite apparat
une liste de suggestions fournies automatiquement par Qt Linguist.
Lorsque nous disposons dun fichier .ts traduit, nous devons le convertir en un fichier
binaire .qm afin quil puisse tre exploit par QTranslator. Pour effectuer cette opration
depuis Qt Linguist, cliquez sur File/Release. Nous commenons gnralement par
traduire quelques chanes et nous excutons lapplication avec le fichier .qm pour nous assurer
que tout fonctionne correctement. (Voir Figure 17.2)
Figure 17.2
Qt Linguist en action
Si vous souhaitez gnrer les fichiers .qm pour tous les fichiers .ts, vous pouvez utiliser
loutil lrelease comme suit :
lrelease -verbose spreadsheet.pro
En supposant que vous avez traduit dix-neuf chanes en franais et cliqu sur lindicateur Done
pour dix-sept dentre elles, lrelease produit la sortie suivante :
Updating spreadsheet_de.qm...
Generated 0 translations (0 finished and 0 unfinished)
Ignored 98 untranslated source texts
Updating spreadsheet_fr.qm...
Generated 19 translations (17 finished and 2 unfinished)
Ignored 79 untranslated source texts
Les chanes non traduites sont prsentes dans les langues initiales lors de lexcution de
lapplication. Lindicateur Done est ignor par lrelease. Il peut tre utilis par les traducteurs
pour identifier les traductions termines et celles qui ont encore besoin dtre rvises.
Chapitre 17
Internationalisation
405
Lorsque nous modifions le code source de lapplication, les fichiers de traduction deviennent
obsoltes. La solution consiste excuter de nouveau lupdate, fournir les traductions pour
les nouvelles chanes et regnrer les fichiers .qm. Certaines quipes de dveloppement
considrent quil faut rgulirement excuter lupdate, alors que dautres prfrent attendre
que lapplication soit prte tre commercialise.
Les outils lupdate et Qt Linguist sont relativement intelligents. Les traductions qui ne sont
plus utilises sont conserves dans les fichiers .ts au cas o elles seraient ncessaires dans des
versions ultrieures. Lors de la mise jour des fichiers .ts, lupdate utilise un algorithme de
fusion intelligent qui permet aux traducteurs dconomiser un temps considrable dans le cas
de texte identique ou similaire utilis dans des contextes diffrents.
Pour plus dinformations concernant Qt Linguist, lupdate et lrelease, rfrez-vous au
manuel Qt Linguist ladresse http://doc.trolltech.com/4.1/linguist-manual.html. Ce
manuel contient une tude dtaille de linterface utilisateur de Qt Linguist et un didactitiel
pour les programmeurs.
18
Environnement multithread
Au sommaire de ce chapitre
Crer des threads
Synchroniser des threads
Communiquer avec le thread principal
Utiliser les classes Qt dans les threads
secondaires
408
les threads. Puis nous vous expliquerons comment communiquer avec le thread principal
depuis les threads secondaires pendant que la boucle dvnement est en cours dexcution.
Nous conclurons enfin par une tude des classes Qt susceptibles ou non dtre utilises dans
des threads secondaires.
Lenvironnement multithread est un sujet vaste, auquel de nombreux livres sont exclusivement
consacrs. Ici, nous supposons que vous matrisez les bases de la programmation multithread.
Nous insisterons davantage sur la faon de dvelopper des applications Qt multithread plutt
que sur le thme du dcoupage en threads lui-mme.
La classe Thread hrite de QThread et rimplmente la fonction run(). Elle fournit deux
fonctions supplmentaires : setMessage() et stop().
La variable stopped est dclare volatile car il est possible dy accder depuis plusieurs
threads et nous souhaitons garantir une lecture de sa version actualise chaque fois quelle est
ncessaire. Si nous omettons le mot-cl volatile, le compilateur peut optimiser laccs la
variable, ce qui risque daboutir des rsultats incorrects.
Thread::Thread()
{
stopped = false;
}
Chapitre 18
Environnement multithread
409
void Thread::run()
{
while (!stopped)
cerr << qPrintable(messageStr);
stopped = false;
cerr << endl;
}
La fonction run() est appele pour lancer lexcution du thread. Tant que la variable stopped
est dfinie en false, la fonction continue afficher le message donn sur la console. Le thread
se termine lorsque la fonction run() rend le contrle.
void Thread::stop()
{
stopped = true;
}
La fonction stop() dfinit la variable stopped en true, indiquant ainsi run() dinterrompre laffichage du texte sur la console. Cette fonction peut tre appele tout moment depuis
un thread quelconque. Dans le cadre de cet exemple, nous supposons que laffectation un
bool est une opration atomique. Cest une supposition raisonnable, si lon considre quun bool
ne peut avoir que deux tats. Nous verrons ultrieurement comment utiliser QMutex pour
garantir latomicit de laffectation une variable.
QThread fournit une fonction terminate() qui met fin lexcution dun thread pendant
quil est toujours en cours dexcution. Lemploi de terminate() nest pas conseill, car elle
peut mettre fin au thread tout moment et ne donne ce dernier aucune chance de lancer les
oprations de rcupration de la mmoire. Il est toujours prfrable de faire appel une variable
stopped et une fonction stop() comme cest le cas ici.
Figure 18.1
Lapplication Threads
A prsent, nous allons voir comment utiliser la classe Thread dans une petite application Qt
qui utilise deux threads, A et B, en plus du thread principal.
class ThreadDialog: public QDialog
{
Q_OBJECT
public:
ThreadDialog(QWidget *parent = 0);
protected:
void closeEvent(QCloseEvent *event);
private slots:
void startOrStopThreadA();
void startOrStopThreadB();
410
private:
Thread threadA;
Thread threadB;
QPushButton *threadAButton;
QPushButton *threadBButton;
QPushButton *quitButton;
};
La classe ThreadDialog dclare deux variables de type Thread et quelques boutons pour
fournir une interface utilisateur de base.
ThreadDialog::ThreadDialog(QWidget *parent)
: QDialog(parent)
{
threadA.setMessage("A");
threadB.setMessage("B");
threadAButton = new QPushButton(tr("Start A"));
threadBButton = new QPushButton(tr("Start B"));
quitButton = new QPushButton(tr("Quit"));
quitButton->setDefault(true);
connect(threadAButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadA()));
connect(threadBButton, SIGNAL(clicked()),
this, SLOT(startOrStopThreadB()));
Dans le constructeur, nous appelons setMessage() pour amener le premier et le second thread
afficher de faon rpte des A et des B, respectivement.
void ThreadDialog::startOrStopThreadA()
{
if (threadA.isRunning()) {
threadA.stop();
threadAButton->setText(tr("Start A"));
} else {
threadA.start();
threadAButton->setText(tr("Stop A"));
}
}
Chapitre 18
Environnement multithread
411
threadB.stop();
threadBButton->setText(tr("Start B"));
} else {
threadB.start();
threadBButton->setText(tr("Stop B"));
}
}
Si lutilisateur clique sur Quit ou ferme la fentre, nous arrtons tous les threads en cours
dexcution et attendons leur fin (en excutant QThread::wait()) avant dappeler QCloseEvent::accept(). Ce processus assure une fermeture correcte de lapplication, bien quil ne
prsente pas vraiment dimportance dans le cadre de cet exemple.
Si vous excutez lapplication et cliquez sur Start A, la console se remplit de A. Si vous
cliquez sur Start B, elle se remplit maintenant de squences alternatives de A et de B. Cliquez
sur Stop A, et elle naffiche alors que les B.
412
QMutex mutex;
};
Les oprations de verrouillage et de dverrouillage dun mutex dans les fonctions complexes,
ou celles utilisant des exceptions C++, peuvent tre sujettes erreur. Qt offre la classe utilitaire
QMutexLocker pour simplifier la gestion des mutex. Le constructeur de QMutexLocker
accepte QMutex comme argument et le verrouille. Le destructeur de QMutexLocker dverrouille le mutex. Nous pourrions, par exemple, rcrire les fonctions run() et stop() comme
suit :
void Thread::run()
{
forever {
{
QMutexLocker locker(&mutex);
if (stopped) {
stopped = false;
break;
}
}
cerr << qPrintable(messageStr);
}
cerr << endl;
}
Chapitre 18
Environnement multithread
413
void Thread::stop()
{
QMutexLocker locker(&mutex);
stopped = true;
}
Le problme de lemploi des mutex est que seul un thread peut accder la mme variable la
fois. Dans les programmes avec de nombreux threads essayant de lire la mme variable simultanment (sans la modifier), le mutex risque de compromettre srieusement les performances.
Dans ces situations, nous pouvons faire appel QReadWriteLock, une classe de synchronisation
qui autorise des accs simultans en lecture seulement sans altrer les performances.
Dans la classe Thread, il serait illogique de remplacer QMutex par QReadWriteLock pour protger la variable stopped, car un thread au maximum tentera de lire la variable un moment donn.
Un exemple plus appropri impliquerait un ou plusieurs threads de lecture accdant des
donnes partages et un ou plusieurs threads dcriture modifiant les donnes. Par exemple :
MyData data;
QReadWriteLock lock;
void ReaderThread::run()
{
...
lock.lockForRead();
access_data_without_modifying_it(&data);
lock.unlock();
...
}
void WriterThread::run()
{
...
lock.lockForWrite();
modify_data(&data);
lock.unlock();
...
}
Pour des raisons de commodit, il est possible dutiliser les classes QReadLocker et QWriteLocker pour verrouiller et dverrouiller un QReadWriteLock.
QSemaphore est une autre gnralisation des mutex, mais contrairement au verrouillage en
lecture/criture, les smaphores peuvent tre employs pour protger un certain nombre de
ressources identiques. Les deux extraits de code suivants montrent la correspondance entre
QSemaphore et QMutex:
QSemaphore semaphore(1); QMutex mutex;
semaphore.acquire(); mutex.lock();
semaphore.release(); mutex.unlock();
414
Le thread producteur crit les donnes dans la mmoire tampon jusqu ce quil atteigne la fin,
puis recommence au dbut, crasant les donnes existantes. Le thread consommateur lit les
donnes lors de sa gnration. La Figure 18.2 illustre ce processus, avec une mmoire tampon
minuscule de 16 octets.
Figure 18.2
Le modle producteur/
consommateur
usedSpace(5)
0
A A G C C T
consommateur
freeSpace(11)
8
10 11 12 13 14 15
A C
producteur
Chapitre 18
Environnement multithread
415
"libres" et les convertit en octets "utiliss".Le smaphore usedSpace est initialis avec la
valeur 0 pour sassurer que le consommateur ne lit pas des donnes incohrentes au dmarrage.
Dans cet exemple, chaque octet est considr comme une ressource. Dans une application du
monde rel, nous agirions probablement sur des units de taille plus importante (64 ou 256 octets
la fois, par exemple) pour rduire la surcharge associe lemploi des smaphores.
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
freeSpace.acquire();
buffer[i % BufferSize] = "ACGT"[uint(rand()) % 4];
usedSpace.release();
}
Dans le producteur, chaque itration dbute par lacquisition dun octet "libre".Si la mmoire
tampon est remplie de donnes nayant pas encore t lues par le consommateur, lappel
acquire() bloquera le processus jusqu ce que le consommateur ait commenc consommer les donnes. Une fois loctet acquis, nous le remplissons de donnes alatoires (A, C,
G ou T) et le librons avec le statut "utilis", de sorte quil puisse tre lu par le thread
consommateur.
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
usedSpace.acquire();
cerr << buffer[i % BufferSize];
freeSpace.release();
}
cerr << endl;
}
416
Enfin, nous lanons les threads producteur et consommateur dans main(). Le producteur
convertit alors de lespace "libre" en espace "utilis", puis le consommateur le reconvertit en
espace "libre".
Lorsque nous excutons le programme, il crit une squence alatoire de 100 000 A, C, G
et T sur la console et se termine. Pour vraiment comprendre ce qui se passe, nous pouvons
dsactiver lcriture de la sortie et crire la place un P chaque fois que le producteur
gnre un octet et c chaque fois que le consommateur en lit un. Pour simplifier les choses,
nous pouvons utiliser des valeurs plus petites pour DataSize et BufferSize.
Voici, par exemple, une excution possible avec un DataSize de 10 et un BufferSize de 4:
"PcPcPcPcPcPcPcPcPcPc". Dans ce cas, le consommateur lit les octets ds quils sont gnrs
par le producteur. Les deux threads sont excuts la mme vitesse. Il est galement possible
que le producteur remplisse toute la mmoire tampon avant que le consommateur ne
commence sa lecture : "PPPPccccPPPPccccPPcc". Il existe de nombreuses autres possibilits.
Les smaphores offrent une grande libert au planificateur de thread spcifique au systme, qui
peut tudier le comportement des threads et choisir une stratgie de planification approprie.
Une approche diffrente au problme de synchronisation dun producteur et dun consommateur consiste employer QWaitCondition et QMutex. Un QWaitCondition permet un
thread den rveiller dautres lorsque les conditions requises sont remplies. Cette approche
autorise un contrle plus prcis quavec les mutex seuls. Pour illustrer le fonctionnement de ce
processus, nous allons reprendre lexemple producteur/consommateur en dclarant des conditions
dattente.
const int DataSize = 100000;
const int BufferSize = 4096;
char buffer[BufferSize];
QWaitCondition bufferIsNotFull;
QWaitCondition bufferIsNotEmpty;
QMutex mutex;
int usedSpace = 0;
En plus du tampon, nous dclarons deux QWaitConditions, un QMutex et une variable qui
stocke le nombre doctets "utiliss" dans la mmoire tampon.
void Producer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
buffer[i % BufferSize] = "ACGT"[uint(rand()) % 4];
++usedSpace;
bufferIsNotEmpty.wakeAll();
mutex.unlock();
}
}
Chapitre 18
Environnement multithread
417
Dans le producteur, nous commenons par vrifier si la mmoire tampon est remplie. Si tel est
le cas, nous attendons la condition "buffer is not full".Une fois cette condition remplie, nous
crivons un octet dans la mmoire tampon, nous incrmentons usedSpace, puis nous rveillons
tout thread en attente de la condition "buffer is not full".
Nous faisons appel un mutex pour protger tous les accs la variable usedSpace. La fonction
QWaitCondition::wait() peut recevoir un mutex verrouill comme premier argument, quelle
dverrouille avant de bloquer le thread en cours puis verrouille avant de rendre le contrle.
Dans cet exemple, nous aurions pu remplacer la boucle while
while (usedSpace == BufferSize)
bufferIsNotFull.wait(&mutex);
Ce processus risque dtre perturb si nous autorisons plusieurs threads producteurs, car un
autre producteur pourrait se saisir du mutex immdiatement aprs lappel de wait() et annuler
la condition "buffer is not full".
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
while (usedSpace == 0)
bufferIsNotEmpty.wait(&mutex);
cerr << buffer[i % BufferSize];
--usedSpace;
bufferIsNotFull.wakeAll();
mutex.unlock();
}
cerr << endl;
}
418
Cette classe est gnralement utilise pour les caches. En ayant un cache distinct dans les diffrents threads, nous vitons la surcharge du verrouillage, dverrouillage et dattente dun mutex.
Par exemple :
QThreadStorage<QHash<int, double> *> cache;
void insertIntoCache(int id, double value)
{
if (!cache.hasLocalData())
cache.setLocalData(new QHash<int, double>);
cache.localData()->insert(id, value);
}
void removeFromCache(int id)
{
if (cache.hasLocalData())
cache.localData()->remove(id);
}
La variable cache contient un pointeur vers un QMap<int,double> par thread. Lorsque nous
utilisons le cache pour la premire fois dans un thread particulier, hasLocalData() retourne
false et nous crons lobjet QHash<int,double>.
QThreadStorage<T> peut galement tre utilis pour les variables dtat derreur globales
(exactement comme errno) afin de sassurer que les modifications apportes un thread
naffectent pas les autres.
Chapitre 18
Environnement multithread
419
Cependant, lorsque nous connectons des objets "actifs" dans des threads diffrents, le mcanisme
devient asynchrone. (Ce comportement peut tre modifi par le biais dun cinquime paramtre facultatif de QObject::connect().) Ces connexions sont implmentes larrire-plan en
publiant un vnement. Le slot est alors appel par la boucle dvnement du thread dans
laquelle se trouve lobjet destinataire. Par dfaut, un QObject se situe dans le thread o il a t
cr. Ceci peut tre chang tout moment en appelant QObject::moveToThread().
Figure 18.3
Lapplication Image Pro
Pour illustrer le fonctionnement des connexions signal/slot entre threads, nous allons tudier le
code de lapplication Image Pro, une application de retouche dimages de base qui permet
lutilisateur de faire pivoter, de redimensionner et de changer la prcision des couleurs dune
image. Lapplication utilise un thread secondaire pour pouvoir raliser des oprations sur les images
sans verrouiller la boucle dvnement. La diffrence est significative lors du traitement de trs
grosses images. Le thread secondaire possde une liste de tches, ou "transactions", accomplir
et envoie les vnements la fentre principale pour indiquer la progression.
ImageWindow::ImageWindow()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Dark);
imageLabel->setAutoFillBackground(true);
imageLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
setCentralWidget(imageLabel);
createActions();
createMenus();
statusBar()->showMessage(tr("Ready"), 2000);
connect(&thread, SIGNAL(transactionStarted(const QString &)),
statusBar(), SLOT(showMessage(const QString &)));
connect(&thread, SIGNAL(finished()),
420
this, SLOT(allTransactionsDone()));
setCurrentFile("");
}
Le slot allTransactionsDone() est appel quand la file dattente de transactions de TransactionThread se vide.
Passons maintenant la classe TransactionThread:
class TransactionThread: public QThread
{
Q_OBJECT
public:
void addTransaction(Transaction *transact);
void setImage(const QImage &image);
QImage image();
Chapitre 18
Environnement multithread
421
signals:
void transactionStarted(const QString &message);
protected:
void run();
private:
QMutex mutex;
QImage currentImage;
QQueue<Transaction *> transactions;
};
La classe TransactionThread conserve une liste des transactions grer et les excute les
unes aprs les autres larrire-plan.
void TransactionThread::addTransaction(Transaction *transact)
{
QMutexLocker locker(&mutex);
transactions.enqueue(transact);
if (!isRunning())
start();
}
Les fonctions setImage() et image() permettent au thread principal de dfinir limage sur
laquelle les transactions doivent tre effectues et de rcuprer limage rsultante une fois
toutes les transactions termines. Nous protgeons de nouveau les accs une variable membre
en utilisant un mutex.
void TransactionThread::run()
{
Transaction *transact;
422
forever {
mutex.lock();
if (transactions.isEmpty()) {
mutex.unlock();
break;
}
QImage oldImage = currentImage;
transact = transactions.dequeue();
mutex.unlock();
emit transactionStarted(transact->message());
QImage newImage = transact->apply(oldImage);
delete transact;
mutex.lock();
currentImage = newImage;
mutex.unlock();
}
}
La fonction run() parcourt la file dattente des transactions et les excute chacune tour tour
en appelant apply() sur celles-ci.
Quand une transaction est lance, nous mettons le signal transactionStarted() avec un
message afficher dans la barre dtat de lapplication. Une fois le traitement de toutes les
transactions termin, la fonction run() se termine et QThread met le signal finished().
class Transaction
{
public:
virtual ~Transaction() { }
virtual QImage apply(const QImage &image) = 0;
virtual QString message() = 0;
};
La classe Transaction est une classe de base abstraite pour les oprations susceptibles dtre
effectues par lutilisateur sur une image. Le destructeur virtuel est ncessaire, car nous devons
supprimer les instances des sous-classes Transaction par le biais dun pointeur Transaction.
(Si nous lomettons, certains compilateurs mettent un avertissement.) Transaction possde
trois sous-classes concrtes : FlipTransaction, ResizeTransaction et ConvertDepthTransaction. Nous ntudierons que FlipTransaction, les deux autres classes tant similaires.
class FlipTransaction: public Transaction
{
public:
FlipTransaction(Qt::Orientation orientation);
QImage apply(const QImage &image);
QString message();
Chapitre 18
Environnement multithread
423
private:
Qt::Orientation orientation;
};
La fonction apply() appelle QImage::mirrored() sur le QImage reu en tant que paramtre
et retourne le QImage rsultant.
QString FlipTransaction::message()
{
if (orientation == Qt::Horizontal) {
return QObject::tr("Flipping image horizontally...");
} else {
return QObject::tr("Flipping image vertically...");
}
}
La fonction message() retourne le message afficher dans la barre dtat pendant la progression de lopration. Cette fonction est appele dans TransactionThread::run() lors de
lmission du signal transactionStarted().
424
rentrantes sont marques comme telles dans la documentation de rfrence Qt. En rgle gnrale, toute classe C++ ne faisant pas rfrence une donne globale ou partage est rentrante.
QObject est rentrant, mais trois contraintes doivent tre prises en considration :
Les QObject enfants doivent tre crs dans le thread de leur parent.
Ceci signifie que les objets crs dans un thread secondaire ne doivent jamais tre crs
avec le mme objet QThread que leur parent, car cet objet a t cr dans un autre thread
(soit dans le thread principal, soit dans un thread secondaire diffrent).
Nous devons supprimer tous les QObject crs dans un thread secondaire avant denvisager la
suppression de lobjet QThread correspondant.
Pour ce faire, nous pouvons crer les objets sur la pile dans QThread::run().
Les QObject doivent tre supprims dans le thread les ayant crs.
Si nous devons supprimer un QObject situ dans un thread diffrent, nous devons appeler
la fonction QObject::deleteLater(), qui publie un vnement "deferred delete"
(suppression diffre).
Les sous-classes QObject non GUI, telles que QTimer, QProcess et les classes de rseau, sont
rentrantes. Nous pouvons les utiliser dans tout thread, ceci tant que ce thread possde une
boucle dvnement. En ce qui concerne les threads secondaires, la boucle dvnement est
dmarre en appelant QThread::exec() ou par des fonctions utilitaires telles que QProcess::waitForFinished() et QAbstractSocket::waitForDisconnected().
QWidget et ses sous-classes ne sont pas rentrantes, cause des limites hrites des bibliothques sur lesquelles repose la prise en charge GUI de Qt. Par consquent, nous ne pouvons
pas appeler directement des fonctions depuis un thread secondaire sur un widget. Pour changer, par exemple, le texte dun QLabel depuis un thread secondaire, nous pouvons mettre un
signal connect QLabel::setText() ou appeler QMetaObject::invokeMethod() depuis
ce thread. Par exemple :
void MyThread::run()
{
...
QMetaObject::invokeMethod(label, SLOT(setText(const QString &)),
Q_ARG(QString, "Hello"));
...
}
De nombreuses classes non GUI de Qt, dont QImage, QString et les classes conteneur, utilisent le partage implicite comme technique doptimisation. Bien que cette optimisation ne rende
gnralement pas une classe rentrante, ceci ne pose pas de problme dans Qt. En effet, des
instructions de langage assembleur atomiques sont employes pour implmenter un dcompte
de rfrences thread-safe, qui rend les classes implicitement partages de Qt rentrantes.
Le module QtSql de Qt peut galement tre employ dans les applications multithread, mais il
prsente des restrictions qui varient dune base de donnes une autre. Vous trouverez plus de
dtails ladresse http://doc.trolltech.com/4.1/sql-driver.html.
19
Crer des plug-in
Au sommaire de ce chapitre
Dvelopper Qt avec les plug-in
Crer des applications capables
de grer les plug-in
Ecrire des plug-in pour des
applications
+= -ldb_cxx
+= /usr/local/BerkeleyDB.4.2/include
Lalternative consiste charger dynamiquement la bibliothque quand elle est requise, puis
rsoudre les symboles que nous souhaitons utiliser. Qt fournit la classe QLibrary pour y
parvenir dune manire indpendante de la plate-forme. A partir dun nom de bibliothque,
426
QAccessibleBridgePlugin
QAccessibleBridge
QAccessiblePlugin
QAccessibleInterface
QIconEnginePlugin
QIconEngine
QImageIOPlugin
QImageIOHandler
QInputContextPlugin
QInputContext
QPictureFormatPlugin
N/A
QSqlDriverPlugin
QSqlDriver
QStylePlugin
QStyle
QTextCodecPlugin
QTextCodec
Figure 19.1
Les classes gestionnaire et de plug-in de Qt ( lexception de Qtopia Core)
Chapitre 19
427
Pour illustrer ceci, nous allons implmenter un plug-in capable de lire des fichiers curseur
(fichiers .cur) Windows monochromes. Ces fichiers peuvent contenir plusieurs images du
mme curseur des tailles diffrentes. Une fois le plug-in de curseur cr et install, Qt sera en
mesure de lire les fichiers .cur et daccder aux curseurs individuels (par le biais de QImage,
QImageReader ou QMovie, par exemple). Il pourra galement convertir les curseurs en tout
autre format de fichier dimage, tel que BMP, JPEG et PNG. Le plug-in pourrait aussi tre
dploy avec les applications Qt, car elles contrlent automatiquement les emplacements standard
des plug-in Qt et chargent ceux quelles trouvent.
Les nouveaux conteneurs de plug-in de format dimage doivent sous-classer QImageIOPlugin
et rimplmenter quelques fonctions virtuelles :
class CursorPlugin: public QImageIOPlugin
{
public:
QStringList keys() const;
Capabilities capabilities(QIODevice *device,
const QByteArray &format) const;
QImageIOHandler *create(QIODevice *device,
const QByteArray &format) const;
};
La fonction keys() retourne une liste de formats dimage pris en charge par le plug-in. Le
paramtre format des fonctions capabilities() et create() est suppos avoir une valeur
qui provient de cette liste.
QStringList CursorPlugin::keys() const
{
return QStringList() << "cur";
}
Notre plug-in ne prend en charge quun seul format dimage. Il retourne donc une liste avec un
nom unique. Idalement, le nom doit tre lextension de fichier utilise pour ce format. Dans le
cas de formats avec plusieurs extensions (telles que .jpg et .jpeg pour JPEG), nous pouvons
retourner une liste avec plusieurs entres, chacune correspondant une extension.
QImageIOPlugin::Capabilities
CursorPlugin::capabilities(QIODevice *device,
const QByteArray &format) const
{
if (format == "cur")
return CanRead;
if (format.isEmpty()) {
CursorHandler handler;
handler.setDevice(device);
if (handler.canRead())
return CanRead;
}
return 0;
}
428
Quand un fichier curseur est ouvert (par QImageReader, par exemple), la fonction create()
du conteneur de plug-in est appele avec le pointeur de priphrique et avec le format "cur".
Nous crons une instance de CursorHandler et la configurons avec le priphrique et le format
spcifis. Lappelant prend la proprit du gestionnaire et le supprimera quand il ne sera plus
utile. Si plusieurs fichiers doivent tre lus, un nouveau gestionnaire peut tre cr pour chacun.
Q_EXPORT_PLUGIN2(cursorplugin, CursorPlugin)
A la fin du fichier .cpp, nous utilisons la macro Q_EXPORT_PLUGIN2() pour sassurer que le
plug-in est reconnu par Qt. Le premier paramtre est un nom arbitraire que nous souhaitons
attribuer au plug-in. Le second paramtre est le nom de classe du plug-in.
Il est facile de sous-classer QImageIOPlugin. Le travail vritable du plug-in est effectu dans
le gestionnaire. Les gestionnaires de format dimage doivent sous-classer QImageIOHandler
et rimplmenter ses fonctions publiques en totalit ou en partie. Commenons par len-tte :
class CursorHandler: public QImageIOHandler
{
public:
CursorHandler();
bool canRead() const;
bool read(QImage *image);
bool jumpToNextImage();
int currentImageNumber() const;
int imageCount() const;
private:
enum State { BeforeHeader, BeforeImage, AfterLastImage, Error };
void readHeaderIfNecessary() const;
Chapitre 19
429
Les signatures de toutes les fonctions publiques sont fixes. Nous avons omis quelques fonctions dont la rimplmentation est inutile pour un gestionnaire en lecture seulement, en particulier write(). Les variables de membre sont dclares avec le mot-cl mutable, car elles
sont modifies lintrieur des fonctions const.
CursorHandler::CursorHandler()
{
state = BeforeHeader;
currentImageNo = 0;
numImages = 0;
}
Lors de la construction du gestionnaire, nous commenons par dfinir son tat. Nous appliquons au premier curseur le numro dimage du curseur en cours. Ici, numImage tant dfini en
0, il est vident que nous navons pas encore dimage.
bool CursorHandler::canRead() const
{
if (state == BeforeHeader) {
return device()->peek(4) == QByteArray("\0\0\2\0", 4);
} else {
return state!= Error;
}
}
La fonction canRead() peut tre appele tout moment pour dterminer si le gestionnaire
dimages peut lire des donnes supplmentaires depuis le priphrique. Si la fonction est appele avant que nous ayons lu toute donne et alors que nous nous trouvons toujours dans ltat
BeforeHeader, nous vrifions la signature particulire qui identifie les fichiers curseur
Windows. Lappel QIODevice::peek() lit les quatre premiers octets sans changer le pointeur
du fichier du priphrique. Si canRead() est appele ultrieurement, nous retournons true
quand aucune erreur ne sest produite.
int CursorHandler::currentImageNumber() const
{
return currentImageNo;
}
Cette fonction retourne le numro du curseur au niveau duquel le pointeur de fichier du priphrique est positionn. Une fois le gestionnaire construit, il est possible pour lutilisateur
dappeler toutes ses fonctions publiques, dans un ordre quelconque. Cest un problme potentiel,
car nous devons partir du principe que nous ne pouvons effectuer quune lecture squentielle.
430
Nous devons donc lire len-tte de fichier au moins une fois avant tout autre lment. Nous
rsolvons le problme en appelant la fonction readHeaderIfNecessary().
int CursorHandler::imageCount() const
{
readHeaderIfNecessary();
return numImages;
}
Cette fonction retourne le nombre dimages dans le fichier. Pour un fichier valide o aucune
erreur de lecture ne sest produite, elle retournera un dcompte dau moins 1 (voir
Figure 19.2).
Header
quint16 reserved (0x0000)
Image 1
Image n
quint32 size
quint32
quint32 width
quint32
quint32 height
quint16 planes
quint16 bits per pixel
quint32
quint16
Figure 19.2
Le format
de fichier .cur
quint32 compression
-- 20) + 8 bytes color table
(size +
La fonction read() lit les donnes de toute image, en commenant lemplacement du pointeur de priphrique courant. Nous pouvons lire limage suivante si len-tte du fichier est lu
avec succs, ou aprs quune image ait t lue et que le pointeur de priphrique se dplace au
dbut dune autre image.
quint32 size;
quint32 width;
quint32 height;
quint16 numPlanes;
quint16bitsPerPixel;
quint32 compression;
QDataStream in(device());
Chapitre 19
431
in.setByteOrder(QDataStream::LittleEndian);
in >> size;
if (size!= 40) {
enterErrorState();
return false;
}
in >> width >> height >> numPlanes >> bitsPerPixel >> compression;
height /= 2;
if (numPlanes!= 1 || bitsPerPixel!= 1 || compression!= 0) {
enterErrorState();
return false;
}
in.skipRawData((size - 20) + 8);
Nous crons un QDataStream pour lire le priphrique. Nous devons dfinir un ordre doctets
correspondant celui mentionn par la spcification de format de fichier .cur. Il nest pas
besoin de dfinir un numro de version QDataStream, car le format de nombres entiers et de
nombres virgule flottante ne varie pas entre les versions de flux de donnes. Nous lisons
ensuite les divers lments de len-tte du curseur. Nous passons les parties inutiles de len-tte
et la table des couleurs de 8 octets au moyen de QDataStream::skipRawData(). Nous
devons revoir toutes les caractristiques du format par exemple en diminuant de moiti la
hauteur car le format .cur fournit une hauteur qui est deux fois suprieure celle de limage
vritable. Les valeurs bitsPerPixel et compression sont toujours de 1 et de 0 dans un
fichier .cur monochrome. Si nous rencontrons des problmes, nous pouvons appeler enterErrorState() et retourner false.
QBitArray xorBitmap = readBitmap(width, height, in);
QBitArray andBitmap = readBitmap(width, height, in);
if (in.status()!= QDataStream::Ok) {
enterErrorState();
return false;
}
Les lments suivants du fichier sont deux bitmaps, lune tant un masque XOR et lautre un
masque AND. Nous les lisons dans les QBitArray plutt que dans les QBitmap. Un QBitmap
est une classe conue pour tre dessine lcran, mais ici, nous avons besoin dun tableau
doctets ordinaire. Lorsque nous en avons fini avec la lecture du fichier, nous vrifions ltat du
QDataStream. Cette opration fonctionne correctement, car si un QDataStream entre dans un
tat derreur, il y reste et ne peut retourner que des zros. Si, par exemple, la lecture choue au
niveau du premier tableau doctets, la tentative de lecture du deuxime rsulte en un QBitArray vide.
*image = QImage(width, height, QImage::Format_ARGB32);
for (int i = 0; i < int(height); ++i) {
for (int j = 0; j < int(width); ++j) {
QRgb color;
432
Nous construisons un nouveau QImage de la taille correcte et dfinissons image de faon quil
pointe ver celui-ci. Puis nous parcourons chaque pixel des tableaux doctets XOR et AND et nous
les convertissons en spcifications de couleur ARGB de 32 bits. Les tableaux doctets AND et
XOR sont utiliss comme prsent dans le tableau suivant pour obtenir la couleur de chaque
pixel du curseur :
AND
XOR
Rsultat
Pixel transparent
Pixel blanc
Pixel noir
Les pixels noirs, blancs et transparents ne prsentent pas de problme, mais il nexiste aucun
moyen dobtenir un pixel darrire-plan invers au moyen dune spcification de couleur
ARGB sans connatre la couleur du pixel darrire-plan initial. En remplacement, nous utilisons
une couleur grise semi-transparente (0x7F7F7F7F).
++currentImageNo;
if (currentImageNo == numImages)
state = AfterLastImage;
return true;
}
Une fois la lecture de limage termine, nous mettons jour le numro de limage courante
ainsi que ltat si nous avons atteint la dernire image. A la fin de la fonction, le priphrique
sera positionn au niveau de limage suivante ou la fin du fichier.
Chapitre 19
433
bool CursorHandler::jumpToNextImage()
{
QImage image;
return read(&image);
}
La fonction jumpToNextImage() permet de passer une image. Pour des raisons de simplicit,
nous appelons simplement read() et ignorons le QImage rsultant. Une implmentation plus
efficace consisterait utiliser linformation stocke dans len-tte du fichier .cur pour passer
directement loffset appropri dans le fichier.
void CursorHandler::readHeaderIfNecessary() const
{
if (state!= BeforeHeader)
return;
quint16 reserved;
quint16 type;
quint16 count;
QDataStream in(device());
in.setByteOrder(QDataStream::LittleEndian);
in >> reserved >> type >> count;
in.skipRawData(16 * count);
if (in.status()!= QDataStream::Ok || reserved!= 0
|| type!= 2 || count == 0) {
enterErrorState();
return;
}
state = BeforeImage;
currentImageNo = 0;
numImages = int(count);
}
434
Si une erreur se produit, nous supposons quil nexiste pas dimages valides et dfinissons
ltat en Error. Ltat du gestionnaire ne peut alors plus tre modifi.
QBitArray CursorHandler::readBitmap(int width, int height,
QDataStream &in) const
{
QBitArray bitmap(width * height);
quint8 byte;
quint32 word;
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
if ((j % 32) == 0) {
word = 0;
for (int k = 0; k < 4; ++k) {
in >> byte;
word = (word << 8) | byte;
}
}
bitmap.setBit(((height - i - 1) * width) + j,
word & 0x80000000);
word <<= 1;
}
}
return bitmap;
}
La fonction readBitmap() permet de lire les masques AND et XOR dun curseur. Ces masques
ont deux fonctions inhabituelles. En premier lieu, ils stockent les lignes de bas en haut, au lieu de
lapproche courante de haut en bas. En second lieu, lordre des donnes apparat invers par rapport
celui utilis tout autre emplacement dans les fichiers .cur. Nous devons donc inverser la
coordonne y dans lappel de setBit(), et nous lisons les valeurs bit par bit dans le masque.
Nous terminons ici limplmentation du plug-in de format dimage CursorHandler. Les
plug-in correspondant dautres formats dimage doivent suivre le mme schma, bien que
certains puissent implmenter une part plus importante de lAPI QImageIOHandler, en particulier les fonctions utilises pour crire les images. Les plug-in dautres types, tels que les
codecs de texte ou les pilotes de base de donnes, suivent le mme schma : un conteneur de
plug-in fournit une API gnrique que les applications peuvent utiliser, ainsi quun gestionnaire fournissant les fonctionnalits sous-jacentes.
Le fichier .pro diffre entre les plug-in et les applications. Nous terminerons avec ceci :
TEMPLATE
CONFIG
HEADERS
SOURCES
DESTDIR
= lib
+= plugin
= cursorhandler.h \
cursorplugin.h
= cursorhandler.cpp \
cursorplugin.cpp
= $(QTDIR)/plugins/imageformats
Chapitre 19
435
Par dfaut, les fichiers .pro font appel au modle app, mais ici, nous devons utiliser le modle
lib car un plug-in est une bibliothque, et non une application autonome. La ligne CONFIG
indique Qt que la bibliothque nest pas classique, mais quil sagit dune bibliothque de
plug-in. DESTDIR mentionne le rpertoire o doit tre plac le plug-in. Tous les plug-in Qt
doivent se trouver dans le sous-rpertoire plugins appropri o a t install Qt, et comme
notre plug-in fournit un nouveau format dimage, nous le plaons dans le sous-rpertoire
plugins/imageformats. La liste des noms de rpertoire et des types de plug-in est fournie
ladresse http://doc.trolltech.com/4.1/plugins-howto.html. Pour cet exemple, nous supposons
que la variable denvironnement QTDIR est dfinie dans le rpertoire o est install Qt.
Les plug-in crs pour Qt en mode release et en mode debug sont diffrents. Ainsi, si les deux
versions de Qt sont installes, il est conseill de prciser laquelle doit tre utilise dans le
fichier .pro, en ajoutant par exemple la ligne suivante :
CONFIG
+= release
Les applications qui utilisent les plug-in Qt doivent tre dployes avec ceux quelles sont
senses exploiter. Les plug-in Qt doivent tre placs dans des sous-rpertoires spcifiques
(imageformats, par exemple, pour les formats dimage). Les applications Qt recherchent les
plug-in dans le sous-rpertoire plugins situ dans le dossier o rside leur excutable. Ainsi,
pour les plug-in dimage elles recherchent dans application_dir/plugins/imageformats. Si nous souhaitons dployer les plug-in Qt dans un rpertoire diffrent, les directions de
recherche des plug-in peuvent tre multiplies en utilisant QCoreApplication::addLibraryPath().
436
public:
virtual ~TextArtInterface() { }
virtual QStringList effects() const = 0;
virtual QPixmap applyEffect(const QString &effect,
const QString &text,
const QFont &font, const QSize &size,
const QPen &pen,
const QBrush &brush) = 0;
};
Q_DECLARE_INTERFACE(TextArtInterface,
"com.software-inc.TextArt.TextArtInterface/1.0")
Figure 19.3
Lapplication Text Art
Une classe dinterface dclare normalement un destructeur virtuel, une fonction virtuelle qui
retourne un QStringList ainsi quune ou plusieurs autres fonctions virtuelles. Le destructeur
est avant tout destin satisfaire le compilateur, qui, sans sa prsence se plaindrait du manque
de destructeur virtuel dans une classe possdant des fonctions virtuelles. Dans cet exemple, la
fonction effects() retourne une liste deffets de texte fournis par le plug-in. Nous pouvons
envisager cette liste comme une liste de cls. A chaque fois que nous appelons une autre fonction, nous transmettons lune de ces cls en tant que premier argument, permettant ainsi
limplmentation deffets multiples dans un plug-in.
Nous faisons finalement appel la macro Q_DECLARE_INTERFACE() pour associer un identificateur linterface. Cet identificateur comprend gnralement quatre composants : un nom de
domaine inverse spcifiant le crateur de linterface, le nom de lapplication, le nom de linterface et un numro de version. Ds que nous modifions linterface (en ajoutant une fonction
virtuelle ou en changeant la signature dune fonction existante), nous devons incrmenter le
numro de version. Dans le cas contraire, lapplication risque de tomber brusquement en panne
en essayant daccder un plug-in obsolte.
Chapitre 19
437
Lapplication est implmente dans une classe nomme TextArtDialog. Nous ntudierons
ici que le code prsentant un intrt. Commenons par le constructeur :
TextArtDialog::TextArtDialog(const QString &text, QWidget *parent)
: QDialog(parent)
{
listWidget = new QListWidget;
listWidget->setViewMode(QListWidget::IconMode);
listWidget->setMovement(QListWidget::Static);
listWidget->setIconSize(QSize(260, 80));
loadPlugins();
populateListWidget(text);
Dans loadPlugins(), nous tentons de charger tous les fichiers du rpertoire plugins de
lapplication. (Sous Windows, lexcutable de lapplication rside gnralement dans un sousrpertoire debug ou release. Nous remontons donc dun niveau. Sous Mac OS X, nous
prenons en compte la structure de rpertoire du paquet.)
438
Si le fichier que nous essayons de charger est un plug-in qui utilise la mme version de Qt que
lapplication, QPluginLoader::instance() retournera un QObject* qui pointe vers un
plug-in Qt. Nous excutons qobject_cast<T>() pour vrifier si le plug-in implmente le
TextArtInterface. A chaque fois que la conversion est russie, nous ajoutons linterface la
liste dinterfaces (de type QList<TextArtInterface*>) de TextArtDialog.
Certaines applications peuvent vouloir charger plusieurs interfaces diffrentes, auquel cas le
code permettant dobtenir les interfaces doit tre similaire celui-ci :
QObject *plugin = loader.instance();
if (TextArtInterface *i = qobject_cast<TextArtInterface *>(plugin))
textArtInterfaces.append(i);
if (BorderArtInterface *i = qobject_cast<BorderArtInterface *>(plugin))
borderArtInterfaces.append(i);
if (TextureInterface *i = qobject_cast<TextureInterface *>(plugin))
textureInterfaces.append(i);
Les mmes plug-in peuvent tre convertis avec succs en plusieurs pointeurs dinterface, car il
est possible pour les plug-in de fournir plusieurs interfaces avec lhritage multiple.
void TextArtDialog::populateListWidget(const QString &text)
{
QSize iconSize = listWidget->iconSize();
QPen pen(QColor("darkseagreen"));
QLinearGradient gradient(0, 0, iconSize.width() / 2,
iconSize.height() / 2);
gradient.setColorAt(0.0, QColor("darkolivegreen"));
gradient.setColorAt(0.8, QColor("darkgreen"));
gradient.setColorAt(1.0, QColor("lightgreen"));
QFont font("Helvetica", iconSize.height(), QFont::Bold);
foreach (TextArtInterface *interface, interfaces) {
foreach (QString effect, interface->effects()) {
QListWidgetItem *item = new QListWidgetItem(effect,
listWidget);
QPixmap pixmap = interface->applyEffect(effect, text, font,
iconSize, pen,
gradient);
item->setData(Qt::DecorationRole, pixmap);
}
}
listWidget->setCurrentRow(0);
}
Chapitre 19
439
fourni par chaque interface, un nouveau QListWidgetItem est cr avec le nom de leffet
quil reprsente, et un QPixmap est gnr au moyen de applyEffect().
Dans cette section, nous avons vu comment charger des plug-in en appelant loadPlugins()
dans le constructeur, et comment les exploiter dans populateListWidget(). Le code dtermine lgamment combien il existe de plug-in fournissant TextArtInterface. En outre, des
plug-in supplmentaires peuvent tre ajouts ultrieurement : chaque nouvelle ouverture de
lapplication, celle-ci charge tous les plug-in fournissant les interfaces souhaites. Il est ainsi
possible de dvelopper la fonctionnalit de lapplication sans pour autant la modifier.
La fonction effects() retourne une liste deffets de texte pris en charge par le plug-in. Celuici supportant trois effets, nous retournons simplement une liste contenant le nom de chacun
deux.
440
Nous souhaitons nous assurer que le texte donn sadaptera si possible la taille spcifie.
Cest la raison pour laquelle nous utilisons les mtriques de la police afin de dterminer si texte
est trop gros pour sadapter. Si tel est le cas, nous excutons une boucle o nous rduisons la taille
en points jusqu parvenir une dimension correcte, ou jusqu parvenir 9 points, notre
taille minimale fixe.
QPixmap pixmap(size);
QPainter painter(&pixmap);
painter.setFont(myFont);
painter.setPen(pen);
painter.setBrush(brush);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::TextAntialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
painter.eraseRect(pixmap.rect());
Nous crons un pixmap de la taille requise et un painter pour peindre sur le pixmap. Nous dfinissons aussi quelques conseils de rendu pour assurer les meilleurs rsultats possible. Lappel
eraseRect() efface le pixmap avec la couleur darrire-plan.
if (effect == "Plain") {
painter.setPen(Qt::NoPen);
} else if (effect == "Outline") {
QPen pen(Qt::black);
pen.setWidthF(2.5);
painter.setPen(pen);
} else if (effect == "Shadow") {
QPainterPath path;
painter.setBrush(Qt::darkGray);
path.addText(((size.width() - metrics.width(text)) / 2) + 3,
(size.height() - metrics.descent()) + 3, myFont,
text);
painter.drawPath(path);
painter.setBrush(brush);
}
Chapitre 19
441
Pour leffet "Plain" aucun relief nest requis. En ce qui concerne leffet "Outline", nous ignorons le stylet initial et en crons un de couleur noire avec une largeur de 2,5 pixels. Quant
leffet "Shadow", il nous faut dabord dessiner lombre, de faon pouvoir inscrire le texte
par-dessus.
QPainterPath path;
path.addText((size.width() - metrics.width(text)) / 2,
size.height() - metrics.descent(), myFont, text);
painter.drawPath(path);
return pixmap;
}
A prsent, nous disposons du stylet et de lensemble des pinceaux adapts chaque effet de
texte. Nous sommes maintenant prts afficher le texte. Ce dernier est centr horizontalement
et suffisamment loign du bas du pixmap pour laisser de la place aux hampes infrieures.
Q_EXPORT_PLUGIN2(basiceffectsplugin, BasicEffectsPlugin)
=
+=
=
=
=
lib
plugin
../textart/textartinterface.h \<RC>basiceffectsplugin.h
basiceffectsplugin.cpp
../textart/plugins
Si ce chapitre vous a donn lenvie den savoir plus sur les plug-in, vous pouvez tudier
lexemple plus avanc Plug & Paint fourni avec Qt. Lapplication prend en charge trois interfaces diffrentes et inclut une bote de dialogue Plugin Information rpertoriant les plug-in et
interfaces disponibles pour lapplication.
20
Fonctionnalits
spcifiques
la plate-forme
Au sommaire de ce chapitre
Construire une interface avec les API
natives
ActiveX sous Windows
Prendre en charge la gestion de session
de X11
444
de crer des applications qui se comportent comme des serveurs ActiveX. Dans la dernire
section, nous expliquerons comment amener des applications Qt cooprer avec le gestionnaire de session sous X11.
Pour complter les fonctionnalits prsentes ci-dessus, Trolltech offre plusieurs solutions Qt
spcifiques la plate-forme, notamment les frameworks de migration Qt/Motif et Qt/MFC qui
simplifient la migration des applications Motif/Xt et MFC vers Qt. Une extension analogue est
fournie pour les applications Tcl/Tk par froglogic, et un convertisseur de ressource Microsoft
Windows est disponible auprs de Klarlvdalens Datakonsult. Consultez les pages Web suivantes
pour obtenir des dtails complmentaires :
http://www.trolltech.com/products/solutions/catalog/
http://www.froglogic.com/tq/
http://www.kdab.net/knut/
Pour tout ce qui concerne le dveloppement intgr, Trolltech propose la plate-forme dapplication Qtopia. Cette plate-forme est dtaille au Chapitre 21.
Sur toutes les plates-formes, QWidget fournit une fonction winId() qui renvoie lidentifiant
de la fentre ou le handle (descripteur). QWidget fournit aussi une fonction statique nomme
find() qui renvoie le QWidget avec un ID de fentre particulier. Nous pouvons transmettre
cet identifiant aux fonctions dAPI natives pour obtenir des effets spcifiques la plate-forme.
Chapitre 20
445
Par exemple, le code suivant sappuie sur winId() pour dplacer la barre de titre dune fentre
doutil vers la gauche laide des fonctions natives de Mac OS X :
#ifdef Q_WS_MAC
ChangeWindowAttributes(HIViewGetWindow(HIViewRef(toolWin.winId())),
kWindowSideTitlebarAttribute,
kWindowNoAttributes);
#endif
Sous X11, voici comment nous pourrions modifier une proprit de fentre :
#ifdef Q_WS_X11
Atom atom = XInternAtom(QX11Info::display(), "MY_PROPERTY", False);
long data = 1;
XChangeProperty(QX11Info::display(), window->winId(), atom, atom,
32, PropModeReplace,
reinterpret_cast<uchar *>(&data), 1);
#endif
Les directives #ifdef et #endif qui encadrent le code spcifique la plate-forme garantissent
la compilation de lapplication sur les autres plates-formes.
Pour une application uniquement destine Windows, voici un exemple dutilisation des
appels de GDI pour afficher un widget Qt :
void GdiControl::paintEvent(QPaintEvent * /* event */)
{
RECT rect;
GetClientRect(winId(), &rect);
HDC hdc = GetDC(winId());
FillRect(hdc, &rect, HBRUSH(COLOR_WINDOW + 1));
SetTextAlign(hdc, TA_CENTER | TA_BASELINE);
TextOutW(hdc, width() / 2, height() / 2, text.utf16(), text.size());
ReleaseDC(winId(), hdc);
}
446
Le mlange des appels de QPainter avec les appels GDI comme dans ce code conduit quelquefois des rsultats tranges, en particulier quand les appels de QPainter se produisent
aprs les appels GDI. En effet, QPainter sappuie dans ce cas sur certaines hypothses
concernant ltat de la couche de dessin sous-jacente.
Qt dfinit un des quatre symboles de systme de fentre suivant : Q_WS_WIN, Q_WS_ X11,
Q_WS_MAC, et Q_WS_QWS (Qtopia). Nous devons inclure au moins un en-tte Qt pour tre en
mesure de les utiliser dans les applications. Qt fournit aussi des symboles de prprocesseur
destins identifier le systme dexploitation :
c Q_OS_AIX
c Q_OS_HPUX
c Q_OS_OPENBSD
c Q_OS_SOLARIS
c Q_OS_BSD4
c Q_OS_HURD
c Q_OS_OS2EMX
c Q_OS_ULTRIX
c Q_OS_BSDI
c Q_OS_IRIX
c Q_OS_OSF
c Q_OS_UNIXWARE
c Q_OS_CYGWIN
c Q_OS_LINUX
c Q_OS_QNX6
c Q_OS_WIN32
c Q_OS_DGUX
c Q_OS_LYNX
c Q_OS_QNX
c Q_OS_WIN64
c Q_OS_DYNIX
c Q_OS_MAC
c Q_OS_RELIANT
c Q_OS_FREEBSD
c Q_OS_NETBSD
c Q_OS_SCO
Nous pouvons supposer quau moins un de ces symboles sera dfini. Pour des raisons pratiques,
Qt dfinit aussi Q_OS_WIN ds que Win32 ou Win64 est dtect, et Q_OS_UNIX lorsquun
systme dexploitation de type UNIX (y compris Linux et Mac OS X), est reconnu. Au moment de
lexcution, nous pouvons contrler QSysInfo::WindowsVersion ou QSysInfo::MacintoshVersion pour identifier les diffrentes versions de Windows (2000, XP, etc.) ou de Mac OS X
(10.2, 10.3, etc.).
Des macros de compilateur viennent complter les macros du systme de fentrage et du
systme dexploitation. Par exemple, Q_CC_MSVC est dfinie si le compilateur est Microsoft
Visual C++. Elles peuvent se rvler trs pratiques pour rsoudre les erreurs de compilateur.
Plusieurs classes de Qt lies linterface graphique fournissent des fonctions spcifiques la
plate-forme qui renvoient des handles de bas niveau vers lobjet sous-jacent. Ces descripteurs
sont numrs en Figure 20.2.
Sous X11, QPixmap::x11Info() et QWidget::x11Info() renvoient un objet QX11Info qui
fournit divers pointeurs ou handles, tels que display(), screen(), colormap() et visual().
Nous pouvons les utiliser pour configurer un contexte graphique X11 sur un QPixmap ou un
QWidget, par exemple.
Les applications Qt qui doivent collaborer avec dautres toolkits ou bibliothques ont souvent
besoin daccder aux vnements de bas niveau sous X11, MSGs sous Windows, EventRef
sous Mac OS X, QWSEvents sous Qtopia) avant quils ne soient convertis en QEvents. Nous
pouvons procder en drivant QApplication et en rimplmentant le filtre dvnement
spcifique la plate-forme appropri, cest--dire x11EventFilter(), winEventFilter(),
Chapitre 20
Mac OS X
ATSFontFormatRef
QFont::handle()
CGImageRef
QPixmap::macCGHandle()
GWorldPtr
QPixmap::macQDAlphaHandle()
GWorldPtr
QPixmap::macQDHandle()
RgnHandle
QRegion::handle()
HIViewRef
QWidget::winId()
Windows
HCURSOR
QCursor::handle()
HDC
QPaintEngine::getDC()
HDC
QPrintEngine::getPrinterDC()
HFONT
QFont::handle()
HPALETTE
QColormap::hPal()
HRGN
QRegion::handle()
HWND
QWidget::winId()
X11
Cursor
QCursor::handle()
Font
QFont::handle()
Picture
QPixmap::x11PictureHandle()
Picture
QWidget::x11PictureHandle()
Pixmap
QPixmap::handle()
QX11Info
QPixmap::x11Info()
QX11Info
QWidget::x11Info()
Region
QRegion::handle()
Screen
QCursor::x11Screen()
SmcConn
QSessionManager::handle()
Window
QWidget::handle()
Window
QWidget::winId()
Figure 20.2
Fonctions spcifiques la plate-forme pour accder aux handles de bas niveau
447
448
Chapitre 20
449
slots:
onPlayStateChange(int oldState, int newState);
onReadyStateChange(ReadyStateConstants readyState);
onPositionChange(double oldPos, double newPos);
sliderValueChanged(int newValue);
openFile();
private:
QAxWidget *wmp;
QToolButton *openButton;
QToolButton *playPauseButton;
QToolButton *stopButton;
QSlider *seekSlider;
QString fileFilters;
int updateTimer;
};
Dans le constructeur, nous commenons par crer un objet QAxWidget afin dencapsuler le
contrle ActiveX Windows Media Player. Le module QAxContainer se compose de trois classes : QAxObject englobe un objet COM, QAxWidget englobe un contrle ActiveX, et
QAxBase implmente la fonctionnalit COM pour QAxObject et QAxWidget.
Nous appelons setControl() sur QAxWidget avec lID de la classe du contrle Windows
Media Player 6.4. Une instance du composant requis va ainsi tre cre. A partir de l, toutes
450
les proprits, vnements, et mthodes du contrle ActiveX vont tre disponibles sous la
forme de proprits, signaux et slots Qt, par le biais de lobjet QAxWidget.
Figure 20.4
Schma dhritage du
module QAxContainer
QObject
QAxBase
QAxObject
QWidget
QAxWidget
Les types de donnes COM sont automatiquement convertis vers les types Qt correspondants,
comme illustr en Figure 20.5. Par exemple, un paramtre dentre de type VARIANT_BOOL
prend le type bool, et un paramtre de sortie de type VARIANT_BOOL devient un type bool &.
Si le type obtenu est une classe Qt (QString, QDateTime, etc.), le paramtre dentre est une
rfrence const (par exemple, const QString &).
Types COM
Types Qt
VARIANT_BOOL
bool
int
uint
double
CY
qlonglong, qulonglong
BSTR
QString
DATE
OLE_COLOR
QColor
SAFEARRAY(VARIANT)
QList<QVariant>
SAFEARRAY(BSTR)
QStringList
SAFEARRAY(BYTE)
QByteArray
VARIANT
QVariant
IFontDisp *
QFont
IPictureDisp *
QPixmap
Figure 20.5
Relations entre types COM et types Qt
Chapitre 20
451
Pour obtenir la liste de toutes les proprits, signaux et slots disponibles pour un QAxObject
ou un QAxWidget avec leurs types de donnes Qt, appelez QAxBase::generateDocumentation() ou faites appel loutil de ligne de commande de Qt dumpdoc, que vous trouverez
dans le rpertoire tools\activeqt\dumpdoc de Qt.
Examinons maintenant le constructeur de PlayerWindow:
wmp->setProperty("ShowControls", false);
wmp->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
connect(wmp, SIGNAL(PlayStateChange(int, int)),
this, SLOT(onPlayStateChange(int, int)));
connect(wmp, SIGNAL(ReadyStateChange(ReadyStateConstants)),
this, SLOT(onReadyStateChange(ReadyStateConstants)));
connect(wmp, SIGNAL(PositionChange(double, double)),
this, SLOT(onPositionChange(double, double)));
La fin du constructeur PlayerWindow suit le modle habituel, sauf que nous connectons quelques
signaux Qt aux slots fournis par lobjet COM (Play(), Pause(), et Stop()). Les boutons
tant analogues, nous ne prsentons ici que limplmentation du bouton Stop.
Il est temps daborder la fonction timerEvent():
void PlayerWindow::timerEvent(QTimerEvent *event)
{
if (event->timerId() == updateTimer) {
double curPos = wmp->property("CurrentPosition").toDouble();
onPositionChange(-1, curPos);
} else {
QWidget::timerEvent(event);
}
}
452
La fonction timerEvent() est appele intervalles rguliers pendant la diffusion dun clip.
Nous lemployons pour faire avancer le curseur. Pour ce faire, nous appelons property() sur
le contrle ActiveX afin dobtenir la valeur de la proprit CurrentPosition en tant que
QVariant et nous appelons toDouble() pour le convertir en double. Nous appelons alors
onPositionChange() pour raliser la mise jour.
La suite du code prsente peu dintrt parce quelle ne concerne pas directement ActiveX et
quelle ne comporte rien qui nait dj t abord. Le code complet est inclus sur le CD-ROM.
Dans le fichier .pro, nous devons introduire lentre suivante pour tablir une liaison avec le
module QAxContainer :
CONFIG
+= qaxcontainer
Lorsquon gre des objets COM, on a souvent besoin dappeler directement une mthode
COM (et pas seulement de la connecter un signal Qt). La mthode la plus simple de procder
est dinvoquer QAxBase::dynamicCall() avec le nom et la signature de la mthode en
premier paramtre et les arguments de cette dernire comme paramtres supplmentaires.
Saisissez par exemple :
wmp->dynamicCall("TitlePlay(uint)", 6);
Si nous dsirons appeler des mthodes dont la liste de paramtres contient des types de
donnes non pris en charge, nous pouvons utiliser QAxBase::queryInterface()pour rcuprer linterface COM et appeler directement la mthode. Comme toujours avec COM, nous
devons appeler Release() quand nous navons plus besoin de linterface. Sil devient trs
frquent davoir appeler de telles mthodes, nous pouvons driver QAxObject ou QAxWidget
et fournir des fonctions membres qui encapsulent les appels de linterface COM. Vous ne devez
pas ignorer que les sous classes QAxObject et QAxWidget ne peuvent pas dfinir leurs
propres proprits, signaux, ou slots.
Nous allons maintenant dtailler le module QAxServer. Il nous permet de transformer un
programme Qt standard en serveur ActiveX. Le serveur peut tre une bibliothque partage ou
une application autonome. Les serveurs dfinis en tant que bibliothques partages sont
souvent appels serveurs in-process (in-process signifie "qui sexcute dans lespace de traitement
dun client") ; les applications autonomes sont nommes serveurs hors processus.
Chapitre 20
453
Notre premier exemple de QAxServer est un serveur in-process qui fournit un widget affichant
une bille qui se balance de gauche droite. Nous allons aussi expliquer comment intgrer le
widget dans Internet Explorer.
Voici le dbut de la dfinition de classe du widget AxBouncer:
class AxBouncer: public QWidget, public QAxBindable
{
Q_OBJECT
Q_ENUMS(SpeedValue)
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(SpeedValue speed READ speed WRITE setSpeed)
Q_PROPERTY(int radius READ radius WRITE setRadius)
Q_PROPERTY(bool running READ isRunning)
En prsence dhritage multiple impliquant une classe drive de QObject, nous devons toujours
positionner la classe drive de QObject en premier de sorte que moc puisque la rcuprer.
Nous dclarons trois proprits de lecture-criture et une proprit en lecture seulement. La
macro Q_ ENUMS() est ncessaire pour signaler moc que le type SpeedValue est un type
enum. Lnumration est dclare dans la section publique de la classe :
public:
enum SpeedValue { Slow, Normal, Fast };
454
Les sections protges et prives de la classe sont identiques celles que nous aurions sil
sagissait dun widget Qt standard.
AxBouncer::AxBouncer(QWidget *parent)
: QWidget(parent)
{
ballColor = Qt::blue;
ballSpeed = Normal;
ballRadius = 15;
myTimerId = 0;
x = 20;
delta = 2;
}
Chapitre 20
455
La fonction setColor() dfinie la valeur de la proprit color. Elle appelle update() pour
redessiner le widget.
Les appels de requestPropertyChange() et propertyChanged() sont plus inhabituels.
Ces fonctions drivent de QAxBindable et devraient normalement tre appeles chaque fois
que nous modifions une proprit. requestPropertyChange() demande au client la permission de changer une proprit, et renvoie true si le client accepte. La fonction propertyChanged() signale au client que la proprit a chang.
Les mthodes daccs de proprit setSpeed() et setRadius() suivent galement ce modle,
ainsi que les slots start() et stop(), puisquils modifient la valeur de la proprit running.
La fonction membre AxBouncer ne doit pas tre oublie :
QAxAggregated *AxBouncer::createAggregate()
{
return new ObjectSafetyImpl;
}
456
La fonction queryInterface() est une fonction virtuelle pure de QAxAggregated. Elle est
appele par lobjet COM contrleur afin doffrir laccs aux interfaces fournies par la sous classe
QAxAggregated. Nous devons renvoyer E_NOINTERFACE pour les interfaces que nous
nimplmentons pas et pour IUnknown.
HRESULT WINAPI ObjectSafetyImpl::GetInterfaceSafetyOptions(
REFIID /* riid */, DWORD *pdwSupportedOptions,
DWORD *pdwEnabledOptions)
{
*pdwSupportedOptions = INTERFACESAFE_FOR_UNTRUSTED_DATA
| INTERFACESAFE_FOR_UNTRUSTED_CALLER;
*pdwEnabledOptions = *pdwSupportedOptions;
return S_OK;
}
HRESULT WINAPI ObjectSafetyImpl::SetInterfaceSafetyOptions(
REFIID /* riid */, DWORD /* pdwSupportedOptions */,
DWORD /* pdwEnabledOptions */)
{
return S_OK;
}
Chapitre 20
457
= lib
+= dll qaxserver
= axbouncer.h \
objectsafetyimpl.h
= axbouncer.cpp \
main.cpp \
objectsafetyimpl.cpp
= qaxserver.rc
= qaxserver.def
Les fichiers qaxserver.rc et qaxserver.def auxquels il est fait rfrence dans le fichier
.pro sont des fichiers standards que lon peut copier dans le rpertoire src\activeqt\control
de Qt.
Le make?le ou le fichier de projet Visual C++ gnr par qmake contient les rgles qui rgissent lenregistrement du serveur dans le registre de Windows. Pour enregistrer le serveur sur
les machines utilisateur, nous pouvons faire appel loutil regsvr32 disponible sur tous les
systmes Windows.
Nous incluons alors le composant Bouncer dans une page HTML via la balise <object>:
<object id="AxBouncer"
classid="clsid:5e2461aa-a3e8-4f7a-8b04-307459a4c08c">
<b>The ActiveX control is not available. Make sure you have built and
registered the component server.</b>
</object>
458
Nous pouvons manipuler le widget laide de JavaScript ou VBScript comme nimporte quel
autre contrle ActiveX. Le fichier demo.html propos sur le CD prsente une page rudimentaire qui utilise le serveur ActiveX.
Notre dernier exemple est une application Carnet dadresse. Elle peut se comporter comme une
application standard Qt/Windows ou comme un serveur ActiveX hors processus. Cette dernire
option nous permet de crer le script de lapplication en Visual Basic, par exemple.
class AddressBook: public QMainWindow
{
Q_OBJECT
Q_PROPERTY(int count READ count)
Q_CLASSINFO("ClassID", "{588141ef-110d-4beb-95ab-ee6a478b576d}")
Q_CLASSINFO("InterfaceID", "{718780ec-b30c-4d88-83b3-79b3d9e78502}")
Q_CLASSINFO("ToSuperClass", "AddressBook")
public:
AddressBook(QWidget *parent = 0);
~AddressBook();
int count() const;
public slots:
ABItem *createEntry(const QString &contact);
ABItem *findEntry(const QString &contact) const;
ABItem *entryAt(int index) const;
private slots:
void addEntry();
void editEntry();
void deleteEntry();
private:
void createActions();
void createMenus();
QTreeWidget *treeWidget;
QMenu *fileMenu;
QMenu *editMenu;
QAction *exitAction;
QAction *addEntryAction;
QAction *editEntryAction;
QAction *deleteEntryAction;
};
Chapitre 20
459
460
La classe ABItem reprsente une entre dans le carnet dadresses. Elle hrite de QTreeWidgetItem pour pouvoir tre affiche dans un QTreeWidget et de QObject pour pouvoir tre
exporte sous forme dobjet COM.
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
if (!QAxFactory::isServer()) {
AddressBook addressBook;
addressBook.show();
return app.exec();
}
return app.exec();
}
Dans main(), nous vrifions si lapplication sexcute en autonome ou en tant que serveur.
Loption de ligne de commande -activex est reconnue par QApplication et excute
lapplication en tant que serveur. Si lapplication nest pas excute de cette faon, nous crons
le widget principal et nous laffichons comme nous le ferions normalement pour une application
Qt autonome.
En complment de -activex, les serveurs ActiveX comprennent les options de ligne de
commande suivantes :
-regserver enregistre le serveur dans le registre systme.
-unregserver annule lenregistrement du serveur dans le registre systme.
-dumpidl file inscrit lIDL (Interface Definition Langage) du serveur dans le fichier
spcifi.
Lorsque lapplication sexcute en tant que serveur, nous devons exporter les classes AddressBook et ABItem en tant que composants COM :
QAXFACTORY_BEGIN("{2b2b6f3e-86cf-4c49-9df5-80483b47f17b}",
"{8e827b25-148b-4307-ba7d-23f275244818}")
QAXCLASS(AddressBook)
QAXTYPE(ABItem)
QAXFACTORY_END()
Les macros prcdentes exportent un composant fabricant dobjets COM. Puisque nous devons
exporter deux types dobjets COM, nous ne pouvons pas nous contenter dexcuter
QAXFACTORY_DEFAULT() comme nous lavions fait dans lexemple prcdent.
Le premier argument de QAXFACTORY_BEGIN() correspond lID de bibliothque de types ;
le second est lID de lapplication. Entre QAXFACTORY_BEGIN() et QAXFACTORY_ END(),
nous spcifions toutes les classes pouvant tre instancies et tous les types de donnes qui ont
besoin dtre accessibles sous forme dobjets COM.
Voici le fichier .pro pour notre serveur ActiveX hors processus :
TEMPLATE
CONFIG
= app
+= qaxserver
Chapitre 20
HEADERS
SOURCES
FORMS
RC_FILE
461
= abitem.h \
addressbook.h \
editdialog.h
= abitem.cpp \
addressbook.cpp \
editdialog.cpp \
main.cpp
= editdialog.ui
= qaxserver.rc
Le fichier qaxserver.rc auquel il est fait rfrence dans le fichier .pro est un fichier standard
que lon peut copier dans le rpertoire src\activeqt\control de Qt.
Cherchez dans le rpertoire vb de lexemple un projet Visual Basic qui utilise le serveur de
carnet dadresses.
Nous en avons termin avec la prsentation du framework ActiveQt. La distribution de Qt
propose des exemples supplmentaires, et la documentation contient des informations concernant la faon de crer les modules QAxContainer et QAxServer et comment rsoudre les
problmes dinteroprabilit courants.
462
Windows 2000 et XP, et certains systmes Unix, proposent un autre mcanisme nomm Mise
en veille. Ds que lutilisateur active la mise en veille, le systme dexploitation sauvegarde
simplement la mmoire de lordinateur sur disque puis la recharge au moment de la ractivation.
Les applications ne sont pas sollicites et nont pas besoin dtre averties de lopration.
Lorsque lutilisateur demande larrt de lordinateur, nous pouvons prendre le contrle juste
avant lexcution de cette opration en rimplmentant QApplication::commitData().
Nous avons ainsi la possibilit denregistrer toute donne non sauvegarde et de dialoguer avec
lutilisateur si ncessaire. Cette partie de la gestion de session est prise en charge sur les deux
plates-formes X11 et Windows.
Notre tude de la gestion de session va seffectuer en analysant le code dune application
Tic-Tac-Toe compatible avec cette fonction. Commenons par examiner la fonction main():
int main(int argc, char *argv[])
{
Application app(argc, argv);
TicTacToe toe;
toe.setObjectName("toe");
app.setTicTacToe(&toe);
toe.show();
return app.exec();
}
Chapitre 20
463
La classe Application stocke un pointeur vers le widget TicTacToe dans une variable
prive.
void Application::saveState(QSessionManager &sessionManager)
{
QString fileName = ticTacToe->saveState();
QStringList discardCommand;
discardCommand << "rm" << fileName;
sessionManager.setDiscardCommand(discardCommand);
}
o sessionfile est le nom du fichier qui contient ltat enregistr pour la session, et rm est
la commande Unix standard pour supprimer des fichiers.
Le gestionnaire de session comporte aussi une commande de redmarrage. Il sagit de celle
que le gestionnaire excute pour redmarrer lapplication. Par dfaut, Qt fournit la commande
de redmarrage suivante :
appname -session id_key
464
La premire partie, appname, est drive de argv[0]. Le composant id correspond lidentifiant de session fourni par le gestionnaire de session; dont lunicit est garantie au sein de
plusieurs applications et de diffrentes excutions de la mme application. La partie key est
ajoute afin didentifier de faon unique lheure laquelle ltat a t enregistr. Pour diverses
raisons, le gestionnaire de session peut appeler plusieurs fois saveState() au cours dune
mme session, et les diffrents tats doivent pouvoir tre distingus.
Etant donn les limites des gestionnaires de session existants, nous devons nous assurer que le
rpertoire de lapplication se trouve dans la variable denvironnement PATH si nous voulons
que lapplication puisse redmarrer correctement. Si vous dsirez en particulier tester lexemple Tic-Tac-Toe, vous devez linstaller dans le rpertoire /usr/bin par exemple et linvoquer
en tapant tictactoe.
Pour des applications simples, comme Tic-Tac-Toe, nous pourrions enregistrer ltat sous
forme dargument de ligne de commande supplmentaire de la commande de redmarrage.
Par exemple :
tictactoe -state OX-XO-X-O
Ceci nous viterait davoir stocker les donnes dans un fichier puis fournir une commande
dannulation pour supprimer le fichier.
void Application::commitData(QSessionManager &sessionManager)
{
if (ticTacToe->gameInProgress()
&& sessionManager.allowsInteraction()) {
int r = QMessageBox::warning(ticTacToe, tr("Tic-Tac-Toe"),
tr("The game hasnt finished.\n"
"Do you really want to quit?"),
QMessageBox::Yes | QMessageBox::Default,
QMessageBox::No | QMessageBox::Escape);
if (r == QMessageBox::Yes) {
sessionManager.release();
} else {
sessionManager.cancel();
}
}
}
La fonction commitData() est appele quand lutilisateur ferme la session. Nous pouvons la
rimplmenter de sorte dafficher un message davertissement qui signale lutilisateur le
risque de perte de donnes. Limplmentation par dfaut ferme tous les widgets de niveau le plus
haut, ce qui donne le mme rsultat que lorsque lutilisateur ferme les fentres lune aprs
lautre en cliquant sur le bouton de fermeture de leur barre de titre. Au Chapitre 3, nous avons
vu comment rimplmenter closeEvent() pour dtecter cette situation et afficher un message.
Pour cet exemple, nous allons rimplmenter commitData() et afficher un message demandant lutilisateur de confirmer la fermeture de session si un jeu est en cours dexcution et si
le gestionnaire de session nous permet de dialoguer avec lutilisateur. Si lutilisateur clique sur
Chapitre 20
465
466
clearBoard();
if (qApp->isSessionRestored())
restoreState();
setWindowTitle(tr("Tic-Tac-Toe"));
}
Dans clearBoard(), nous effaons toutes les cellules et nous dfinissons turnNumber 0.
QString TicTacToe::saveState() const
{
QFile file(sessionFileName());
if (file.open(QIODevice::WriteOnly)) {
QTextStream out(&file);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column)
out << board[row][column];
}
}
return file.fileName();
}
Dans saveState(), nous enregistrons ltat du tableau sur disque. Le format est simple, avec
X pour les croix, O pour les ronds, et -+- pour les cellules vides.
QString TicTacToe::sessionFileName() const
{
return QDir::homePath() + "/.tictactoe_" + qApp->sessionId() + "_"
+ qApp->sessionKey();
}
Chapitre 20
467
if (file.open(QIODevice::ReadOnly)) {
QTextStream in(&file);
for (int row = 0; row < 3; ++row) {
for (int column = 0; column < 3; ++column) {
in >> board[row][column];
if (board[row][column]!= Empty)
++turnNumber;
}
}
}
update();
}
Dans restoreState(), nous chargeons le fichier qui correspond la session restaure et nous
remplissons le tableau avec ces informations. Nous dduisons la valeur de turnNumber
partir du nombre de X et O sur le tableau.
Dans le constructeur de TicTacToe, nous appelons restoreState() lorsque QApplication::isSessionRestored() renvoie true. Dans ce cas, sessionId() et sessionKey()
renvoient les mmes valeurs que lorsque ltat de lapplication tait enregistr, et sessionFileName() renvoie le nom de fichier pour cette session.
Les tests et le dbogage de la gestion de session peuvent tre pnibles, parce que vous devez
continuellement vous connecter puis vous dconnecter. Un moyen dviter ces oprations
consiste utiliser lutilitaire standard xsm fourni avec X11. Le premier appel de xsm ouvre
une fentre du gestionnaire de session et un terminal. Les applications dmarres dans ce
terminal vont toutes utiliser xsm comme gestionnaire de session plutt que celui du systme.
Nous pouvons alors nous servir de la fentre de xsm pour terminer, redmarrer, ou supprimer
une session, et voir si notre application se comporte normalement. Vous trouverez tous les
dtails de cette procdure ladresse http://doc.trolltech.com/4.1/session.html.
21
Programmation
embarque
Au sommaire de ce chapitre
Dmarrer avec Qtopia
Personnaliser Qtopia Core
Le dveloppement des logiciels destins sexcuter sur des priphriques mobiles tels
que les PDA et les tlphones portables prsente des difficults bien spcifiques parce que
les systmes embarqus possdent gnralement des processeurs plus lents, une capacit de mmoire permanente rduites (mmoire flash ou disque dur), moins de mmoire
et un cran plus petit que les ordinateurs de bureau.
Qtopia Core (prcdemment nomm Qt/Embedded) est une version de Qt optimise
pour le systme dexploitation Linux embarqu. Qtopia Core fournit les mmes outils et
la mme API que les versions de bureau de Qt (Qt/Windows, Qt/X 11 et Qt/Mac)
complts des classes et outils requis pour la programmation embarque. Par le biais
dune double licence, ce systme est disponible la fois pour le dveloppement open
source et le dveloppement commercial.
470
Qtopia Core peut sexcuter sur nimporte quel matriel quip de Linux (notamment les
architectures Intel x86, MIPS, ARM, StrongARM, Motorola 68000, et PowerPC). Il comporte
une mmoire dimage et prend en charge un compilateur C++. Contrairement Qt/X11, il na
pas besoin du systme XWindow ; en fait, il implmente son propre systme de fentrage
(QWS) ce qui permet doptimiser au maximum la gestion des mmoires. Pour rduire encore
ses besoins en mmoire, Qtopia Core peut tre recompil en excluant les fonctions non
utilises. Si les applications et composants exploits sur un priphrique sont connus par
avance, ils peuvent tre compils ensemble pour fournir un seul excutable avec des liens statiques
vers les bibliothques de Qtopia Core.
Qtopia Core bnficie galement de diverses fonctionnalits qui existent aussi dans les
versions bureau de Qt, notamment lusage extensif du partage de donnes implicite ("copie
lors de lcriture") pour ce qui concerne la technique doptimisation de la mmoire, la prise en
charge des styles de widget personnaliss via QStyle et un systme de disposition qui sadapte
pour optimiser lespace cran disponible.
Qtopia Core est au cur de loffre embarque de Trolltech, qui comprend galement Qtopia
Platform, Qtopia PDA, et Qtopia Phone. Ces versions fournissent les classes et applications
conues spcifiquement pour les priphriques portables et elles peuvent tre intgres avec
plusieurs machines virtuelles Java tiers.
Chapitre 21
Programmation embarque
471
serveur si vous spcifiez -qws sur sa ligne de commande ou si vous transmettez QApplication::GuiServer comme troisime paramtre du constructeur de QApplication.
Les applications client communiquent avec le serveur Qtopia Core par le biais de la mmoire
partage. En arrire plan, les clients se dessinent eux-mmes dans cette mmoire partage et
sont chargs dafficher leurs propres dcorations de fentre. Cela permet dobtenir un niveau
de communication minimum entre les clients et le serveur tout en proposant une interface utilisateur soigne. Les applications de Qtopia Core sappuient normalement sur QPainter pour
se dessiner elles-mmes mais elles peuvent aussi accder au matriel vido directement
laide de QDirectPainter.
Les clients ont la possibilit de communiquer via le protocole QCOP. Un client peut couter sur
un canal nomm en crant un objet QCopChannel et en se connectant son signal received().
Par exemple :
QCopChannel *channel = new QCopChannel("System", this);
connect(channel, SIGNAL(received(const QString &, const QByteArray &)),
this, SLOT(received(const QString &, const QByteArray &)));
Un message QCOP est constitu dun nom et ventuellement dun QByteArray. La fonction
QCopChannel::send() statique diffuse un message sur le canal. Par exemple :
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly);
out << QDateTime::currentDateTime();
QCopChannel::send("System", "clockSkew(QDateTime)", data);
Lexemple prcdent illustre un idiome connu : nous nous servons de QDataStream pour
coder les donnes, et pour garantir que le QByteArray sera correctement interprt par le
destinataire, nous joignons le format de donnes dans le nom du message comme sil sagissait
dune fonction C++.
Plusieurs variables denvironnement affectent les applications de Qtopia Core. Les plus importantes sont QWS_MOUSE_PROTO et QWS_KEYBOARD, qui spcifient le priphrique souris et le
type de clavier. Vous trouverez une liste complte des variables denvironnement sur la page
http://doc.trolltech.com/4.1/emb-envvars. html.
Si UNIX est la plate-forme de dveloppement, nous pouvons tester lapplication en utilisant la
mmoire dimage virtuelle de Qtopia (qvfb), une application X11 qui simule pixel par pixel
la mmoire dimage relle. Cela acclre considrablement le cycle de dveloppement. Pour
activer la prise en charge de la mmoire virtuelle dans Qtopia Core, vous transmettez loption
-qvfb au script configure. Noubliez pas que cette option nest pas destine un usage en
production. Lapplication de mmoire dimage virtuelle se trouve dans le rpertoire tools/
qvfb et peut tre invoque de la faon suivante :
qvfb -width 320 -height 480 -depth 32
Une autre option qui fonctionne sur la plupart des plates-formes consiste utiliser VNC
(Virtual Network Computing) pour excuter des applications distance. Pour activer la prise en
472
charge de VNC dans Qtopia Core, vous transmettez loption -qt-gfx-vnc configure.
Lancez ensuite vos applications Qtopia Core avec loption de ligne de commande -display
VNC:0 et excutez un client VNC qui pointe sur lhte sur lequel vos applications sexcutent.
La taille et la rsolution de lcran peuvent tre spcifis en dfinissant les variables denvironnement QWS_SIZE et QWS_DEPTH sur lhte qui excute les applications Qtopia Core (par
exemple, QWS_SIZE=320x480 et QWS_DEPTH=32).
Pour crer des configurations personnalises, nous pouvons fournir manuellement un fichier
qconfig-xxx.h et lutiliser comme sil sagissait dune configuration standard. Nous pourrions
aussi nous servir de loutil graphique qconfig, disponible dans le sous-rpertoire tools de Qt.
Qtopia Core propose les classes suivantes pour le dialogue avec les priphriques dentre et
de sortie et pour personnaliser laspect et le comportement du systme de fentrage :
Classe
QScreen
Pilotes dcran
QScreenDriverPlugin
QWSMouseHandler
Pilotes de souris
QMouseDriverPlugin
QWSKeyboardHandler
Pilotes de clavier
QKbdDriverPlugin
QWSInputMethod
Mthodes dentre
QDecoration
QDecorationPlugin
Chapitre 21
Programmation embarque
473
Vous obtenez la liste des pilotes prdfinis, des mthodes dentre, et des styles de dcoration
de fentre en excutant le script configure avec loption -help.
Vous spcifiez le pilote vido laide de loption de ligne de commande -display au dmarrage du serveur Qtopia Core, comme expliqu dans la section prcdente, ou en dfinissant la
variable denvironnement QWS_DISPLAY. Vous spcifiez le pilote de souris et le priphrique
associ via la variable denvironnement QWS_MOUSE_PROTO, dont la valeur suit la syntaxe
type : device, o type est un des pilotes pris en charge et device le chemin daccs au priphrique (par exemple, QWS_MOUSE_PRO-TO=IntelliMouse:/dev/mouse). Les claviers sont
grs dune faon analogue dans la variable denvironnement QWS_ KEYBOARD. Les mthodes
dentre et dcorations de fentre sont dfinies par programme dans le serveur en excutant
QWSServer::setCurrentInputMethod() et QApplication::qwsSetDecoration().
Les styles de dcoration de fentre sont dfinis indpendamment du style de widget, qui hrite
de QStyle. Il est tout fait possible, par exemple, de dfinir Windows comme style de dcoration de fentre et Plastique comme style de widget. Si vous en avez envie, les dcorations
peuvent tre rgles fentre par fentre.
La classe QWSServer fournit diverses fonctions pour personnaliser le systme de fentrage.
Les applications qui sexcutent en tant que serveurs Qtopia Core peuvent accder linstance
unique QWSServer via la variable globale qwsServer, initialise dans le constructeur de
QApplication.
Qtopia Core prend en charge les formats de police suivants : TrueType (TTF), PostScript Type
1, Bitmap Distribution Format (BDF), et Qt Pre-rendered Fonts (QPF).
QPF tant un format brut, il est plus rapide et gnralement plus compact que des formats
vectoriels tels que TTF et Type 1 si le besoin se limite une ou deux tailles diffrentes. Loutil
makeqpf permet de crer des fichiers QPF partir de fichiers TTF ou Type 1. Une autre solution
consiste excuter nos applications avec loption de ligne de commande -savefonts.
Au moment dcrire ces lignes, Trolltech dveloppe une couche supplmentaire au-dessus de
Qtopia Core pour rendre le dveloppement des applications embarques encore plus rapide et
efficace. Une prochaine dition de cet ouvrage devrait contenir de plus amples informations
ce propos.
Annexes
A
Installer Qt
A
Installer Qt
Au sommaire de ce chapitre
A propos des licences
Installer Qt/Windows
Installer Qt/Mac
Installer Qt/X11
Cette annexe explique comment installer Qt sur votre systme partir du CD qui
accompagne cet ouvrage. Ce CD comporte les ditions de Qt 4.1.1 pour Windows,
Mac OS X, et X11 (pour Linux et la plupart des versions dUnix). Elles intgrent toutes
SQLite, une base de donnes du domaine public, ainsi quun pilote SQLite. Les ditions
de Qt fournies sur le CD vous sont proposes pour des raisons pratiques. Si vous envisagez srieusement le dveloppement logiciel, il est prfrable de tlcharger la dernire
version de Qt partir de http://www.trolltech.com/download/ ou dacheter une
version commercialise.
Trolltech fournit aussi Qtopia Core pour la cration dapplications destines aux priphriques embarqus quips de Linux tels que les PDA et les tlphones portables. Si
vous envisagez de crer des applications embarques, vous pouvez obtenir Qtopia Core
depuis la page Web de tlchargement de Trolltech.
Les exemples dapplication tudis dans cet ouvrage se trouvent dans le rpertoire
examples du CD. De plus, Qt propose de nombreux petits exemples dapplication dans
le sous rpertoire examples.
478
Installer Qt/Windows
Lorsque vous insrez le CD sur un ordinateur quip de Windows, le programme dinstallation
devrait dmarrer automatiquement. Sinon, ouvrez lexplorateur de fichiers pour localiser le
rpertoire racine du CD et double-cliquez sur install.exe. (Il est possible que ce
programme se nomme install selon la faon dont votre systme est configur.)
Si vous disposez dj du compilateur MinGW C++ vous devez prciser le rpertoire dans
lequel il est install ; sinon cochez la case afin dinstaller aussi ce programme. La version GPL
de Qt fournie sur le CD ne fonctionnera pas avec Visual C++, vous devez donc absolument
installer MinGW si vous ne lavez pas encore fait. Le programme dinstallation vous propose
galement dinstaller les exemples qui accompagnent cet ouvrage. Les exemples standard de
Qt sont eux automatiquement installs ainsi que la documentation.
Si vous choisissez dinstaller le compilateur MinGW, vous constaterez certainement un dlai
entre la fin de linstallation de ce dernier et le dbut de linstallation de Qt.
Aprs linstallation, un nouveau dossier apparatra dans le menu Dmarrer intitul "Qt by
Trolltech v4.1.1 (OpenSource)". Ce dossier propose des raccourcis vers Qt Assistant et Qt
Designer, et un troisime nomm "Qt 4.1.1 Command Prompt" qui dmarre une fentre de
Annexe A
Installer Qt
479
console. Ds que vous ouvrez cette fentre, elle va dfinir les variables denvironnement pour
la compilation des programmes Qt avec MinGW. Cest dans cette fentre que vous allez excuter
qmake et make afin de gnrer vos applications Qt.
Installer Qt/Mac
Avant dinstaller Qt sur Mac OS X, vous devez dabord avoir install le jeu doutils Xcode
Tools dApple. Le CD (ou le DVD) contenant ces outils est gnralement fourni avec Mac OS
X ; vous pouvez aussi les tlcharger partir du site Apple Developer Connection, ladresse
http://developer.apple.com.
Si vous travaillez avec Mac OS X 10.4 (Tiger) et Xcode Tools 2.x (avec GCC 4.0.x), vous
pouvez excuter le programme dinstallation dcrit prcdemment. Si vous possdez une
version plus ancienne de Mac OS X, ou une version plus ancienne de GCC, vous devrez installer manuellement le package source. Celui-ci se nomme qt-mac-opensource4.1.1.tar.gz et il est stock dans le dossier mac sur le CD. Si vous linstallez, suivez les
instructions de la section suivante qui concernent linstallation de Qt sous X11.
Pour excuter le programme dinstallation, insrez le CD et double-cliquez sur le package
nomm Qt.mpkg. Le programme dinstallation, Installer.app, va se lancer et Qt sera
install avec les exemples standard, la documentation, et les exemples associs cet ouvrage.
Cette installation seffectue dans le rpertoire /Developer, et les exemples du livre sont enregistrs dans le rpertoire /Developer/Examples/Qt4Book.
Pour excuter des commandes telles que qmake et make, vous devrez avoir recours une fentre terminal, par exemple, Terminal.app dans /Applications/Utilities. Vous avez aussi
la possibilit de gnrer des projets Xcode laide de qmake. Pour gnrer, par exemple, un
projet Xcode pour lexemple hello, dmarrez une console telle que Terminal.app, placezvous sur le rpertoire /Developer/Examples/Qt4Book/chap01/hello, puis saisissez la
commande suivante :
qmake -spec macx-xcode hello.pro
Installer Qt/X11
Pour installer Qt sur son emplacement par dfaut sous X11, vous devez tre connect en tant
que root. Si vous navez pas ce niveau daccs, spcifiez largument -prefix de configure
pour indiquer un rpertoire dans lequel vous avez lautorisation dcrire.
1. Placez-vous sur un rpertoire temporaire. Par exemple :
cd /tmp
480
cp /cdrom/x11/qt-x11-opensource-src-4.1.1.tgz .
gunzip qt-x11-opensource-src-4.1.1.tgz
tar xvf qt-x11-opensource-src-4.1.1.tar
Vous pouvez excuter ./configure -help pour obtenir une liste des options de configuration.
4. Pour gnrer Qt, saisissez
make
Cette commande va crer la bibliothque et compiler toutes les dmos, les exemples et les
outils. Sur certains systmes, make se nomme gmake.
5. Pour installer Qt, saisissez
su -c "make install"
Si vous travaillez avec le shell csh ou tcsh, ajoutez la ligne suivante dans votre fichier
.login:
setenv PATH /usr/local/Trolltech/Qt-4.1.1/bin:$PATH
Si vous aviez prcis -prefix avec configure, utilisez le chemin indiqu plutt que le
chemin par dfaut de la ligne prcdente.
Annexe A
Installer Qt
481
Si votre compilateur ne prend pas en charge rpath, vous devez aussi tendre la variable
denvironnement LD_LIBRARY_PATH pour inclure /usr/local/ Trolltech/Qt-4.1.1/lib.
Ce nest pas ncessaire sous Linux avec GCC.
Qt est distribu avec une application de dmonstration, qtdemo, qui exploite de nombreuses
fonctionnalits de la bibliothque. Elle reprsente un bon point de dpart pour tester les
possibilits de Qt. Vous pouvez consulter la documentation de Qt soit en visitant le site
http://doc.trolltech.com, soit en excutant Qt Assistant, lapplication daide de Qt, que vous
obtenez en tapant assistant dans une fentre console.
B
Introduction au
langage C++ pour les
programmeurs Java et C#
Au sommaire de ce chapitre
Dmarrer avec C++
Principales diffrences de langage
La bibliothque C++ standard
Cette annexe fournit une courte introduction au langage C++ pour les dveloppeurs qui
connaissent dj Java ou C#. Nous supposons que vous matrisez les concepts de
lorient objet tels que lhritage et le polymorphisme, et que vous dsirez tudier le
C++. Pour ne pas transformer cet ouvrage en bible de 1500 pages qui couvrirait la totalit du langage C++, cette annexe vous livre uniquement lessentiel. Elle prsente les
techniques et connaissances de base requises pour comprendre les programmes prsents dans le reste de cet ouvrage, avec suffisamment dinformations pour dvelopper des
applications graphiques C++ multiplateforme laide de Qt.
Au moment dcrire ces lignes, C++ est la seule option raliste pour crire des applications graphiques orientes objet performantes, multiplateforme. Les dtracteurs de ce
langage soulignent gnralement que Java et C#, qui ont abandonn la compatibilit
avec le langage C, sont plus pratiques utiliser ; en fait, Bjarne Stroustrup, linventeur
484
du C++, signale dans son ouvrage The Design and Evolution of C++ que "dans le langage
C++, il y a beaucoup moins de difficults de langage viter".
Heureusement, lorsque nous programmons avec Qt, nous exploitons gnralement un sousensemble de C++ qui est trs proche du langage utopique envisag par Stroustrup, ce qui nous
laisse libre de nous concentrer sur le problme immdiat. De plus, Qt tend C++ sur plusieurs
points de vue, par le biais de son mcanisme "signal et slot" innovant, de sa prise en charge
dUnicode et de son mot-cl foreach.
Dans la premire section de cette annexe, nous allons voir comment combiner des fichiers de
code source C++ afin dobtenir un programme excutable. Nous serons ainsi amens tudier
les concepts de base de C++ tels que les units de compilation, les fichiers den-tte, les
fichiers dobjets, les bibliothques et nous apprendrons devenir familier avec le prprocesseur,
le compilateur et lditeur de liens C++.
Nous examinerons ensuite les diffrences de langage les plus importantes entre C++, Java et C# :
comment dfinir des classes, comment utiliser les pointeurs et rfrences, comment surcharger les
oprateurs, comment utiliser le prprocesseur et ainsi de suite. Mme si la syntaxe de C++ semble
analogue celle de Java ou C# au premier abord, les concepts sous-jacents diffrent de faon
subtile. Dautre part, en tant que source dinspiration pour Java et C#, le langage C++ comporte
de nombreux points communs avec ces deux langages, notamment les types de donnes similaires, les mmes oprateurs arithmtiques et les mmes instructions de contrle de flux de base.
La dernire section est ddie la bibliothque C++ standard qui fournit une fonctionnalit prt
lemploi que vous pouvez exploiter dans tout programme C++. Cette bibliothque rsulte de plus
de 30 annes de dveloppement, et en tant que telle fournit une large gamme dapproches comprenant les styles de programmation procdural, orient objet, et fonctionnel, ainsi que des macros
et des modles. Compare aux bibliothques Java et C#, la porte de la bibliothque C++ standard est relativement limite ; elle ne fournit aucun support au niveau de linterface graphique
GUI, de la programmation multithread, des bases de donnes, de linternationalisation, de la gestion
de rseau, XML et Unicode. Pour tendre la porte de C++ dans ces domaines, les dveloppeurs
C++ doivent avoir recours diverses bibliothques (souvent spcifiques la plate-forme).
Cest ce niveau que Qt apporte son bonus. Qt a dmarr comme "bote outils" GUI multiplateforme (un ensemble de classes qui permettent dcrire des applications interface utilisateur graphique portables) mais qui a rapidement volu en framework part entire qui tend
et remplace partiellement la bibliothque C++ standard. Bien que cet ouvrage traite de Qt, il
est intressant de connatre ce que la bibliothque C++ standard a offrir, puisque vous risquez
davoir besoin de travailler avec du code qui lutilise.
Annexe B
485
Pour chaque unit de compilation, le compilateur gnre un fichier objet, avec lextension
.obj (sous Windows) ou .o (sous Unix et Mac OS X). Le fichier objet est un fichier binaire
qui contient le code machine destin larchitecture sur laquelle le programme va sexcuter.
Une fois que les fichiers .cpp ont t compils, nous pouvons combiner les fichiers objet fin de
crer un excutable laide dun programme particulier nomm diteur de liens. Cet diteur
de liens concatne les fichiers objet et rsout les adresses mmoire des fonctions et autres
symboles auxquels font rfrence les units de compilation.
Figure B.1
Le processus
de compilation de C++
(sous Windows)
unit1.cpp
unit2.cpp
unit3.cpp
Compilation
unit1.obj
unit2.obj
unit3.obj
Lorsque vous gnrez un programme, une seule unit de compilation doit contenir la fonction
main() qui joue le rle de point dentre dans le programme. Cette fonction nappartient
aucune classe; il sagit dune fonction globale.
Contrairement Java, pour lequel chaque fichier source doit contenir une classe exactement,
C++ nous laisse libre dorganiser nos units de compilation. Nous avons la possibilit dimplmenter plusieurs classes dans le mme fichier .cpp, ou de rpartir limplmentation dune
classe dans plusieurs fichiers .cpp. Nous pouvons aussi choisir nimporte quel nom pour nos
fichiers source. Quand nous effectuons une modification dans un fichier .cpp particulier, il
suffit de recompiler uniquement ce fichier puis dexcuter de nouveau lditeur de liens sur
application pour crer un nouvel excutable.
Avant de poursuivre, examinons rapidement le code source dun programme C++ trs simple
qui calcule le carr dun entier. Le programme est constitu de deux units de compilation :
main.cpp et square.cpp.
Voici square.cpp:
double square(double n)
{
return n * n;
}
1
2
3
4
Ce fichier contient simplement une fonction globale nomme square() qui renvoie le carr de
son paramtre.
Voici main.cpp:
1
2
#include <cstdlib>
#include <iostream>
486
double square(double);
5
6
7
8
9
10
11
12
13
14
Le fichier source de main.cpp contient la dfinition de la fonction main(). En C++, cette fonction reoit en paramtres un int et un tableau de char* (un tableau de chanes de caractres).
Le nom du programme est disponible en tant que argv[0] et les arguments de ligne de
commande en tant que argv[1], argv[2], , argv[argc -1]. Les noms de paramtre argc
("nombre darguments") et argv ("valeurs des arguments") sont conventionnels. Si le
programme na pas accs aux arguments de ligne de commande, nous pouvons dfinir main()
sans paramtre.
La fonction main() utilise strtod() ("string to double"), cout (flux de sortie standard de
C++), et cerr (flux derreur standard du C++) de la bibliothque Standard C++ pour convertir
largument de ligne de commande en double puis pour afficher le texte sur la console. Les
chanes, les nombres, et les marqueurs de fin de ligne (endl) sont transmis en sortie laide de
loprateur <<, qui est aussi utilis pour les oprations avec dcalage des bits. Pour obtenir
cette fonctionnalit standard, il faut coder les deux directives #include des lignes 1 et 2.
La directive using namespace de la ligne 3 indique au compilateur que nous dsirons importer tous les identificateurs dclars dans lespace de noms std da528ns lespace de noms
global. Cela nous permet dcrire strtod(), cout, cerr, et endl plutt que les versions
compltement qualifies std::strtod(), std::cout, std::cerr, et std::endl. En C++,
loprateur :: spare les composants dun nom complexe.
La dclaration de la ligne 4 est un prototype de fonction. Elle signale au compilateur quune
fonction existe avec les paramtres et la valeur de retour indiqus. La fonction relle peut se
trouver dans la mme unit de compilation ou dans une autre. En labsence de ce prototype,
le compilateur aurait refus lappel de la ligne 12. Les noms de paramtre dans les prototypes de
fonction sont optionnels.
La procdure de compilation du programme varie dune plate-forme lautre. Pour compiler sur
Solaris, par exemple, avec le compilateur C++ de Sun, il faudrait taper les commandes suivantes :
CC -c main.cpp
CC -c square.cpp
ld main.o square.o -o square
Annexe B
487
Les deux premires lignes invoquent le compilateur afin de gnrer les fichiers .o pour les
fichiers .cpp. La troisime ligne invoque lditeur de liens et gnre un excutable nomm
square, que nous pouvons ensuite appeler de la faon suivante :
./square 64
Pour compiler le programme, vous prfrez peut -tre obtenir laide de votre gourou C++
local. Si vous ny parvenez pas, lisez quand mme la suite de cette annexe sans rien compiler et
suivez les instructions du Chapitre 1 pour compiler votre premire application C++/Qt. Qt
fournit des outils partir desquels il nest pas difficile de gnrer des applications pour
nimporte quelle plate-forme.
Revenons notre programme. Dans une application du monde rel, nous devrions normalement placer le prototype de la fonction square() dans un fichier spar puis inclure ce fichier
dans toutes les units de compilation dans lesquelles nous avons besoin dappeler la fonction.
Un tel fichier est nomm fichier den-tte et il comporte gnralement lextension .h (.hh,
.hpp, et .hxx sont galement courantes). Si nous reprenons notre exemple avec la mthode du
fichier den-tte, il faudrait crer le fichier square.h avec le contenu suivant :
1
2
3
4
#ifndef SQUARE_H
#define SQUARE_H
double square(double);
#endif
488
11
double n = strtod(argv[1], 0);
12
cout << "The square of " << argv[1] << " is " << square(n) << endl;
13
return 0;
14 }
La directive #include en ligne 3 insre cet endroit le contenu du fichier square.h. Les
directives qui dbutent par un # sont prises en charge par le prprocesseur C++ avant mme
le dbut de la compilation. Dans le temps, le prprocesseur tait un programme spar que le
programmeur invoquait manuellement avant dexcuter le compilateur. Les compilateurs
modernes grent dsormais implicitement ltape du prprocesseur.
Les directives #include en lignes 1 et 2 insrent le contenu des fichiers den-tte cstdlib et
iostream, qui font partie de la bibliothque C++ standard. Les fichiers den-tte standard
nont pas de suffixe .h. Les crochets (< >) autour des noms de fichier indiquent que les
fichiers den-tte se situent un emplacement standard sur le systme, alors que les guillemets
doubles indiquent au compilateur quil doit examiner le rpertoire courant. Les inclusions sont
normalement insres au dbut du fichier .cpp.
Contrairement aux fichiers .cpp, les fichiers den-tte ne sont pas des units de compilation
proprement parler et ne produisent pas le moindre fichier objet. Ils ne peuvent contenir que des
dclarations qui permettent diverses units de compilation de communiquer entre elles. Il ne
serait donc pas appropri dintroduire limplmentation de la fonction square() dans un tel
fichier. Si nous lavions fait dans cet exemple, cela naurait dclench aucune erreur, parce que
nous incluons square.h une seule fois, mais si nous avions inclus square.h dans plusieurs
fichiers .cpp, nous aurions obtenu de multiples implmentations de square() (une par fichier
.cpp dans lequel elle est incluse). Lditeur signalerait alors la prsence de dfinitions multiples (identiques) de square() et refuserait de gnrer lexcutable. Inversement, si nous
dclarons une fonction mais que nous oublions de limplmenter, lditeur de liens signale un
"symbole non rsolu".
Jusqu prsent, nous avons suppos quun excutable tait uniquement constitu de fichiers
objet. En pratique, ils comportent aussi souvent des liaisons vers des bibliothques qui implmentent une fonctionnalit prte lemploi. Il existe deux principaux types de bibliothque :
Les bibliothques statiques qui sont directement insres dans lexcutable, comme sil
sagissait de fichiers objet. Cela annule les risques de perte de la bibliothque mais cela
augmente la taille de lexcutable.
Pour le programme square, nous tablissons une liaison avec la bibliothque C++ standard,
qui est implmente en tant que bibliothque dynamique sur la plupart des plates-formes. Qt
lui-mme est une collection de bibliothques qui peuvent tre gnres en tant que bibliothques
statiques ou dynamiques (dynamique est loption par dfaut).
Annexe B
489
Description
bool
Valeur boolenne
char
Entier 8 bits
short
Entier 16 bits
int
Entier 32 bits
long
long long_
Entier 64 bits
float
double
Figure B.2
Les types primitifs de C++
Par dfaut, les types short, int, long, et long long sont signs, ce qui signifie quils
peuvent stocker aussi bien des valeurs ngatives que des valeurs positives. Si nous navons
besoin de stocker que des nombres positifs, nous pouvons coder le mot-cl unsigned devant le
type. Alors quun short est capable de stocker nimporte quelle valeur entre 32 768 et
490
+32 767, un unsigned short stocke une valeur entre 0 et 65 535. Loprateur de dcalage
droite >> ("remplit avec des 0") a une smantique non signe si un des oprandes est non
sign.Le type bool peut avoir les valeurs true et false. De plus, les types numriques
peuvent tre utiliss partout o un bool est attendu, 0 tant interprt comme false et toute
valeur non nulle par true.
Le type char sert la fois pour le stockage des caractres ASCII et pour celui des entiers 8 bits
(octets). Lorsquil est utilis pour un entier, celui-ci peut tre sign ou non sign, selon la plateforme. Les types signed char et unsigned char sont des alternatives non ambiges au
char. Qt fournit un type QChar qui stocke des caractres Unicode 16 bits.
Les instances des types intgrs ne sont pas initialises par dfaut. Lorsque nous crons une
variable int, sa valeur pourrait tout aussi bien tre de 0 que de 209 486 515. Heureusement,
les compilateurs nous avertissent pour la plupart lorsque nous tentons de lire le contenu dune
variable non initialise. Nous pouvons faire appel des outils tels que Rational PurifyPlus et
Valgrind pour dtecter les accs de la mmoire non initialise et les autres problmes lis la
mmoire lors de lexcution.
En mmoire, les types numriques ( lexception des long) ont des tailles identiques sur les
diffrentes plates-formes prises en charge par Qt, mais leur reprsentation varie selon larchitecture du systme. Sur les architectures big-endian (telles que PowerPC et SPARC), la valeur
32 bits 0x12345678 est stocke sous la forme des quatre octets 0x12 0x34 0x56 0x78, alors
que sur les architectures little-endian (telles que Intel x86), la squence doctets est inverse.
La diffrence apparat dans les programmes qui copient des zones de mmoire sur disque ou
qui envoient des donnes binaires sur le rseau. La classe QDataStream de Qt, prsente au
Chapitre 12 (Entres/Sorties), peut tre utilise pour stocker des donnes binaires en restant
indpendant de la plate-forme.
Chez Microsoft, le type long long sappelle __int64. Dans les programmes Qt, qlonglong
est propos comme une alternative compatible avec toutes les plates-formes de Qt.
Dfinitions de classe
Les dfinitions de classe en C++ sont analogues celles de Java et C#, mais vous devez
connatre plusieurs diffrences. Nous allons les tudier avec une srie dexemples.
Commenons par une classe qui reprsente une paire de coordonnes (x, y) :
#ifndef POINT2D_H
#define POINT2D_H
class Point2D
{
Public
Point2D() {
xVal = 0;
yVal = 0;
}
Annexe B
491
Point2D(double x, double y) {
xVal = x;
yVal = y;
}
void setX(double
void setY(double
double x() const
double y() const
x) { xVal = x;
y) { yVal = y;
{ return xVal;
{ return yVal;
}
}
}
}
Private
double xVal;
double yVal;
};
#endif
492
Private
double xVal;
double yVal;
};
#endif
Nous commenons par inclure point2d.h parce que le compilateur a besoin de la dfinition
de classe pour tre en mesure danalyser les implmentations de fonction membre. Nous
implmentons ensuite les fonctions, en prfixant leurs noms de celui de la classe suivi de
loprateur ::.
Nous avons dj expliqu comment implmenter une fonction inline et maintenant comment
limplmenter dans un fichier .cpp. Les deux mthodes sont quivalentes dun point de vue
smantique, mais lorsque nous appelons une fonction qui est dclare inline, la plupart des
Annexe B
493
En C++, les variables de tout type peuvent tre dclares directement sans coder new. La
premire variable est initialise laide du constructeur par dfaut Point2D (celui qui ne reoit
aucun paramtre). La seconde variable est initialise laide du second constructeur. Laccs
au membre de lobjet seffectue via loprateur "." (point).
Les variables dclares de cette faon se comportent comme les types primitifs de Java/C# tels
que int et double. Lorsque nous codons loprateur daffectation, par exemple, cest le
contenu de la variable qui est copi et non une simple rfrence lobjet. Si nous modifions
une variable par la suite, cette modification ne sera pas rpercute dans les variables qui on a
pu affecter la valeur de la premire.
En tant que langage orient objet, C++ prend en charge lhritage et le polymorphisme. Nous
allons tudier ces deux proprits avec lexemple de classe de base abstraite Shape et une
sous-classe nomme Circle. Commenons par la classe de base :
#ifndef SHAPE_H
#define SHAPE_H
#include "point2d.h"
class Shape
{
Public
Shape(Point2D center) { myCenter = center; }
virtual void draw() = 0;
494
Protected
Point2D myCenter;
};
#endif
La dfinition se trouve dans un fichier den-tte nomm shape.h. Cette dfinition faisant rfrence la classe Point2D, nous incluons point2d.h.
La classe Shape ne possde pas de classe de base. Contrairement Java et C#, C++ ne fournit
pas de classe Object gnrique partir de laquelle toutes les classes hritent. Qt fournit
QObject comme classe de base naturelle pour toutes sortes dobjets.
La dclaration de la fonction draw() prsente deux caractristiques intressantes : elle comporte
le mot-cl virtual, et elle se termine par = 0. Ce mot-cl signale que la fonction pourrait tre
rimplmente dans les sous-classes. Comme en C#, les fonctions membres C++ ne sont pas rimplmentables par dfaut. La syntaxe bizarre =0 indique que la fonction est une fonction
virtuelle pure, cest--dire une fonction qui ne possde aucune implmentation par dfaut et qui
doit obligatoirement tre implmente dans les sous-classes. Le concept "dinterface" en Java
et C# correspond une classe constitue uniquement de fonctions virtuelles pures en C++.
Voici la dfinition de la sous-classe Circle :
#ifndef CIRCLE_H
#define CIRCLE_H
#include "shape.h"
class Circle: public Shape
{
Public
Circle(Point2D center, double radius = 0.5)
: Shape(center) {
myRadius = radius;
}
void draw() {
//excuter ici une action
}
Private
double myRadius;
};
#endif
La classe Circle hrite publiquement de Shape, ce qui signifie que tous les membres publics
de cette dernire restent publics dans Circle. C++ prend aussi en charge lhritage priv et
protg, qui limite laccs aux membres publics et protgs de la classe de base.
Le constructeur reoit deux paramtres : le second est facultatif et il prend la valeur 0.5
lorsquil nest pas spcifi. Le constructeur transmet le paramtre correspondant au centre au
Annexe B
495
Dautre part, C++ nautorise pas linitialisation dune variable membre dans la dfinition de
classe, le code suivant est donc incorrect :
// ne compilera pas
Private
double myRadius = 0.5;
};
La fonction draw() possde la mme signature que la fonction virtuelle draw() dclare dans
Shape. Il sagit dune rimplmentation et elle sera invoque de faon polymorphique au
moment o draw() sera appele pour une instance de Circle par le biais dun pointeur ou
dune rfrence de Shape. C++ ne fournit pas de mot-cl pour une redfinition de fonction
comme en C#. Ce langage ne comporte pas non plus de mot-cl super ou base qui fasse rfrence la classe de base. Si nous avons besoin dappeler limplmentation de base dune
fonction, nous pouvons prfixer son nom avec celui de la classe de base suivi de loprateur ::.
Par exemple :
class LabeledCircle: public Circle
{
Public
void draw() {
Circle::draw();
drawLabel();
}
...
};
C++ prend en charge lhritage multiple, ce qui signifie quune classe peut tre drive de
plusieurs classes la fois. Voici la syntaxe :
class DerivedClass: public BaseClass1, public BaseClass2, ...,
public BaseClassN
{
...
};
Par dfaut, fonctions et variables dclares dans une classe sont associes avec les instances de
cette classe. Nous avons aussi la possibilit de dclarer des fonctions membres et des variables
membres statiques, que vous utilisez ensuite sans instance.
496
Par exemple :
#ifndef TRUCK_H
#define TRUCK_H
class Truck
{
Public
Truck() { ++counter; }
~Truck() { --counter; }
static int instanceCount() { return counter; }
Private
static int counter;
};
#endif
La variable membre statique counter assure le suivi du nombre dinstances de Truck un instant
donn. Cest le constructeur de Truck qui lincrmente. Le destructeur, que vous reconnaissez
au prfixe ~, dcrmente cette valeur. En C++, le destructeur est automatiquement invoqu
lorsquune variable alloue de faon statique sort de la porte ou lorsquune variable alloue
avec new est supprime. Ce comportement est analogue celui de la mthode finalize() en
Java, sauf que nous pouvons lobtenir en y faisant appel un instant spcifique.
Une variable membre statique existe uniquement dans sa classe : il sagit de "variables de
classe" plutt que de "variables dinstance". Chaque variable membre statique doit tre dfinie
dans un fichier .cpp (mais sans rpter le mot-cl static). Par exemple :
#include "truck.h"
int Truck::counter = 0;
Si vous ne suivez pas cette rgle, vous obtiendrez une erreur de "symbole non rsolu" au
moment de ldition des liens. La fonction statique instanceCount() est accessible depuis
lextrieur de la classe, en la prfixant avec le nom de cette dernire. Par exemple :
#include <iostream>
#include "truck.h"
using namespace std;
int main()
{
Truck truck1;
Truck truck2;
cout << Truck::instanceCount() << " equals 2" << endl;
return 0;
}
Annexe B
497
Les pointeurs
Un pointeur en C++ est une variable qui stocke ladresse mmoire dun objet (plutt que
lobjet lui-mme). Java et C# ont un concept analogue, la "rfrence," mais avec une syntaxe
diffrente. Nous allons commencer par tudier un exemple (peu raliste) pour observer les
pointeurs en action :
1 #include "point2d.h"
2 int main()
3 {
4
Point2D alpha;
5
Point2D beta;
6
Point2D *ptr;
7
8
9
ptr = α
ptr->setX(1.0);
ptr->setY(2.5);
10
11
12
ptr = β
ptr->setX(4.0);
ptr->setY(4.5);
13
ptr = 0;
14
return 0;
15 }
Cet exemple sappuie sur la classe Point2D de la sous-section prcdente. Les lignes 4 et 5
dfinissent deux objets de type Point2D. Ces objets sont initialiss (0, 0) par le constructeur
de Point2D par dfaut.
La ligne 6 dfinit un pointeur vers un objet Point2D. La syntaxe des pointeurs place un astrisque devant le nom de la variable. Ce pointeur nayant pas t initialis, il contient une adresse
mmoire alatoire. Ce problme est rgl ligne 7 par laffectation de ladresse dalpha ce
pointeur. Loprateur unaire & renvoie ladresse mmoire dun objet. Une adresse est typiquement une valeur entire sur 32 ou 64 bits qui spcifie le dcalage dun objet en mmoire.
En lignes 8 et 9, nous accdons lobjet alpha via le pointeur ptr. ptr tant un pointeur et
non un objet, nous devons coder loprateur -> (flche) plutt que loprateur . (point).
Ligne 10, nous affectons ladresse de beta au pointeur. A partir de l, toute opration sur le
pointeur va affecter lobjet beta.
Ligne 13, le pointeur est dfini en pointeur nul. C++ ne fournit pas de mot-cl pour reprsenter
un pointeur qui ne pointe pas vers un objet; cest pourquoi nous affectons la valeur 0 (ou la
constante symbolique NULL, qui reprsente 0). Lemploi dun pointeur nul entrane aussitt
une panne accompagne dun message derreur du type "Erreur de segmentation", "Erreur de
498
protection gnrale", ou "Erreur de bus". Avec laide dun dbogueur, nous pouvons retrouver
la ligne de code lorigine de lerreur.
A la fin de la fonction, lobjet alpha contient la paire de coordonnes (1.0, 2.5), alors que
beta contient (4.0, 4.5).
Les pointeurs sont souvent employs pour stocker des objets allous dynamiquement laide
de new. En jargon C++, nous disons que ces objets sont allous sur le "tas", alors que les variables
locales (les variables dfinies dans une fonction) sont stockes sur la "pile".
Voici un extrait de code qui illustre lallocation de mmoire dynamique laide de new:
#include "point2d.h"
int main()
{
Point2D *point = new Point2D;
point->setX(1.0);
point->setY(2.5);
delete point;
return 0;
}
Loprateur new renvoie ladresse mmoire de lobjet qui vient dtre allou. Nous stockons
ladresse dans une variable pointeur et laccs lobjet seffectue via le pointeur. Quand nous
en avons termin avec lobjet, nous librons la mmoire associe laide de loprateur
delete. Contrairement Java et C#, C++ ne possde pas de programme de libration de la
mmoire (garbage collector) ; les objets allous dynamiquement doivent tre explicitement
librs laide de delete quand nous nen navons plus besoin. Le Chapitre 2 dcrit le
mcanisme parentenfant de Qt, qui simplifie largement la gestion de mmoire dans les
programmes C++.
Si nous oublions dappeler delete, la mmoire reste occupe jusqu ce que le programme se
termine. Cela ne poserait aucun problme dans lexemple prcdent, parce que nous nallouons
quun seul objet, mais dans le cas dun programme qui allouerait constamment de nouveaux
objets, les allocations de mmoire associes se produiraient jusqu saturation de la mmoire
de lordinateur. Une fois quun objet est supprim, la variable pointeur contient toujours
ladresse de cet objet. Ce pointeur devient un "pointeur dans le vide" et ne doit plus tre utilis.
Qt fournit un pointeur "intelligent," QPointer<T>, qui se dfinit automatiquement 0 si le
QObject sur lequel il pointe est supprim.
Dans lexemple ci-dessus, nous avons invoqu le constructeur par dfaut et nous avons appel
setX() et setY() pour initialiser lobjet. Nous aurions pu faire plutt appel au constructeur
deux paramtres :q
Point2D *point = new Point2D(1.0, 2.5);
Annexe B
499
Lexemple nexigeait pas lemploi de new et de delete. Nous aurions trs bien pu allouer
lobjet sur la pile de la faon suivante :
Point2D point;
point.setX(1.0);
point.setY(2.5);
La mmoire occupe par des objets allous de cette faon est automatiquement rcupre la
fin du bloc dans lequel ils apparaissent.
Si nous navons pas lintention de modifier lobjet par lintermdiaire du pointeur, nous
pouvons dclarer le pointeur const. Par exemple :
const Point2D *ptr = new Point2D(1.0, 2.5);
double x = ptr->x();
double y = ptr->y();
// la compilation va chouer
ptr->setX(4.0);
*ptr = Point2D(4.0, 4.5);
Le pointeur ptr const sert uniquement appeler des fonctions membres const telles que
x() et y(). Prenez lhabitude de dclarer vos pointeurs comme const quand vous ne
prvoyez pas de modifier lobjet par leur intermdiaire. De plus, si lobjet lui-mme est const,
nous navons de toute faon pas dautre choix que dutiliser un pointeur const pour stocker
son adresse. Le mot-cl const apporte des informations au compilateur qui est ainsi en mesure
de dtecter trs tt des erreurs et damliorer les performances. C# comporte un mot-cl const
trs similaire celui de C++. Lquivalent Java le plus proche est final, mais il protge les
variables uniquement contre les affectations, pas contre les appels de fonctions membres "nonconst" sur ces dernires.
Les pointeurs fonctionnent avec les types intgrs ainsi quavec les classes. Dans une expression,
loprateur unaire * renvoie la valeur de lobjet associ au pointeur. Par exemple :
int i = 10;
int j = 20;
int *p = &i;
int *q = &j;
cout << *p << " equals 10" << endl;
cout << *q << " equals 20" << endl;
*p = 40;
cout << i << " equals 40" << endl;
p = q;
*p = 100;
cout << i << " equals 40" << endl;
cout << j << " equals 100" << endl;
500
Loprateur ->, partir duquel vous avez accs aux membres dun objet via un pointeur, a une
syntaxe trs particulire. Plutt que de coder ptr->membre, nous pouvons aussi crire
(*ptr).membre. Les parenthses sont ncessaires parce que loprateur "." (point) est prioritaire sur loprateur unaire *.
Les pointeurs avaient mauvaise rputation en C et C++, tel point que la promotion de Java
insiste souvent sur labsence de pointeur dans ce langage. En ralit, les pointeurs C++ sont
analogues dun point de vue conceptuel aux rfrences de Java et C# sauf que nous pouvons
nous servir de pointeurs pour parcourir la mmoire, comme vous le dcouvrirez un peu plus
loin dans cette section. De plus, linclusion des classes conteneur en "copie lcriture" dans
Qt, ainsi que la possibilit de C++ dinstancier une classe sur la pile, signifie que nous pouvons
souvent viter davoir recours aux pointeurs.
Les rfrences
En complment des pointeurs, C++ prend aussi en charge le concept de "rfrence".Comme un
pointeur, une rfrence C++ stocke ladresse dun objet. Voici les principales diffrences :
Les rfrences doivent tre initialises et ne peuvent tre raffectes par la suite.
Lobjet associ une rfrence est directement accessible ; il ny a pas de syntaxe particulire telle que * ou ->.
Les rfrences sont surtout utilises pour dclarer des paramtres. Par dfaut, C++ applique le
mcanisme de transmission par valeur lors de la transmission des paramtres, ce qui signifie
que lorsquun argument est transmis une fonction, celle-ci reoit une copie entirement
nouvelle de lobjet. Voici la dfinition dune fonction qui reoit ses paramtres par le biais dun
appel par valeur :
#include <cstdlib>
using namespace std;
double manhattanDistance(Point2D a, Point2D b)
{
return abs(b.x() - a.x()) + abs(b.y() - a.y());
}
Annexe B
501
Les programmeurs C convertis vitent les oprations de copie inutiles en dclarant leurs paramtres sous forme de pointeurs plutt que sous forme de valeurs :
double manhattanDistance(const Point2D *ap, const Point2D *bp)
{
return abs(bp->x() - ap->x()) + abs(bp->y() - ap->y());
}
Ils doivent ensuite transmettre les adresses plutt que les valeurs lors de lappel de la fonction :
Point2D broadway(12.5, 40.0);
Point2D harlem(77.5, 50.0);
double distance = manhattanDistance(&broadway, &harlem);
C++ a introduit les rfrences afin de simplifier la syntaxe et dviter que lappelant ne transmette un pointeur nul. Si nous utilisons des rfrences plutt que des pointeurs, voici ce que
devient la fonction :
double manhattanDistance(const Point2D &a, const Point2D &b)
{
return abs(b.x() - a.x()) + abs(b.y() - a.y());
}
La dclaration dune rfrence est analogue celle dun pointeur, & remplaant *. Mais lorsque nous utilisons effectivement la rfrence, nous pouvons oublier quil sagit dune adresse
mmoire et la traiter comme une variable ordinaire. De plus, lappel dune fonction qui reoit
des rfrences en arguments nexige aucune attention particulire (pas doprateur &).
Dans lensemble, en remplaant Point2D par const Point2D & dans la liste de paramtres,
nous avons rduit la surcharge de traitement de lappel de fonction : plutt que de copier
256 bits (la taille de quatre double), nous copions seulement 64 ou 128 bits, selon la taille du
pointeur de la plate-forme cible.
Lexemple prcdent sappuyait sur les rfrences const, ce qui empchait la fonction de
modifier les objets associs aux rfrences. Lorsquon veut au contraire autoriser ce comportement,
nous pouvons transmettre une rfrence ou un pointeur non-const. Par exemple :
void transpose(Point2D &point)
{
double oldX = point.x();
point.setX(point.y());
point.setY(oldX);
}
Dans certains cas, nous disposons dune rfrence et nous avons besoin dappeler une fonction
qui reoit un pointeur, ou vice versa. Pour convertir une rfrence en pointeur, nous pouvons
tout simplement utiliser loprateur unaire &:
Point2D point;
Point2D &ref = point;
Point2D *ptr = &ref;
502
Rfrences et pointeurs sont reprsents de la mme faon en mmoire, et ils peuvent souvent
tre utiliss de faon interchangeable, ce qui soulve la question de quel lment utiliser et
quand. Dun ct, la syntaxe des rfrences est plus pratique. Dun autre ct, tout moment
vous pouvez raffecter les pointeurs pour pointer sur un autre objet, ils peuvent stocker une
valeur nulle, et leur syntaxe plus explicite est plutt une bonne chose. Cest pour cette raison
que les pointeurs ont tendance lemporter, les rfrences tant plutt presque exclusivement
employe pour dclarer les paramtres de fonction, en conjonction avec const.
Les tableaux
Les tableaux en C++ sont dclars en spcifiant leur nombre dlments entre crochets dans la
dclaration de variable aprs le nom de variable. Il est possible de crer des tableaux deux
dimensions laide dun tableau de tableau. Voici la dfinition dun tableau une dimension
contenant 10 lments de type int:
int fibonacci[10];
Dans ce cas, nous pouvons alors omettre la taille de tableau puisque le compilateur peut la
dduire partir du nombre dinitialisations :
int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Linitialisation statique fonctionne aussi pour les types complexes, tels que Point2D:
Point2D triangle[] = {
Point2D(0.0, 0.0), Point2D(1.0, 0.0), Point2D(0.5, 0.866)
};
Si nous navons pas lintention de modifier le tableau par la suite, nous pouvons le rendre const:
const int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Pour trouver le nombre dlments dun tableau, il suffit dexcuter loprateur sizeof()
comme suit :
int n = sizeof(fibonacci) / sizeof(fibonacci[0]);
Annexe B
503
Cet oprateur renvoie la taille de ses arguments en octets. Le nombre dlments dans un
tableau correspond la taille de ce dernier en octets divise par la taille dun de ses lments.
Comme cest un peu fatiguant taper, une solution courante consiste dclarer une constante
et lutiliser pour dfinir le tableau :
enum { NFibonacci = 10 };
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Nous initialisons le pointeur avec ladresse du premier lment et nous bouclons jusqu ce
que nous atteignons llment "qui suit le dernier" (le "onzime" lment, fibonacci[10]).
A chaque itration, loprateur ++ positionne le pointeur sur llment suivant.
Au lieu de &fibonacci[0], nous aurions aussi pu crire fibonacci. En effet, le nom dun
tableau utilis seul est automatiquement converti en pointeur vers le premier lment du
tableau. De la mme faon, nous pourrions remplacer fibonacci + 10 par &fibonacci[10]. Cela marche aussi en sens inverse : nous pouvons rcuprer le contenu de
llment courant en codant soit *ptr soit ptr[0] et nous pourrions accder llment suivant
laide de *(ptr + 1) ou ptr[1].
On fait quelquefois rfrence ce principe en termes "dquivalence entre pointeurs et tableaux".
Pour empcher ce quil considre comme une perte defficacit gratuite, C++ nous interdit de
transmettre des tableaux des fonctions par valeur. Il faut plutt transmettre leur adresse.
Par exemple :
#include <iostream>
using namespace std;
void printIntegerTable(const int *table, int size)
{
for (int i = 0; i < size; ++i)
cout << table[i] << endl;
}
504
int main()
{
const int fibonacci[10] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
printIntegerTable(fibonacci, 10);
return 0;
}
Ironiquement, alors que le C++ ne nous donne aucun choix concernant le mode de transmission dun tableau, par adresse ou par valeur, il nous accorde quelques liberts dans la syntaxe
employe pour dclarer le type de paramtre. Pour remplacer const int *table, nous
aurions pu coder const int table[] pour dclarer un paramtre pointeur-vers-constanteint. Dune faon analogue, le paramtre argv de main() peut tre dclar soit en tant que
char *argv[] soit en tant que char **argv.
Pour copier un tableau dans un autre tableau, une solution consiste boucler dans le tableau :
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
int temp[NFibonacci];
for (int i = 0; i < NFibonacci; ++i)
temp[i] = fibonacci[i];
Pour les types de base tel que int, nous pouvons aussi utiliser std::memcpy(), qui copie un
bloc de mmoire. Par exemple :
memcpy(temp, fibonacci, sizeof(fibonacci));
Lorsque nous dclarons un tableau C++, la taille doit tre une constante. Si nous dsirons crer
un tableau de taille variable, nous avons plusieurs solutions.
Nous pouvons allouer dynamiquement ce tableau :
int *fibonacci = new int[n];
Certains compilateurs autorisent les variables dans ce contexte, mais il ne faut pas exploiter ce
type de fonctionnalit pour crer un programme portable.
Loprateur new[] alloue un certain nombre dlments dans des emplacements mmoire
conscutifs et renvoie un pointeur vers le premier lment. Grce au principe de "lquivalence
des pointeurs et des tableaux," on peut accder aux lments par lintermdiaire dun pointeur
avec fibonacci[0], fibonacci[1], , fibonacci[n -1]. Ds que le tableau nest plus
ncessaire, nous devons librer la mmoire quil occupe en excutant loprateur delete[]:
delete [] fibonacci;
Annexe B
505
Vous accdez aux lments laide de loprateur [], exactement comme dans un tableau
C++ ordinaire. Avec std::vector<T> (o T est le type des lments stocks dans le
vecteur), nous pouvons redimensionner le tableau tout moment en excutant resize() et
nous le copions avec loprateur daffectation. Les classes qui contiennent des crochets
(<>) dans leur nom sont appeles classes modle.
Nous pouvons utiliser la syntaxe QVector<T> class de Qt :
#include <QVector>
QVector<int> fibonacci(n);
LAPI de QVector<T> est trs proche de celle de std::vector<T>, mais elle prend aussi en
charge litration via le mot-cl foreach de Qt et elle utilise le partage de donnes ("copie
lcriture") afin doptimiser la mmoire et les temps de rponse. Le Chapitre 11 prsente les
classes conteneur de Qt et expose leurs relations avec les conteneurs standard du C++.
Vous pourriez tre tent dviter au maximum les tableaux intgrs et de coder plutt std::
vector<T> ou QVector<T>. Il est pourtant essentiel de bien comprendre le fonctionnement
des tableaux intgrs parce que tt ou tard vous en aurez besoin dans du code fortement optimis ou pour servir dinterface avec les bibliothques C existantes.
506
void hello4()
{
const char *str = "Hello world!";
cout << str << endl;
}
Dans la premire fonction, nous dclarons la chane en tant que tableau et nous linitialisons de
faon basique. Notez le caractre 0 (zro) de fin, qui signale la fin de la chane. La deuxime
fonction comporte une dfinition de tableau similaire mais nous faisons cette fois appel un
littral chane pour initialiser le tableau. En C++, les littraux chane sont de simples tableaux
de const char avec un caractre 0 de fin implicite. La troisime fonction emploie directement un littral chane, sans lui donner de nom. Une fois traduit en instructions de langage
machine, ce code est identique celui des deux fonctions prcdentes.
La quatrime fonction est un peu diffrente parce quelle cre non seulement un tableau
(anonyme) mais aussi une variable pointeur appele str qui stocke ladresse du premier
lment du tableau. Malgr cela, la smantique de la fonction est identique aux trois fonctions
prcdentes, et un compilateur performant devrait liminer la variable str superflue.
Les fonctions qui reoivent des chanes C++ en arguments reoivent gnralement un char*
ou un const char*. Voici un programme court qui illustre lemploi de chacune delles :
#include <cctype>
#include <iostream>
using namespace std;
void makeUppercase(char *str)
{
for (int i = 0; str[i]!= \0; ++i)
str[i] = toupper(str[i]);
}
void writeLine(const char *str)
{
cout << str << endl;
}
int main(int argc, char *argv[])
{
for (int i = 1; i < argc; ++i) {
makeUppercase(argv[i]);
writeLine(argv[i]);
}
return 0;
}
En C++, le type char stocke normalement une valeur 8 bits. Cela signifie que nous pouvons
facilement stocker des chanes ASCII, ISO 8859-1 (Latin-1), et autres codages 8 bits dans un
tableau de caractres, mais que nous ne pouvons pas stocker des caractres Unicode arbitraires
sans faire appel des squences multioctet. Qt fournit la puissante classe QString, qui stocke
Annexe B
507
des chanes Unicode sous forme de squences de QChars 16 bits et qui exploite en interne
loptimisation du partage de donnes implicite ("copie lcriture"). Cette classe est dtaille
dans les Chapitres 11 (Classes conteneur) et 17 (Internationalisation).
Les numrations
C++ comprend une fonctionnalit dnumration pour la dclaration dun jeu de constantes
nommes analogue celle fournie par C#. Supposons que nous voulions stocker les jours de la
semaine dans un programme :
enum DayOfWeek {
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
};
Normalement, nous devrions placer cette dclaration dans un fichier den-tte ou mme dans
une classe. La dclaration prcdente est quivalente en apparence aux dfinitions de constantes
suivantes :
const
const
const
const
const
const
const
int
int
int
int
int
int
int
Sunday = 0;
Monday = 1;
Tuesday = 2;
Wednesday = 3;
Thursday = 4;
Friday = 5;
Saturday = 6;
En exploitant la construction de lnumration, nous pouvons dclarer par la suite des variables
ou paramtres de type DayOfWeek et le compilateur garantira que seules les valeurs de
lnumration DayOfWeek leur seront affectes. Par exemple :
DayOfWeek day = Sunday;
Si la scurit des types ne nous concerne pas, nous pouvons aussi coder
int day = Sunday;
Notez que pour faire rfrence la constante Sunday dans lnumration DayOfWeek, nous
crivons simplement Sunday, et non DayOfWeek::Sunday.
Par dfaut, le compilateur affecte des valeurs dentiers conscutifs aux constantes dune
numration, en commenant 0. Nous pouvons spcifier dautres valeurs si ncessaire :
enum DayOfWeek {
Sunday = 628,
Monday = 616,
Tuesday = 735,
Wednesday = 932,
Thursday = 852,
Friday = 607,
Saturday = 845
};
508
Si nous ne spcifions pas la valeur dun lment de lnumration, celui-ci prend la valeur de
llment prcdent, plus 1. Les numrations sont quelquefois employes pour dclarer
des constantes entires, auquel cas nous omettons normalement le nom de lnumration :
Enum
FirstPort = 1024,
MaxPorts = 32767
};
Chaque option est souvent nomme "indicateur". Nous pouvons combiner ces indicateurs
laide des oprateurs bit bit | ou |=:
int options = NoOptions;
if (wilcardSyntaxCheckBox->isChecked())
options |= WildcardSyntax;
if (caseSensitiveCheckBox->isChecked())
options |= CaseSensitive;
if (searchBackwardCheckBox->isChecked())
options |= SearchBackwardSyntax;
if (wrapAroundCheckBox->isChecked())
options |= WrapAround;
Nous pouvons tester si un indicateur existe laide de loprateur
if (options & CaseSensitive) {
// rechercher avec casse significative
}
&
bit bit:
Une variable de type FindOption ne peut contenir quun seul indicateur la fois. La combinaison de plusieurs indicateurs laide de | donne un entier ordinaire. Dans ce cas, les types
ne sont malheureusement pas scuriss : le compilateur ne va rien signaler si une fonction qui
doit recevoir une combinaison de FindOptions dans un paramtre int reoit plutt Saturday. Qt utilise QFlags<T> pour scuriser les types de ses propres types dindicateur. La classe
est galement disponible lorsque nous dfinissons des types dindicateur personnaliss.
Consultez la documentation en ligne de QFlags<T> pour connatre tous les dtails.
Annexe B
509
TypeDef
Avec C++, nous pouvons dfinir un alias pour un type de donnes laide du mot-cl
typedef. Si nous codons souvent par exemple QVector<Point2D> et que nous dsirons conomiser les oprations de frappe, nous pouvons placer la dclaration de typedef suivante dans
un de nos fichiers den-tte :
typedef QVector<Point2D> PointVector;
A partir de l, PointVector peut tre cod en lieu et place de QVector<Point2D>. Notez que
le nouveau nom du type apparat aprs lancien. La syntaxe de typedef suit dlibrment
celle des dclarations de variable.
Avec Qt, les typedef sont surtout employs pour les raisons suivantes :
Laspect pratique : Qt dclare uint et QWidgetList en tant que typedef pour unsigned
int et QList<QWidget *> afin de simplifier la frappe.
Les diffrences entre plates-formes : certains types ont des dfinitions diffrentes sur des
plates-formes diffrentes. Par exemple, qlonglong est dfini comme __int64 sous
Windows et comme long long sur dautres plates-formes.
La compatibilit : la classe QIconSet de Qt 3 a t renomme en QIcon dans Qt 4. Pour
aider les utilisateurs de Qt 3 porter leurs applications vers Qt 4, QIconSet est fourni
comme typedef pour QIcon lorsque la compatibilit Qt 3 est active.
Conversions de type
C++ fournit plusieurs syntaxes pour la conversion des valeurs dun type lautre. La syntaxe
traditionnelle, hrite de C, implique de placer le type obtenu entre parenthses avant la valeur
convertir :
const double Pi = 3.14159265359;
int x = (int)(Pi * 100);
cout << x << " equals 314" << endl;
Cette syntaxe est trs puissante. Elle permet de changer le type des pointeurs, de supprimer
const, et plus encore. Par exemple :
short j = 0x1234;
if (*(char *)&j == 0x12)
cout << "The byte order is big-endian" << endl;
Dans lexemple prcdent, nous convertissons un short* en char* et nous utilisons loprateur
unaire * pour accder loctet situ lemplacement de mmoire donn. Sur les systmes bigendian, cet octet se trouve en 0x12; sur les systmes little-endian, il se trouve lemplacement
0x34. Les pointeurs et rfrences tant reprsents de la mme faon, vous ne serez pas surpris
dapprendre que le code prcdent peut tre rcrit laide dune conversion de rfrence :
510
short j = 0x1234;
if ((char &)j == 0x12)
cout << "The byte order is big-endian" << endl;
Si le type de donnes est un nom de classe, un typedef, ou un type primitif qui peut sexprimer
sous la forme dun unique jeton alphanumrique, nous pouvons nous servir de la syntaxe du
constructeur comme dune conversion :
int x = int(Pi * 100);
La conversion des pointeurs et rfrences laide des conversions traditionnelles de style C est
assez prilleuse parce que le compilateur nous autorise convertir nimporte quel type de pointeur (ou rfrence) vers nimporte quel autre type de pointeur (ou rfrence). Cest pourquoi
C++ a introduit quatre conversions de style nouveau avec une smantique plus prcise. Pour
les pointeurs et rfrences, les nouvelles conversions sont prfrables aux conversions de style
C plus risques et sont utilises dans cet ouvrage.
static_cast<T>() convertit un pointeur-vers-A en pointeur-vers-B, en imposant que la
classe B hrite de la classe A. Par exemple :
A *obj = new B;
B *b = static_cast<B *>(obj);
b->someFunctionDeclaredInB();
Si lobjet nest pas une instance de B (mais quil hrite de A), lusage du pointeur obtenu
peut produire des pannes obscures.
dynamic_cast<T>() est analogue static_cast<T>(), sauf que les informations
de type lexcution (RTTI) servent contrler si lobjet associ au pointeur est bien
une instance de la classe B. Si ce nest pas le cas, la conversion renvoie un pointeur nul.
Par exemple :
A *obj = new B;
B *b = dynamic_cast<B *>(obj);
if (b)
b-> someFunctionDeclaredInB();
Annexe B
511
that->recomputeInternalData();
}
...
}
Dans lexemple prcdent, nous convertissons le qualificateur const du pointeur this afin
dappeler la fonction membre non-const recomputeInternalData(). Il nest pas recommand de procder de cette faon et vous pouvez lviter en codant mutable, comme expliqu
au Chapitre 4.
reinterpret_cast<T>() convertit tout type pointeur ou rfrence vers un autre type de
ce genre. Par exemple :
short j = 0x1234;
if (reinterpret_cast<char &>(j) == 0x12)
cout << "The byte order is big-endian" << endl;
En Java et C#, il est possible de stocker toute rfrence en tant que rfrence dObject. C++
ne comporte pas une telle classe de base universelle, mais il fournit un type de donnes particulier, void*, qui stocke ladresse dune instance de nimporte quel type. Un void* doit tre
reconvertit vers un autre type ( laide de static_cast<T>()) avant dtre exploit.
C++ offre de nombreuses mthodes de conversion entre types, mais nous avons rarement
besoin deffectuer une conversion dans ce langage. Lorsque nous utilisons des classes conteneurs telles que std::vector<T> ou QVector<T>, nous spcifions le type T et nous rcuprons les lments sans convertir. De plus, pour les types primitifs, certaines conversions
seffectuent implicitement (de char vers int par exemple), et pour les types personnaliss
nous pouvons dfinir des conversions implicites en fournissant un constructeur un paramtre.
Par exemple :
class MyInteger
{
Public
MyInteger();
MyInteger(int i);
...
};
int main()
{
MyInteger n;
n = 5;
...
}
Pour certains constructeurs un paramtre, la conversion automatique nest pas justifie. Il est
possible de la dsactiver en dclarant le constructeur avec le mot-cl explicit:
class MyVector
{
512
Public
explicit MyVector(int size);
...
};
Surcharge doprateur
C++ nous permet de surcharger des fonctions, ce qui signifie que nous pouvons dclarer
plusieurs fonctions avec le mme nom dans la mme porte, partir du moment o leurs listes
de paramtres sont diffrentes. C++ prend aussi en charge la surcharge des oprateurs. Il sagit
de la possibilit daffecter une smantique particulire des oprateurs intgrs (tels que +, <<,
et []) lorsquils sont employs avec des types personnaliss.
Nous avons dj tudi quelques exemples doprateurs surchargs. Lorsque nous avions
utilis << pour transmettre du texte en sortie vers cout ou cerr, nous navions pas excut
loprateur de dcalage de C++, mais plutt une version particulire de cet oprateur qui recevait un objet ostream (tel que cout et cerr) gauche et une chane (ou bien un nombre ou un
manipulateur de flux tel que endl) droite et qui renvoyait lobjet ostream, ce qui permettait
deffectuer plusieurs appels par ligne.
Lintrt de la surcharge des oprateurs est que nous pouvons obtenir un comportement pour
nos types personnaliss identique celui des types intgrs. Pour illustrer ce point, nous allons
surcharger +=, -=, +, et - pour travailler avec des objets Point2D:
#ifndef POINT2D_H
#define POINT2D_H
class Point2D
{
Public
Point2D();
Point2D(double x, double y);
void setX(double x);
void setY(double y);
double x() const;
double y() const;
Point2D &operator+=(const Point2D &other) {
xVal += other.xVal;
yVal += other.yVal;
return *this;
}
Point2D &operator-=(const Point2D &other) {
xVal -= other.xVal;
yVal -= other.yVal;
return *this;
}
Private
Annexe B
513
double xVal;
double yVal;
};
inline Point2D operator+(const Point2D &a, const Point2D &b)
{
return Point2D(a.x() + b.x(), a.y() + b.y());
}
inline Point2D operator-(const Point2D &a, const Point2D &b)
{
return Point2D(a.x() - b.x(), a.y() - b.y());
}
#endif
Les oprateurs peuvent tre implments soit en tant que fonctions membres soit en tant que
fonctions globales. Dans notre exemple, nous avons implment += et -= en tant que fonctions
membres, et + et - en tant que fonctions globales.
Les oprateurs += et -= reoivent la rfrence dun autre objet Point2D et incrmentent ou
dcrmentent les coordonnes x et y de lobjet courant en fonction de lautre objet. Ils
renvoient *this, qui reprsente une rfrence lobjet courant (de type Point2D*). Puisquon
renvoie une rfrence, nous pouvons crire du code un peu exotique comme ci-aprs :
a += b += c;
Les oprateurs + et - reoivent deux paramtres et renvoient un objet Point2D par valeur (et
non la rfrence dun objet existant). Grce au mot-cl inline, nous avons la possibilit de
placer ces dfinitions de fonction dans le fichier den-tte. Si le corps de la fonction avait t
plus long, nous aurions insr le prototype de fonction dans le fichier den-tte et la dfinition
de fonction (sans le mot-cl inline) dans le fichier .cpp.
Lextrait de code suivant prsente les quatre oprateurs surchargs en action :
Point2D alpha(12.5, 40.0);
Point2D beta(77.5, 50.0);
alpha += beta;
beta -= alpha;
Point2D gamma = alpha + beta;
Point2D delta = beta - alpha;
Nous invoquons les fonctions oprateur comme nimporte quelle autre fonction :
Point2D alpha(12.5, 40.0);
Point2D beta(77.5, 50.0);
alpha.operator+=(beta);
beta.operator-=(alpha);
514
La surcharge des oprateurs en C++ est un sujet complexe, mais vous navez pas forcment
besoin den connatre tous les dtails. Il est surtout important de comprendre les principes de
base de la surcharge des oprateurs parce que plusieurs classes Qt (notamment QString et
QVector<T>) sappuient sur cette fonctionnalit pour fournir une syntaxe plus simple et plus
naturelle pour des oprations telles que la concatnation et lajout dlment.
Types rfrence : Il sagit de classes telles que Integer (en Java), String, et MaClasseAmoi. Les instances sont cres en codant new. Loprateur daffectation copie uniquement
une rfrence de lobjet ; pour obtenir une copie intgrale, il faut appeler clone() (en Java)
ou Clone() (en C#). Par exemple :
Integer i = new Integer(5);
Integer j = new Integer(10);
i = j.clone();
En C++, tous les types peuvent tre employs en tant que "types rfrence," et ceux qui
peuvent tre copis peuvent aussi tre employs en tant que "types valeur".C++na pas besoin
dun classe Integer, par exemple, parce que vous pouvez vous servir des pointeurs et de new
comme dans les lignes suivantes :
int *i = new int(5);
int *j = new int(10);
*i = *j;
Contrairement Java et C#, C++ traite les classes dfinies par lutilisateur exactement comme
des types intgrs :
Point2D *i = new Point2D(5, 5);
Point2D *j = new Point2D(10, 10);
*i = *j;
Si nous dsirons rendre une classe C++ copiable, nous devons prvoir un constructeur de copie
et un oprateur daffectation pour cette dernire. Le constructeur de copie est invoqu lorsque
Annexe B
515
nous initialisons lobjet avec un autre objet de mme type. C++ fournit deux syntaxes quivalentes pour cette situation :
Point2D i(20, 20);
Point2D j(i); // premire syntaxe
Point2D k = i; // deuxime syntaxe
Loprateur daffectation est invoqu lorsque nous utilisons cet oprateur sur une variable
existante :
Point2D i(5, 5);
Point2D j(10, 10);
j = i;
516
...
Private
BankAccount(const BankAccount &other);
BankAccount &operator=(const BankAccount &other);
};
En Qt, beaucoup de classes sont conues pour tre utilises en tant que classes de valeur. Elles
disposent dun constructeur de copie et dun oprateur daffectation, et elles sont normalement
instancies sur la pile sans new. Cest le cas en particulier pour QDateTime, QImage, QString,
et les classes conteneur telles que QList<T>, QVector<T>, et QMap<K,T>.
Les autres classes appartiennent la catgorie "type rfrence", notamment QObject et ses
sous-classes (QWidget, QTimer, QTcpSocket, etc.). Celles-ci possdent des fonctions virtuelles et
ne peuvent pas tre copies. Un QWidget reprsente par exemple une fentre spcifique ou un
contrle sur lcran. Sil y a 75 instances de QWidget en mmoire, il y a aussi 75 fentres et
contrles sur cet cran. Ces classes sont typiquement instancies laide de loprateur new.
Annexe B
517
Dans ce fichier, deux variables globales (randomNumbers et seed) et deux fonctions globales
(nextRandomNumber() et populateRandomArray()) sont dclares. Deux dclarations
contiennent le mot-cl static; elles ne sont visibles que dans lunit de compilation
courante (random.cpp) et on dit quelles ont une liaison statique. Les deux autres sont
disponibles dans nimporte quelle unit de compilation du programme ; elles ont une liaison
externe.
La liaison statique est particulirement indique pour les fonctions assistantes et les variables
internes qui ne doivent pas tre utilises dans dautres units de compilation. Elle rduit le
risque de collision didentificateurs (variables globales de mme nom ou fonctions globales
avec la mme signature dans des units de compilation diffrentes) et empche des utilisateurs
malveillants ou trs maladroits daccder au code dune unit de compilation.
Examinons maintenant le second fichier, main.cpp, qui utilise les deux variables globales
dclares avec une liaison externe dans random.cpp:
#include <iostream>
using namespace std;
extern int randomNumbers[128];
void populateRandomArray();
int main()
{
populateRandomArray();
for (int i = 0; i < 128; ++i)
cout << randomNumbers[i] << endl;
return 0;
}
Nous dclarons les variables et fonctions externes avant de les appeler. La dclaration de variable
externe (qui rend la variable visible dans lunit de compilation courante) pour randomNumbers dbute avec le mot-cl extern. Sans ce mot-cl, le compilateur pourrait penser quil
sagit dune dfinition de variable, et lditeur de liens rapporterait une erreur pour la mme
variable dfinie dans deux units de compilation diffrentes (random.cpp et main.cpp). Vous
pouvez dclarer des variables autant de fois que vous voulez, mais elles ne doivent tre dfinies
quune seule fois. Cest partir de la dfinition que le compilateur rserve lespace requis pour
la variable.
La fonction populateRandomArray() est dclare avec un prototype de fonction. Le mot-cl
extern est facultatif pour les fonctions.
Nous pourrions typiquement placer les dclarations de fonction et de variable externes dans un
fichier den-tte et inclure ce dernier dans tous les fichiers qui en ont besoin :
#ifndef RANDOM_H
#define RANDOM_H
518
Nous avions dj vu comment coder static pour dclarer des fonctions et variables membres
qui ne sont pas associes une instance spcifique de la classe, et nous venons maintenant de
voir comme utiliser ce mot-cl pour dclarer des fonctions et variables avec une liaison statique. Notez quil existe un autre usage de static. En C++, nous pouvons dclarer une variable
locale avec ce mot-cl. De telles variables sont initialises la premire fois que la fonction est
appele et leur valeur est garantie entre plusieurs appels de fonction. Par exemple :
void nextPrime()
{
static int n = 1;
do {
++n;
} while (!isPrime(n));
return n;
}
Les variables locales statiques sont similaires aux variables globales, mais elles ne sont visibles
qu lintrieur de la fonction dans laquelle elles sont dfinies.
Espaces de noms
Les espaces de noms sont un mcanisme destin rduire les risques de collision de noms dans
les programmes C++. Ces collisions sont frquentes dans les gros programmes qui exploitent
plusieurs bibliothques tiers. Dans vos propres programmes, vous avez le choix de les utiliser
ou non.
Nous encadrons typiquement dans un espace de noms toutes les dclarations dun fichier dentte afin de ne pas "polluer" lespace de noms global avec les identificateurs quil contient.
Par exemple :
#ifndef SOFTWAREINC_RANDOM_H
#define SOFTWAREINC_RANDOM_H
namespace SoftwareInc
{
extern int randomNumbers[128];
void populateRandomArray();
}
#endif
Annexe B
519
(Notez que nous avons aussi renomm la macro de prprocesseur utilise pour viter les inclusions multiples, rduisant ainsi le risque de collision de nom avec un fichier den-tte de mme
nom mais situ dans un rpertoire diffrent.)
La syntaxe dun espace de noms est similaire celle dune classe, mais la ligne ne se termine
pas par un point-virgule. Voici le nouveau fichier random.cpp:
#include "random.h"
int SoftwareInc::randomNumbers[128];
static int seed = 42;
static int nextRandomNumber()
{
seed = 1009 + (seed * 2011);
return seed;
}
void SoftwareInc::populateRandomArray()
{
for (int i = 0; i < 128; ++i)
randomNumbers[i] = nextRandomNumber();
}
Contrairement aux classes, les espaces de noms peuvent tre "rouverts" tout moment.
Par exemple :
namespace Alpha
{
void alpha1();
void alpha2();
}
namespace Beta
{
void beta1();
}
namespace Alpha
{
void alpha3();
}
Vous avez ainsi la possibilit de dfinir des centaines de classes, situes dans autant de fichiers
den-tte, dans un seul espace de noms. La bibliothque standard C++ englobe ainsi tous ses
identificateurs dans lespace de noms std. Dans Qt, les espaces de noms servent pour des identificateurs de type global tels que Qt::AlignBottom et Qt::yellow. Pour des raisons historiques,
les classes de Qt nappartiennent aucun espace de noms mais sont prfixes de la lettre "Q".
520
Pour se rfrer un identificateur dclar dans un espace de noms depuis lextrieur de cet
espace, nous le prfixons avec le nom de lespace de noms (et ::). Nous pouvons aussi faire
appel lun des trois mcanismes suivants, qui visent rduire la frappe.
Aprs la dfinition de lalias, celui-ci peut tre employ la place du nom original.
Cette approche augmente les risques de collision de noms. Ds que le compilateur signale un
nom ambigu (deux classes de mme nom, par exemple, dfinies dans deux espaces de noms
diffrents), nous sommes toujours en mesure de qualifier lidentificateur avec le nom de
lespace de noms lorsque nous avons besoin dy faire rfrence.
Le prprocesseur
Le prprocesseur C++ est un programme qui convertit un fichier source .cpp contenant des
directives #- (telles que #include, #ifndef, et #endif) en fichier source ne contenant
aucune directive de cette sorte. Ces directives effectuent des oprations de texte simples sur le
Annexe B
521
fichier source, comme une compilation conditionnelle, une inclusion de fichier et une expansion de macro. Le prprocesseur est normalement invoqu automatiquement par le compilateur, mais dans la plupart des systmes vous avez encore la possibilit de linvoquer seul
(souvent par lintermdiaire dune option de compilateur -E ou /E).
La directive #include insre localement le contenu du fichier indiqu entre crochets (<>)
ou guillemets doubles (""), selon que le fichier den-tte est install sur un emplacement
standard ou avec les fichiers du projet courant. Le nom de fichier peut contenir .. et / (que
les compilateurs Windows interprtent correctement comme tant des sparateurs de rpertoire). Par exemple :
#include "../shared/globaldefs.h"
La directive #define dfinit une macro. Les occurrences de la macro qui apparaissent aprs la
directive #define sont remplaces par la dfinition de la macro. Par exemple, la directive
#define PI 3.14159265359
demande au prprocesseur de remplacer toutes les futures occurrences du jeton PI dans lunit
de compilation courante par le jeton 3.14159265359. Pour viter les collisions entre noms de
variable et de classe, on affecte gnralement aux macros des noms en majuscules. Nous pouvons
trs bien dfinir des macros qui reoivent des arguments :
#define SQUARE(x) ((x) * (x))
Dans le corps de la macro, il est conseill dencadrer toutes les occurrences des paramtres par
des parenthses, ainsi que le corps complet, pour viter les problmes de priorit des oprateurs. Aprs tout, nous voulons que 7*SQUARE(2+3) soit dvelopp en 7*((2+3)*(2+3)), et
non en 7*2+3*2+3.
Les compilateurs C++ nous permettent normalement de dfinir des macros sur la ligne de
commande, via loption -D ou /D. Par exemple :
CC -DPI=3.14159265359 -c main.cpp
Les macros taient trs populaires avant larrive des typedef, numrations, constantes, fonctions inline et modles. Leur rle aujourdhui consiste surtout protger les fichiers den-tte
contre les inclusions multiple.
Cest pratique si nous avons lintention de redfinir une macro, puisque le prprocesseur
interdit de dfinir deux fois la mme macro. a sert aussi contrler la compilation conditionnelle.
522
Vous traitez des parties du code ou vous les sautez en codant #if, #elif, #else, et
#endif, en fonction de la valeur numrique des macros. Par exemple :
#define NO_OPTIM 0
#define OPTIM_FOR_SPEED 1
#define OPTIM_FOR_MEMORY 2
#define OPTIMIZATION OPTIM_FOR_MEMORY
...
#if OPTIMIZATION == OPTIM_FOR_SPEED
typedef int MyInt;
#elif OPTIMIZATION == OPTIM_FOR_MEMORY
typedef short MyInt;
#else
typedef long long MyInt;
#endif
Dans lexemple ci-dessus, seule la deuxime dclaration de typedef devra tre traite par le
compilateur, MyInt tant ainsi dfini comme un synonyme de short. En changeant la dfinition de la macro OPTIMIZATION, nous obtenons des programmes diffrents. Lorsquune
macro nest pas dfinie, sa valeur est considre comme tant 0.
Une autre approche de la compilation conditionnelle consiste tester si une macro est dfinie
ou non. Vous procdez en codant loprateur defined() comme suit :
#define OPTIM_FOR_MEMORY
...
#if defined(OPTIM_FOR_SPEED)
typedef int MyInt;
#elif defined(OPTIM_FOR_MEMORY)
typedef short MyInt;
#else
typedef long long MyInt;
#endif
Pour des raisons pratiques, le prprocesseur reconnat #ifdef X et #ifndef X comme des
synonymes de #if defined(X) et #if!defined(X). Vous protgez un fichier den-tte
des inclusions multiple en encadrant son contenu comme ci-aprs :
#ifndef MYHEADERFILE_H
#define MYHEADERFILE_H
...
#endif
Annexe B
523
La premire fois que le fichier den-tte est inclus, le symbole MYHEADERFILE_H nest pas
dfini, donc le compilateur traite le code entre #ifndef et #endif. La seconde et toutes les
fois suivantes o le fichier den-tte est inclus, MYHEADERFILE_ H est dj dfini, donc le bloc
#ifndef #endif complet est ignor.
Contrairement la plupart des autres constructions C++, pour lesquelles les espaces ntaient
pas significatifs, les directives de prprocesseur doivent apparatre sur leur propre ligne, sans
point-virgule la fin. Les directives trs longues peuvent occuper plusieurs lignes en terminant
chacune delles sauf la dernire par un slash invers (\).
524
Fichier den-tte
Description
<bitset>
<complex>
<exception>
<limits>
<locale>
<new>
<stdexcept>
<string>
<typeinfo>
<valarray>
Figure B.3
Principaux fichiers den-tte de la bibliothque C++
Fichier den-tte
Description
<fstream>
<iomanip>
<ios>
<iosfwd>
<iostream>
<istream>
Classe modle qui contrle les entres en provenance dune mmoire tampon de flux
<ostream>
Classe modle qui contrle les sorties vers une mmoire tampon de flux
<sstream>
Classes modle qui associent des mmoires tampon de flux avec des chanes
<streambuf>
<strstream>
Classes pour effectuer des oprations de flux dE/S sur des tableaux de caractres
Figure B.4
Fichiers den-tte de la bibliothque dE/S C++
Annexe B
525
Le Chapitre 12 consacr aux entres/sorties dtaille les classes Qt correspondantes, qui prsentent les entres/sorties Unicode ainsi quun jeu important de codages de caractres nationaux et
une abstraction indpendante de la plate-forme pour stocker les donnes binaires. Les classes
dE/S de Qt forment la base des communications interprocessus, de la gestion de rseau, et du
support XML de Qt. Les classes de flux de texte et binaire de Qt sont trs faciles tendre pour
prendre en charge les types de donnes personnaliss.
La bibliothque STL (Standard Template Library) existe depuis les annes 90 et propose un
jeu de classes conteneur, itrateurs et algorithmes bass sur des modles, qui font maintenant
partie de la norme standard ISO C++.
La Figure B.5 numre les fichiers den-tte de la STL. La conception de cette bibliothque est
rigoureuse, presque mathmatique, et elle fournit une fonctionnalit gnrique scurise au
niveau des types. Qt fournit ses propres classes conteneur, dont la conception sinspire de celle
de la STL. Ces classes sont dtailles au Chapitre 11.
Fichier den-tte
Description
<algorithm>
<deque>
<functional>
<iterator>
<list>
<map>
<memory>
<numeric>
<queue>
<set>
<stack>
<utility>
<vector>
Figure B.5
fichiers den-tte de la STL
C++ tant surtout une version amliore du langage de programmation C, les programmeurs
C++ disposent aussi de la bibliothque C complte. Les fichiers den-tte C sont disponibles
526
soit sous leurs noms traditionnels (par exemple <stdio.h>) soit sous leur forme moderne avec
le prfixe c- et sans lextension .h (<cstdio>, par exemple). Lorsque nous codons avec le nom
moderne, les fonctions et types de donnes sont dclars dans lespace de noms std. (Cela ne
sapplique pas des macros telles que ASSERT(), parce que le prprocesseur ne connat pas les
espaces de noms.) La nouvelle syntaxe est recommande si votre compilateur la prend en charge.
La Figure B.6 rpertorie les fichiers den-tte C. La plupart proposent une fonctionnalit qui
recouvre celle de fichiers den-tte C++ ou de Qt plus rcents. Notez que <cmath> est une
exception, ce fichier dclare des fonctions mathmatiques telles que sin(), sqrt(), et pow().
Fichier den-tte
Description
<cassert>
La macro ASSERT()
<cctype>
<cerrno>
<cfloat>
<ciso646>
<climits>
<clocale>
<cmath>
<csetjmp>
<csignal>
<cstdarg>
<cstddef>
<cstdio>
<cstdlib>
<cstring>
<ctime>
<cwchar>
<cwctype>
Figure B.6
Fichiers den-tte C++ pour les utilitaires de bibliothque C
Annexe B
527
Index
Symboles
#define 487, 521
#endif 445, 487
#ifdef 445
#ifndef 487
#include 486, 521
#undef 521
& 18, 497
() 94
<algorithm> 276
<cmath> 125
<iostream> 86
<QtAlgorithms> 276
<QtGui> 18, 48
A
About Qt 53
about() (QMessageBox) 70
accept() 411
QDialog 31, 36
Acceptable (QSpinBox) 107
acceptProposedAction() 215
acquire() 414, 415
Action 51
About Qt 53
Auto-Recalculate 52
exitAction 53
New 51
Open 52
Save 52
Save As 52
Select All 52
Show Grid 52
activateWindow() 65
Active (groupe de couleurs) 115
activeEditor() 162
ActiveQt 448
ActiveX 460
sous Windows 448
addAction() (QMenu) 54
addBindValue() 313
addCd() 326
addChildSettings() 235
addDatabase() 311, 314
addItem() (QComboBox) 241
addLibraryPath() 435
addMenu() (QMenu) 54
AddRef() 456
addRow() 233
addStretch() 148
addTrack() 327
addTransaction() 420-421
addWidget() 147, 151
QStatusBar 57
adjust() 139
PlotSettings 134
adjustAxis() 139
adjusted() 130
adjustSize() 127
Aide en ligne 373
Assistant Qt 379
avec lassistant 379
infobulle 374
informations dtat 374
QTextBrowser 376
Quest-ce que cest ? 374
Algorithme
gnrique 276
qBinaryFind 277
qCopy 277
QCopyBackward()
285
qDeleteAll 278
qEqual() 285
qFill 277
qFind 276
qSort 277
qStableSort 278
qSwap 278
AlignHCenter 57
alignment 137
Anticrnelage 186, 189
bas sur X Render 197
AnyKeyPressed (QAbstractItemView) 231
API natives 444
append() 265
QString 279
Apple Developer Connection
479
530
Application
Age 7
capable de grer les plug-in
435
Carnet dadresse 458
CD Collection 317
Cities 246
Color Names 240
Coordinate Setter 232
Currencies 243
Directory Viewer 238
crire des plug-in 439
Flowchart Symbol Picker
230
Image Converter 303, 353
Image Pro 419
implmenter la fonctionnalit
77
infobulle et information
dtat 374
Mail Client 154
MDI Editor 160
ouverte aux traductions 390
Project Chooser 216
Quit 7
Regexp Parser 251
Settings Viewer 234
Splitter 152
Spreadsheet 46, 92
Team Leaders 236
Tetrahedron 207-208
Threads 409
Tic-Tac-Toe 462
traduire 402
Trip Planner 343
Windows Media Player 448
apply() 422-423
applyEffect() 438-440
appTranslator 398, 401
Architecture modle/vue 228
arg() 279, 392
QString 62
ARGB32 197
arguments() 330
QApplication 168
Assistant Qt 10, 379
asVariant() 452
at() 271
Attribut
WA_DeleteOnClose 74,
162
WA_StaticContents 117
Auto-Recalculate 52, 71, 75, 79
avg() 104
B
BackgroundColorRole 242, 256
Barre dtat, configurer 56
Barre doutils
ancrable 157
crer 51
Base de donnes 309
connexion et excution de
requtes 310
formulaires 321
prsenter les donnes 317
beforeInsert() 320, 325
begin() 269
beginGroup() 71
Bibliothque
C++ standard 523
dynamique 425, 488
E/S C++ 524
OpenGL 207
statique 488
STL 63
bindValue() 313
Bote de dialogue
About 69
conception rapide 25
crer 15
de feedback 44
dynamique 39
tapes de cration 26
extensible 33
Find 16
Go-to-Cell 26
intgre 40
multiforme 32
multipage 39
QDialog (classe de base) 16
Sort 33
utiliser 64
bool 489
Boucle
dvnement 4, 178
foreach 271
while 417
boundingRect() (QPainter) 204
break 273
BufferSize 416
buttons() (QMouseEvent) 117
C
C#
diffrences avec C++ 489
diffrences avec Java 483
C++
chanes de caractres 505
dfinitions de classe 490
destructeur 496
diffrences avec Java et C#
489
numrations 507
espaces de noms 518
hritage 493
pointeurs 497
polymorphisme 493
prprocesseur 520
prsentation 483
surcharge doprateur 512
tableaux 502
typeDef 509
types valeur 514
cachedValue 97, 100
cacheIsDirty 97, 98
cacheIsValid 100
canRead() 428, 429
Index
canReadLine() 352
capabilities() 427
CanRead 428
CanReadIncremental 428
CanWrite 428
Capuchon 185
cascade() 164
CaseSensitivity 17
cd() 333
cdModel 325
cdTableView 325, 327
Cell 79, 81, 90, 96
arbre dhritage 79
cell() 83, 90
cerr 486
Chanes de caractres 505
changeEvent() 401
char 386, 489
characters() 362-363
Chargement 85
charset 224
checkable 33
Classe
accs alatoire
QBuffer 288
QFile 288
QTemporaryFile 288
conteneur 263
itrateurs 267
QBrush 271
QByteArray 266, 271
QCache 275
QDateTime 266
QFont 271
QHash 264, 273
QImage 271
QLinkedList 264-266
QList 264
QList<T> 266
QMap 264, 273
QPair 285
QPixmap 271
QRegExp 266
QSet 275
QString 266, 271
QVariant 266
QVarLengthArray
<T,Prealloc> 285
QVector 264, 266
daffichage dlments 227
QListWidget 228
QTableWidget 228
QTreeWidget 228
utiliser 229
dlments 82
de modle 244
QAbstractItemModel
244
QAbstractListModel
244
QAbstractTableModel
244
de widgets 40
QByteArray 278
QString 278
QVariant 278
squentielle
QProcess 288
QTcpSocket 288
QUdpSocket 288
TextArtDialog 437
wrapper de plug-in 426
Cl 71
clear() 81, 82, 252, 278, 400
Spreadsheet 58
clearCurve() 129
clicked() 7, 170, 304
Client
FTP 330
HTTP 339
TCP 342
ClientSocket 349-350
clipboard() 88, 224
clone() 98, 514
close() 19, 61, 74, 164, 333
closeActiveWindow() 164
closeAllWindows() 74, 164
closeConnection(), TCP 348
closeEditor() 260
closeEvent() 70, 164, 167
531
MainWindow 74
QWidget 47, 61
codecForName() 389
CodeEditor 171
ColorGroup 115
ColorNamesDialog 240
column() (QModelIndex) 242
columnCount() 82, 244
columnSpan 148
COM, objets 448
commit() 313
commitAndCloseEditor() 259
commitData() 464
Communication inter-processus
303
Compagnon 18
compare() 68, 94-95
Compilateur
excution 486
macros 446
MinGW C++ 478
moc 22
uic 29
units 484
Composition (mode) 188, 198
CompositionMode_SourceOver
198
CompositionMode_Xor 199
connect() 19, 192, 419, 423
QObject 9, 23
connected() 344, 345
connectionClosedByServer() 349
connectToHost() 333, 345
connectToServer() 345
Connexion
aux bases de donnes 310
de requtes 310
tablir 6
signal/slot 304
const 499
const_cast<T>() 510
constData() 283
Constructeur de copie 514
contains() (QRect) 117
532
Conteneur
associatif 264, 273
QCache 275
QHash<K,T> 264, 273
QMap<K,T> 264, 273
QSet 275
copie 270
squentiel 264
QLinkedList 266
QLinkedList<T> 264265
QList<T> 264, 266
QQueue<T> 266
QStack<T> 266
QVector 264
QVector<T> 264, 266
contentsChanged() 166-167
contextMenuEvent() (QWidget)
55, 67
ContiguousSelection 81, 89
continue 273
Contrle ActiveX 448
controllingUnknown() 456
Conversions de type 509
ConvertDepthTransaction 422
convertTo1Bit() 420
convertTo32Bit() 420
convertTo8Bit() 420
convertToFormat() 112
Coordonnes
conversion logiquesphysiques 190
systme par dfaut 188
copy() 88
CopyAction 218
copyAvailable() 162
count() 265
Courbe de Bzier 186
cout 486
Crayon 185
create() 121, 427-428
createActions() 49, 376, 398
createConnection() 311, 313
createContextMenu() 49
createEditor() 161, 258, 260
createIndex() 249
createLanguageMenu() 399-400
createMenus() 49, 164, 398
createStatusBar() 49, 56
createToolBars() 49
critical() (QMessageBox) 59
curFile 62, 167
currencyAt() 246
CurrencyModel 243
currentCdChanged() 324
currentCellChanged() 57
currentDateTime() (QDateTime)
193
currentFormula() 84
currentIndex() (QComboBox) 68
currentItem 364
currentLocation() 84
currentRow 151
currentRowChanged() 151
currentText 363
currentThread() 417
Curseur 452
CursorHandler 428, 434
curveMap 129
curZoom 127
CustomerInfoDialog 176
cut() 88
Editor 163
D
data() 99, 244-245, 283
QMimeData 219
QTableWidgetItem 96
dataChanged() 249
QClipboard 225
Datagramme UDP 353
DataSize 416
debug 5
Dclaration pralable 16
default 33
defined() 522
Dfinition de classe 490
Dgrad
circulaire 188
conique 188
linaire 187
del() 90
Dlgu 228
personnalis 256
delete 90, 252
deleteLater() 350
delta() 135
Drivation
QDialog 16
QMainWindow 46
QMimeData 224
QTableWidget 78
QTableWidgetItem 96
QWidget 108
Dessin
courbe de Bzier 186
crayon 185
dgrads
circulaires 188
coniques 188
linaires 187
formes gomtriques 186
pinceau 185
police 185
QPainter 184
rectangle sans anticrnelage
189
styles
de capuchon et jointure
185
de crayon 185
trac 187
viewport 189
DiagCrossPattern 187
Dinkumware 527
Directives 445
#include 486
de prprocesseur 487
Disabled (groupe de couleurs)
115
disconnect() 423
Index
drawPie() 186
drawPolygon() 195
drawPolyline() 138
drawPrimitive() 130
QStylePainter 130
drawRect() 189
drawText() 137, 196
conversion des coordonnes
190
dropEvent() 214
glisser-dposer 215
dumpdoc 451
duration() 193
dynamic_cast<T>() 510
dynamicCall() 452
E
E_NOINTERFACE 456
Echelon 140
Ecriture
code XML 370
donnes binaires 288
texte 294
Editeur
de connexion (Qt Designer)
37
de liens 485
editingFinished() 259
Editor 164
cut() 163
fonctions 165
save() 162
EditRole 97-99, 242
effects() 436, 439
Ellipse (dessiner) 186
emit 21
enabled 27
enableFindButton() 19, 21
end() 269
conteneur non const 271
endElement() 360, 362, 364
endGroup() 71
endl 486
533
endsWith() 281
enterErrorState() 431
Entres/sorties 287
lire et crire des donnes
binaires 288
lire et crire du texte 294
parcourir les rpertoires 300
entryHeight() 203, 204
entryList() 301
Enumrations 507
Equivalence pointeurs/tableaux
503
eraseRect() 440
error() 306, 344, 349
errorString() 363
escape() 221
Espace de noms 518
std 125
Espacement 20
evalExpression() 100-101, 104
evalFactor() 101, 104
evalTerm() 101-104
Evnement 4
boucle 178
close 164
drop 214
filtre 175, 177
gestionnaires, rimplmenter
170
key 170
paint 113, 118
propagation 178
resize 131
timer 172
traiter 169, 177
versus signal 170
wheel 135
event() 170
eventFilter() 176
exec() 66, 312, 418
QApplication 178
QDialog 66
QPrintDialog 200
requte SQL 311
534
Excutable 485
intgrer des donnes 302
execute() 307
QProcess 307
exitAction 53
expand() 239
Expanding 149
explicit 511
Expression 101
rgulire 250
syntaxe 101
extern 517
F
faceAtPosition() 211-212
fatalError() 362, 364
Fentre
dapplication
QDialog 4
QMainWindow 4
dessin 188
modale 66
non modale 64
principale
configurer 49
crer 45
Fichier den-tte 18, 48
<QtAlgorithms> 276
<QtGui> 18
dfinition 487
Fichier objet 485
fill() (QPixmap) 136
fillRect() (QPainter) 116
Filtre
cin en cout 299
dvnement 446
installer 169, 175
entres du systme
de fichiers 238
find() 444
findChild<T>() (QObject) 40
findClicked() 19, 21
G
generateDocumentation() 451
generateId() 320
generateRandomTrip() 351
geometry() (QWidget) 72
Gestion de session
tests et dbogage 467
X11 461
Gestionnaire
dvnements
endElement() 360
rimplmenter 170
startElement() 360
de disposition 9, 146
QGridLayout 9, 146
QHBoxLayout 9, 146
QVBoxLayout 9, 146
get() 330, 332-333, 337
oprations HTTP 339
getColor() (QColorDialog) 211
getDC() 445
getDirectory() 335
getFile() 330, 332
oprations HTTP 340
GetInterfaceSafetyOptions() 456
getOpenFileName()
(QFileDialog) 59
getSaveFileName()
(QFileDialog) 61
GL_SELECT 212
glClearColor() 209
glClearIndex() 209
glColor3d() 210
glIndex() 210
Glisser, types personnaliss 219
Glisser-dposer 213
activer 214
QTableWidget 219
GNU (General Public License)
478
Go to Cell 27
GoToCellDialog 27
Index
Graphique
2D 183
3D 183
OpenGL 207
group() 120
Groupe de couleurs
Active 115
Disabled 115
Inactive 115
guidgen 457
GuiServer 471
H
Handle 444, 446
hasAcceptableInput()
(QLineEdit) 32
hasFeature() 313
hasLocalData() 418
hasNext() 267
hasPendingEvents() 181
hasPrevious() 268
head() 341
headerData() 244-245, 248, 256
Hello Qt 4
HelpBrowser 377, 380
Hritage 493
HexSpinBox 106, 108, 118
hide() 128
hideEvent() 173, 175
hostName 392
I
IANA (Internet Assigned
Numbers Authority) 215
icon() 120
IconEditor 109, 115, 120, 122
pixelRect() 115
IconEditorPlugin 120
iconForSymbol() 231
iconImage() 109-110, 113
Identifiant de la fentre 444
IgnoreAction 218
image() 110, 421
presse-papiers 224
imageCount() 433
imageSpace() 300-301
ImageWindow 420
Impression 199
QPainter 205
QTextDocument 202
Inactive (groupe de couleurs) 115
includeFile() 120
incomingConnection() 349-350
index() 253
de modle 249
indexOf() 150, 280
Infobulle 374
information() (QMessageBox) 59
initFrom() 130, 136, 198
initializeGL() 208-209
insert() 237, 281
avec itrateur 269
dans map 273
insertMulti() 274-275
insertRow() 233, 315
installEventFilter() 176-177
instance() (QPluginLoader) 438
Instruction
DELETE 314
GET 337
if 417
INSERT 313-314
SELECT 312, 314
UPDATE 314
int 312, 489
Interface avec les API natives 444
Intermediate 107
QSpinBox 107
Internationalisation 385
passer dynamiquement
dune langue lautre 396
traduire les applications 390,
402
Unicode 386
535
Internet Explorer
accs laide 380
intgrer un widget 453
options de scurit
du composant 455
Introspection 25
Invalid 107
QSpinBox 107
invokeMethod() 424
IObjectSafety 455
isActive() 312
isalpha() 387
isContainer() 121
isdigit() 387
isEmpty() 282
conteneur 269
isLetter() 387
isLetterOrNumber() 387
isLower() 387
isMark() 387
isNumber() 387
isPrint() 387
isPunct() 387
isSessionRestored() 467
isSpace() 387
isSymbol() 387
isUntitled 165, 167
isUpper() 387
item->text() 231
itemChanged() 81
ItemIsEditable 249
Itrateur
de style Java 267
de style STL 269
mutable 268
pour liste chane 265
J
Java
diffrences avec C# 483
diffrences avec C++ 489
machines virtuelles 470
536
join() 282
Jointure 185
JournalView 401
jumpToNextImage() 433
K
KDE XIV
fermeture de session 461
projet XVIII
key() 138, 169, 246, 275-276
map 275
keyPressEvent() 135, 170-171,
175
Plotter 139
keyReleaseEvent() 170
keys() 274, 427
itrateur STL 276
killTimer() (QObject) 175
Klarlvdalens Datakonsult 444
L
LanguageChange 402
LargeGap 203
Latin-1 386
LD_LIBRARY_PATH 481
leaders() 238
Lecture
code XML 360, 365
donnes binaires 288
texte 294
left() 280, 283
LeftDockWidgetArea 158
length() 279, 282
LinkAction 218
list() 333
Liste chane 265
load() 394, 401
loadFile() 59, 64
loadPlugins() 437-438
M
Mac OS X
identifier la version 446
installer Qt 479
macEvent() 448
macEventFilter() 448
Macro
de compilateur 446
Q_ OBJECT 47
Q_DECLARE_INTERFACE
() 436
Q_DECLARE_METATYP
E() 284
Q_EXPORT_PLUGIN2()
121, 428, 441
Q_INTERFACES() 120,
439
Q_OBJECT 17-18, 21,
110, 390, 439
Q_PROPERTY() 109
qPrintable() 283, 289
QT_TR_NOOP() 392
QT_TRANSLATE_NOOP()
393
SIGNAL() 7, 23
signals 17
SLOT() 7, 23
slots 17
MagicNumber 86, 292
mainSplitter 154
MainWindow 399
changeEvent() 401
closeEvent() 74
drivation de QMainWindow
47
glisser-dposer 214
newFile() 82
sort() 69
switchLanguage() 401
updateStatusBar() 84
make 5, 479
Makefile 5, 22, 29
Manipulateur de flux 295
Masque
AND 431
XOR 431
Matrice world 190
dessin 188
MDI (Multiple Document
Interface) 75, 159
Mcanisme
des ressources 50
fentre-viewport 189
parent-enfant 31
MediumGap 204
Mmoire dimage 470
Menu
Bar() (QMainWindow) 53
crer 51
Edit 88
File 57
Options 92
Tools 92
Window 160
message() 423
metaObject(), dclaration avec
Q_OBJECT 25
Mta-objets 25
mid() 280, 283
QString 66
Migration de Motif/Xt et MFC
vers Qt 444
MIME (type) 215
mimeData() 224
MinGW C++ 478
minimumSizeHint() 124, 129,
149
Index
mirrored() 423
Mise en veille 462
mkdir() 239, 333
moc 22, 25
Mode de composition 188, 198
Modle
arborescence 242
index 249
liste 242
personnalis 241
prdfini 236
tableau 242
modified() 80, 85
modifiers() (QKeyEvent) 135
Module
QtCore 18
QtGui 18
QtNetwork 18
QtOpenGL 18, 207
QtSql 18
QtSvg 18
QtXml 18
mouse 169, 208
MouseButtonPress 170
mouseMoveEvent() 117, 136,
210
mousePressEvent() 117, 136,
170, 192, 210, 465
QListWidget 217
QWidget 116
mouseReleaseEvent() 136, 139
collage avec le bouton du
milieu de la souris 225
move() (QWidget) 72
MoveAction 218
moveToThread() 419
Multithread 407
mutable 97, 101
Mutex 411
emploi 413
MVC (Modle-Vue-Contrleur)
228
N
name() 120
New 51
newFile() 51, 58, 161, 166
MainWindow 73, 82
newPage() 199, 200, 205
next() 267, 268
map 275
nextBlockSize() 344-345, 347,
350
nextSibling() 369
nmake 5
NoBrush 187
Node 250, 254
nodeFromIndex() 253-254
Nud 251
normalized() (QRect) 130, 133
notify() (QApplication) 178
number() (QString) 107
numCopies() (QPrinter) 205
numRowsAffected() 312
numTicks 140
numXTicks 125
numYTicks 125
537
P
O
objectName 27
Objet
COM 448
compare 68, 94
QSettings 72
SpreadsheetCompare 68
offset 173, 174
offsetOf() 250
okToContinue() 58, 165, 167
on_browseButton_clicked() 304
on_convertButton_clicked() 304
on_lineEdit_textChanged() 32
Open 52
Open source 478
open() 59, 162, 166
QTemporaryFile 307
Page daccueil 75
paginate() 202-203
paint 113
paintEngine() 445
Painter, transformations 188
paintEvent()
anticrnelage 197
copie de pixmap 136
dfinition du viewport 194
IconEditor 113
implmentation de event()
170
OvenTimer 192
rimplmentation
TicTacToe 465
widget Ticker 174
paintGL() 208-209
538
plugins 122
Pointeur
rticule 132
C++ 497
conteneur 252
dattente standard 86
de fichier 288
de nud 256
glisser-dposer 218
nul 17
QObject 64
QPointer 498
toupie 7
hexadcimal 106
vers la barre dtat 56
void 83
Police 185
Polymorphisme 493
pop() 266
populateListWidget() 437-438
pos 100
post() 339, 341
postEvent() 423
Preferred 149
preferredType 223
prepare() 312
prepend() 62
Prprocesseur 488, 520
Presse-papiers
de slection 224
grer 224
previous() 268
map 275
printBox() 206
printFlowerGuide() 202
printHtml() 201
printImage() 200
printPages() 202, 205
PrintWindow 200
pro (fichier) 122
processEvents() (QApplication)
179
processNextDirectory() 335, 337
processPendingDatagrams() 356
Q
Q_ OBJECT 47
Q_CLASSINFO() 459
Q_DECLARE_INTERFACE()
436
Q_ENUMS() 449
Q_EXPORT_PLUGIN2() 121,
428
Q_INTERFACES() 120, 439
Q_OBJECT 17-18, 21, 110,
390, 439
Q_OS_UNIX 446
Q_OS_WIN 446
Q_PROPERTY() 109
Q_WS_ X11 446
Q_WS_MAC 446
Q_WS_QWS 446
Q_WS_WIN 446
qAbs() 278
QAbstractItemDelegate 258
QAbstractItemModel 244, 252
reset() 246, 250
QAbstractItemView 52, 156,
218
AnyKeyPressed 231
ContiguousSelection 81, 89
NoEditTriggers 233
selectAll() 91
setEditTriggers() 231
QAbstractListModel 244
Index
QAXFACTORY_EXPORT()
459
QAxObject 449
QAxServer 448, 453
qaxserver.def 457
qaxserver.rc 457
QAXTYPE() 459
QAxWidget 449
setControl() 451
qBinaryFind() 264, 277
QBitArray 431
QBrush 116, 186
QBuffer 86, 288
fichiers tlcharger 339
QByteArray 218-219, 222, 266,
278, 288, 346, 352
QCDEStyle 130
QChar 386
isDigit() 387
isLetter() 387
isLetterOrNumber() 387
isLower() 387
isMark() 387
isNumber() 387
isPrint() 387
isPunct() 387
isSpace() 387
isSymbol() 387
isUpper() 387
QCheckBox 40, 147, 178
QClipboard
dataChanged() 225
Selection 224
setText() 88
QCloseEvent, accept() 411
QColor 111, 116
QColorDialog 208
getColor() 211
QComboBox 158
addItem() 241
currentIndex() 68
QComboBoxes 176
qCompress() 294
qconfig 472
539
QCOP 471
QCopChannel 471
qCopy() 277
QCoreApplication 330, 418
addLibraryPath() 435
arguments() 330
postEvent() 423
removePostedEvent() 423
QDataStream 85-86, 288, 290,
351, 431, 490
numro de version 292
oprations TCP 342
Qt_4_1 86
skipRawData() 431
QDate (toString) 395
QDateEdit 395
QDateTime 266
currentDateTime() 193
toString 395
QDateTimeEdit 395
qDeleteAll() 252, 278
QDesignerCustomWidgetCollectionInterface 122
QDesignerCustomWidgetInterface 119
IconEditorPlugin 120
QDialog 4, 16, 31, 178
driver 16
exec() 66
Rejected 66
QDir 300, 301
entryInfoList() 301
entyList() 301
exists() 301
imageSpace() 301
mkdir() 301
rename() 301
rmdir() 301
QDirectPainter 471
QDirModel 236, 238, 240
mkdir() 239
QDockWidget 157
setFeatures() 157
QDockWindow 157
540
QDomDocument
documentElement() 367
save() 370
setContent() 367
QDomElement 369
QDomNode 368
firstChild() 369
nextSibling() 369
tagName() 368
toElement 368
QDoubleValidator 31
QDrag 217
start() 218
QDragEnterEvent 218
QEvent 170
MouseButtonPress 170
type() 170
QFile 85-86, 288, 301, 336
exists() 301
remove() 301
QFileDialog
DontConfirmOverwrite 61
getOpenFileName() 59
getSaveFileName() 61
QFileInfo 301
qFill() 277
qFind() 276
QFont 186, 288, 292
QFontMetrics 174
size() 174
QFrame 41, 121
QFtp 329
cd() 333
close() 333
commandFinished() 333
commandStarted() 333
ConnectToHost 333
done() 332
ftpListInfo() 335
get() 330, 333
list() 333
listInfo() 335
login() 333
mkdir() 333
Index
setText() 424
statut de la dernire
opration 343
QLatin1String() 393
QLayout 146
SetFixedSize 38
setMargin() 147
setSpacing() 147
QLibrary 425
QLineEdit
activer avec barre despace
175
bote de dialogue Find File
147
entres de donnes 42
hasAcceptableInput() 32
stocker une chane de filtre
240
text() 66
QLineEdits 355
QList 274
QListView 229, 237, 314
QListWidget 39, 151, 228, 231,
435
glisser-dposer 215
QListWidgetItem 231, 439
QLocale 395
System() 394
qlonglong 490
QMacStyle 130
QMainWindow 4, 78, 144, 157
driver 46
menuBar() 53
statusBar() 56
qmake 5, 21-22, 29, 479
QMatrix 191
qMax() 278
QMenu 53
addAction() 54
addMenu() 54
QMenuBar 4
QMessageBox
about() 70
critical() 59
Default 58
Escape 58
information() 59
question() 59
warning() 58, 70
QMetaObject 25
invokeMethod() 424
QMimeData 213, 217
conversion en TableMimeData 224
data() 219
driver 224
glisser-dposer 219
setData() 219
text() 219
urls() 215
qMin() 194, 278
QModelIndex 237, 240, 245,
253
column() 242
parent() 242
row() 242
QMotifStyle 130
QMouseEvent 170
buttons() 117
QMovie 427
qmPath 398
QMultiMap 274
QMutex 407, 409, 411, 416,
423
QMutexLocker 412, 423
QObject 24, 266, 424, 439
connect() 9, 419, 423
deleteLater() 424
disconnect() 423
event() 170
filtre dvnement 177
findChild() 40
IconEditorPlugin 120
killTimer() 175
mcanisme parent-enfant 31
moveToThread() 419
QProcess 424
QTimer 424
541
sender() 64
setProperty() 451
startTimer() 174
tr() 18, 390
qobject_cast() 64, 218, 224, 510
QPaintDevice 445
QPainter 114, 136, 183
boundingRect() 204
dessiner 184
sur QPrinter 200
draw...() 184
drawLine() 114
fillRect() 116
imprimer 205
programmation embarque
471
rotate() 191
scale() 191
setCompositionMode() 198
shear() 191
systme de coordonnes 188
translate() 191
QPainterPath 186
QPalette 115
ColorGroup 115
foreground() 115
QPen 186
QPixmap 124, 288, 439
fill() 136
QPlastiqueStyle 130
QPluginLoader 438
QPoint 114
QPointer 498
QPointF 138
qPrintable() 289
QPrintDialog 200
choix de limprimante 199
options dimpression 205
setEnabledOptions() 205
QPrinter 200
fromPage() et toPage() 205
newPage() 199
numCopies() 205
setPrintProgram() 199
542
chane
de caractres 507
Unicode 387
comparaison 277
concatnation 279
emploi comme conteneur
278
localeAwareCompare() 395
menu Edit 88
mid() 66
number() 107
partage implicite 272
replace() 221
split() 90, 282
sprintf() 279
toInt() 66, 107
toUpper() 107
type de valeur 266
QStringList 94, 168, 201
arguments de ligne
de commande 330
concatnation 282
fichiers tlcharger 338
recentFiles 63
takeFirst() 297
QStringListModel 237, 240
QStyle 130
PE_FrameFocusRect 130
QStyleOptionFocusRect 130
QStylePainter 130
qSwap() 250, 278
Qt
AlignHCenter 57
BackgroundColorRole 242,
256
CaseInsensitive 17
CaseSensitive 17
classe dlments 82
DiagCrossPattern 187
DisplayRole 231, 242
EditRole 231, 242
FontRole 242, 256
IconRole 231
ItemIsEditable 249
Index
LeftDockWidgetArea 158
mcanisme des ressources
50
NoBrush 187
SolidPattern 187
StatusTipRole 242
StrongFocus 126
systme de mta-objets 25
TextAlignmentRole 242,
256
TextColorRole 242, 256
ToolTipRole 242
UserRole 231
WA_DeleteOnClose 166
WA_GroupLeader 378
WA_StaticContents 110
WaitCursor 132
WhatsThisRole 242
Qt Designer 26
crer
des widgets personnaliss 108
un formulaire 151
diteur de connexions 37
intgrer des widgets personnaliss 118
Qt Linguist
excution 403
prsentation 403
Qt Quaterly 12
Qt/Embedded 469
Qt/Mac (installer) 479
Qt/Windows (installer) 478
Qt/X11(installer) 479
qt_metacall(), dclaration avec
Q_OBJECT 25
QT_TR_NOOP() 392
QT_TRANSLATE_NOOP() 393
QTableView 93, 229, 314, 320
QTableWidget 78, 228, 232, 401
ajouter le glisser-dposer
219
attributs QTableWidgetItem
79
driver 78
implmentation 108
oprations TCP 343
QHeaderView 82
QScrollBar 82
selectColumn() 91
selectedRanges() 89
selectRow() 91
setCurrentCell() 66
setItem() 84, 232
setItemPrototype() 98
sous classe Spreadsheet 49
Track Editor 257
widgets constitutifs 82
QTableWidgetItem 79, 82, 90,
222, 232, 235
data() 96, 99
driver 96
text() 96
QTableWidgetSelectionRange
222
QTabWidget 39, 41
QtCore 18, 299
QTcpServer 329, 342
QTcpSocket 86, 288, 329, 341,
342
canReadLine() 352
connected() 345, 346
connectionClosedByServer() 349
disconnected() 349
error() 349
listen() 352
readClient() 351
readLine() 352
readyRead() 347
seek() 346
updateTableWidget() 347
write() 346
QTDIR 122
QTemporaryFile 288, 307
QTextBrowser 42, 379
moteur daide 376
QTextCodec 388
543
codecForName() 389
setCodecForCStrings() 389
setCodecForTr() 389, 403
QTextDocument 202
QTextEdit 42, 78, 149, 156, 307
glisser-dposer 214
QTextStream 86, 288, 294, 299,
388
AlignAccountingStyle 296
AlignCenter 296
AlignLeft 296
AlignRight 296
FixedNotation 296
ForcePoint 296
ForceSign 296
internationalisation 388
oprations
TCP 342
XML 370
readAll() 295
readLine() 295
ScientificNotation 296
setRealNumberNotation()
296
SmartNotation 296
UppercaseBase 296
UppercaseDigits 296
QtGui 18
QThread 407
currentThread() 417
exec() 424
run() 408
terminate() 409
wait() 411
QThreadStorage 423
QTime (toString) 395
QTimeEdit 259, 323, 326
Track Editor 260
QTimer 175, 192, 424
sendDatagram() 354
QtNetwork 18
QToolBar 4
QToolBox 41
QToolButton 40, 127, 158
544
QIcon 284
QImage 284
QKeySequence 283
QPalette 283
QPen 283
QPixmap 283, 284
QPoint 283
QRect 283
QRegion 283
QSize 283
QString 283
QVBoxLayout 9, 19, 143, 146,
147
QVector 124, 138
qvfb 471
QWaitCondition 407, 411, 416,
423
wait() 417
QWhatThis, createAction() 376
QWidget 7, 78
changeEvent() 401
close() 19, 61
closeEvent() 47, 61
contextMenuEvent() 55, 67
driver 108
event() 171
find() 444
fontMetrics() 174
geometry() 72
mousePressEvent() 116
move() 72
palette() 115
QGLWidget 207
repaint() 113
resize() 72
scroll() 175
setCursor() 132
setGeometry() 72
setLayout() 9
setMouseTracking() 117
setStyle() 130
setTabOrder() 22
setToolTip() 374
setWindowIcon() 49
setWindowTitle() 8
sizeHint() 20, 38, 57
style() 130
update() 112
updateGeometry() 112
windowModified 62
winID() 444
QWindowsStyle 130
QWindowsXPStyle 130
QWorkspace 78, 144, 159, 167
QWriteLocker 413, 423
QWS_DEPTH 472
QWS_KEYBOARD 471
QWS_MOUSE_PROTO 471
QWS_SIZE 472
qwsEvent() 448
qwsEventFilter() 448
QWSServer 473
qwsServer (variable globale) 473
QXmlContentHandler 362, 365
endElement() 360
startElement() 360
QXmlDeclHandler 360
QXmlDefaultHandler 361-362
errorString() 363
QXmlDTDHandler 360
QXmlEntityResolver 360
QXmlErrorHandler 360, 362,
365
QXmlInputSource 365
QXmlLexicalHandler 360
QXmlSimpleReader 360, 365
R
range.rightColumn() 68
Rational PurifyPlus 490
rawCommand() 333
Ractivit et traitement intensif
178
read() 433
QHttp 342
Index
readAll() 295
QHttp 342
QIODevice 293
readBitmap() 434
readDatagram() 357
readFile() 87
Spreadsheet 60
readHeaderIfNecessary() 430,
433
readLine() 295, 297, 352
ReadOnly (QIODevice) 87
readRawBytes() 290, 291
readSettings() 49, 70-71, 155,
234, 235
readyRead() 339, 342, 344
recalculate() 92, 93
recentFileActions 52
recentFiles 62, 63
record() 327
rect() (QImage) 117
Rfrence 500
refreshPixmap() 128, 131, 135
refreshTrackViewHeader() 324,
327
regExpChanged() 256
RegExpModel 250, 256
RegExpParser 250, 256
RegExpWindow 250
Registre systme
enregistrement
dun serveur ActiveX
460
du serveur 457
stockage des paramtres 71
regsvr32 457
reinterpret_cast() 511
reject() (QDialog) 31, 36
release() 5, 456
releaseDC() 445
remove() 281, 333
avec itrateur 268
removeAll() 62
removePostedEvents() 423
removeRows() 237, 316
rename() 333
repaint() (QWidget) 113
Rpertoire (parcours) 300
replace() 281
QString 221
Reprsentation binaire des types
86
requestFinished() 342
requestPropertyChange() 455
requestStarted() 342
Rseau
envoi et rception de datagrammes UDP 353
gestion 329
programmer
les application client/
serveur TCP 342
les clients FTP 330
les clients HTTP 339
reserve() 275
reset() (QAbstractItemModel)
246, 250
resize() (QWidget) 72, 127, 131
resizeEvent() 131, 145, 146
resizeGL() 208, 209
resizeImage() 420
ResizeTransaction 422
Ressources (intgration) 302
restore(), matrice de transformation 196
restoreState() (QMainWindow)
159
retranslateUi() 397, 398
retrieveData() 222, 223
right() 280, 283
rightSplitter 154
rmdir() 333
Rle 231, 241
DisplayRole 97
EditRole 97
rollback() 313
rotate() 191, 196
row() (QModelIndex) 242
rowCount() 82, 244
545
rowSpan 148
rubberBandIsShown 132
rubberBandRect 132
run() 422
excution multithread 408,
409, 412
S
Sauvegarde 85
save() 47, 52
Editor 162
fichier 60
matrice de transformation
196
module de rendu 188
oprations XML 370
saveAs() 47, 52, 60-61, 167
saveFile() 60, 165, 167
saveState() 463
QMainWindow 159
SAX (Simple API for XML) 359
SaxHandler 362
scale() 191
Script configure
-help 473
Qtopia 470
scroll() 139
QWidget 175
scrollTo() 239
SDI (Single Document Interface)
75
section 245
Scuriser pour les scripts 456
seek() 294
SELECT 312
Select All (action) 52
selectColumn() 91
selectedId() 230
selectedRange() 89
Selection 224
selectionAsString() 220
546
selectRow() 91
Smaphore
emploi 414
freeSpace 414
usedSpace 414
sendDatagram() 354
sender() (QObject) 64
sendRequest() 346, 352
Sparateur 152
sessionFileName() 466
sessionId() 467
sessionKey() 467
setAcceptDrops() 221
setAllowedAreas() 158
setAutoDetectUnicode() 388
setAutoFillBackground() 126
setAutoRecalculate() 93
setBackgroundRole() 126, 136
setBit() 434
setBrush() 186
setByteOrder() 289
setChecked() 163
setClipRect() 138
setCodec() 295, 388
setCodecForCStrings() 389
setCodecForTr() 389
setColumnRange() 38, 68
SortDialog 69
setCompositionMode() 198
setContext() (QShortcut) 172,
367
setCurrentCell() 80
QTableWidget 66
setCurrentFile() 58, 60, 62, 165,
167
setCurrentIndex() 150-151
setCurrentRow() 151
setCursor() 132
setCurveData() 129
setData()
champ de base de donnes
315
index de modle 316
QMimeData 219
rimplmentation 247
setDefault() 19
setDirty() 93, 99
setDuration() 193
setEditorData() 258, 260
setEditTriggers() 233
QAbstractItemView 231
setEnabledOptions() (QPrintDialog) 205
setFeatures() (QDockWidget)
157
SetFixedSize (QLayout) 38
setFocus() 165
setFocusPolicy() 126
setFont() 186
setFormat() (QGLWidget) 209
setFormula() 84, 98, 298
setGeometry() (QWidget) 72
setHorizontalHeaderLabels() 232
setHost() 341
setIconImage() 110, 112
setImage() 421
presse-papiers 224
setImagePixel() 116, 117
SetInterfaceSafetyOptions() 456
setItem() 97
QTableWidget 84, 232
setItemPrototype() 81
QTableWidget 98
setLayout() (QWidget) 9
setLayoutDirection() 395
setlocale() 395
setMargin() (QLayout) 147
setMessage() 408, 410
setMimeData() 224
setModal() 66
setModelData() 258
setMouseTracking() (QWidget)
117
setNum() 280
setOutputFormat() 199
setOverrideCursor() 132
setPen() 115, 186
setPenColor() 112
Index
short 489
Show Grid 52, 71, 75
show() 65, 128, 165
ShowControls 451
showEvent() 173-174
showPage() 379
Signal 7
clicked() 170, 237, 304305, 348
closeEditor() 260
connected() 344-346
connexion aux slots 23, 304
contentsChanged() 166
copyAvailable() 162
currentCellChanged() 57
currentRowChanged() 151
description 22
disconnected() 344, 349,
350
done() 331, 340, 342
editingFinished() 259
error() 344, 349
findNext() 65
findPrevious() 65
finished() 422
itemChanged() 81
listInfo() 335
modified() 80, 85
readyRead() 339, 342, 344,
347, 351
requestFinished() 342
requestStarted() 342
stateChanged() 333
timeout() 192
toggled 36
transactionStarted() 423
triggered() 51, 400
valueChanged() 9
versus vnement 170
windowActivated() 161
signals 7, 17, 23
simplified() 282-283
size() (QFontMetrics) 174
sizeConstraint 38
547
548
SmallGap 204
Socket
QTcpSocket 341
QtSslSocket 341
SolidPattern 187
somethingChanged() 81, 85, 9394
sort() 68
MainWindow 69
Spreadsheet 81
SortDialog 68, 69
setColumnRange() 69
Sorties 287
source() (QDragEnterEvent) 218
split() 282
QString 90
Spreadsheet 49, 65
clear() 58
readFile() 60
setFormula() 98
sort() 68, 81
SpreadsheetCompare 68, 81, 94
arbre dhritage 79
spreadsheetModified() 57
sprintf() 279
Square 95
squeeze() 275
start()
QDrag 218
QProcess 305
slot 455
startDrag() 217, 220
startElement() 360, 364
startOrStopThreadA() 410
startOrStopThreadB() 411
startPos 217
startsWith() 281
startTimer() 174
stateChanged() 333
static_cast 510
static_cast<T>() 64, 511
status() 290
statusBar() (QMainWindow) 56
StatusTipRole 242
std 125
STL (Standard Template Library)
63
fichiers den-tte 525
Stockage
de donnes en tant
qulments 82
des caractres ASCII 490
format 288
local de thread 417
XML 359
stop() 408
excution multithread 412
stopped 408, 411
stopSearch() 348
strippedName() 62, 165
strtod() 486
Style
de widget 12, 130
Plastique 12
spcifique la plate-forme
12
style() 130
submitAll() 315
sum() 104
supportsSelection() 225
Surcharge doprateur 512
switchLanguage() 400, 401
Symbole
de prprocesseur 446
de systme de fentre 446
Synchronisation (des threads) 411
System() 394
Systmes embarqus 469
T
Table de hachage 274
Tableau
C++ 502
doctets 278
TableMimeData 224
tagName() 368
takeFirst() (QStringList) 297
Tas 498
tcpSocket 344
TeamLeadersDialog 238
terminate() 409
Tetrahedron 208
text() 27, 83, 137
presse-papiers 224
QLineEdit 66
QTableWidgetItem 96
TextAlignmentRole 99, 242,
245, 256
TextArtDialog 437-438
TextArtInterface 437-438
TextColorRole 242, 256
Texte
criture 294
lecture 294
textFromValue() 107
Thread
communication 418
consommateur 416
cration 408
producteur 416
setMessage() 408
stop() 408
synchronisation 411
utilisation des classes Qt 423
ThreadDialog 410
Ticker 172
tidyFile() 298
tile() 164
timeout() 175, 192
timer 169, 175
timerEvent() 173, 175, 181, 452
title 34
toAscii() 283
toBack() 268
toCsv() 221
toDouble() 100, 280
toElement() 368
toggled() 36
Index
toHtml() 221
toInt() 280
QString 66, 107
toLatin1() 283, 387
toLongLong() 280
toLower() 281, 283
toolTip() 121
ToolTipRole 242
top() 266
toPage() (QPrinter) 205
toString() 99, 284, 395
toUpper() 281, 283
QString 107
tr()
dclaration avec Q_OBJECT
25
internationalisation 392
traduction de littraux 18
Trac de dessin 187
TrackDelegate 258, 323, 326
transaction() 313, 422
ajout la file dattente 421
ConvertDepthTransaction
422
excutions simultanes 314
FlipTransaction 422
ResizeTransaction 422
SQL 313
transactionStarted() 422, 423
TransactionThread 420
run() 423
translate() 191, 392-393
triggered() 51
trimmed() 282, 283
TripPlanner 344
TripServer 349
incomingConnection() 349
truncate() 272
type() 284
conversions 509
MIME 215
personnalis de glisser 219
primitifs 489
QEvent 170
reprsentation binaire 86
valeur 514
TypeDef 509
U
UDP 329
datagrammes 353
coute du port 355
envoi et rception de datagrammes 353
uic 29, 32, 120
unget-Char() 294
unicode() 386-387
Unit de compilation 484
unlock() 411
unsigned 489
update()
forcer un vnement paint
113
planifier un vnement paint
136
QRect 117
QWidget 112
viewport 93
updateGeometry() 113, 174
QWidget 112
updateGL() 211
updateMenus() 163
updateOutputTextEdit() 306
updateRecentFileActions() 62,
74-75
updateRubberBand() 136
updateRubberBandRegion() 132
updateStatusBar() 57
MainWindow 84
updateTableWidget() 348
updateWindowTitle() 378
UserRole 231
using namespace 486
uuidgen 457
549
V
Valeur 71
bitsPerPixel 431
compression 431
Valgrind 490
Validateur
QDoubleValidator 31
QIntValidator 31
QRegExpValidator 31
value()
champ de base de donnes
312, 314
courbe 138
itrateur STL 276
map 274, 275
valeur
dune cellule 100
dune map 275
valueChanged() 9
valueFromText() 107
values() 274
Variable
globale 516
mutable 97
sur la pile 67
Variable denvironnement
LD_LIBRARY_PATH 481
PATH 5, 380
QTDIR 122
QWS_KEYBOARD 471
QWS_MOUSE_PROTO
471
Variant 278
Vecteur
dlments 264
de coordonnes 122
de QChar 279
distances 248
initialisation 277
parcourir 265
Version de systme dexploitation
446
550
viewport() 155
dessin 188
OpenGL 209
systme de coordonnes 189
transformation du painter
194
update() 93
widget 82
virtual 494
VNC (Virtual Network
Computing) 472
void* 511
Vue
QListView 229
QTableView 229
QTreeView 229
W
WA_DeleteOnClose 74, 162,
166
WA_GroupLeader 378
WA_StaticContents 117
wait() 411, 417
waitForDisconnected() 424
waitForFinished() 424
warning() (QMessageBox) 58,
70
WeatherBalloon 354
whatsThis() 121
WhatsThisRole 242
wheel 135
wheelEvent() 135
while 179
Widget
ancrable 157
bouton 40
QCheckBox 40
QPushButton 40
QRadioButton 40
QToolButton 40
central 78
classes 40
compagnon 18
conteneur 41
multipage 41
QFrame 41
QTabWidget 41
QToolBox 41
daffichage dlments 41
dentre 43
dfinition 4
disposer 7, 28, 144
HelpBrowser 377
OvenTimer 191, 194
palette de couleurs 115
personnalis 106
crer 105
et Qt Designer 108
intgrer avec
le Qt Designer 118
Plotter 122
style 12
viewport 82
windowActivated() 161
windowModified 57, 62
Windows
ActiveX 448
identifier la version 446
installer Qt 478
Media Player 449
windowTitle 27
winEvent() 448
winEventFilter() 448
winId() 444
write() 429
QIODevice 293
writeDatagram() 355
writeFile() 85
WriteOnly (QIODevice) 87
writeRawBytes() 290
writeSettings() 70, 71, 154
X
X Render 197
X11
gestion de session 461
installer Qt 479
x11Event() 448
x11EventFilter() 446
Xcode Tools 479
XML 359
criture 370
lecture
avec DOM 365
avec SAX 360
xsm 467
Z
Zone
daction 188
droulante 155
zoomFactor() 109, 113
zoomIn() 127, 134
zoomOut() 127
zoomStack 127
CampusPress Rfrence
Qt 4
et
C++
ISBN : 978-2-7440-4092-4
Officially Approved
by Trolltech