Mémo sur la bibliothèque graphique C++ Qt

Michel Llibre – Octobre 2009.

Table des matières

1 Introduction

1.1 Récupérer Qt

1.2 Compilation

1.3 Exemple d'applications Qt minimales

1.3.1 Applications minimales de type Qt-console

1.3.2 Applications minimales de type Qt-window

1.3.3 Application Qt-console ou Qt-windows

1.4 Exemples de configuration du Project file *.pro

1.5 Transition de Qt 4.X à Qt5

1.6 Déploiement appli sur autre pc

1.7 Divers

1.7.1 Type qreal

1.7.2 Impression sur stderr :

1.8 QscopedPointer et l'héritage des QObject

1.9 Ménage

2 La communication entre objets

2.1 Signaux et slots

2.2 Classe QAction

3 Classe QMainWindow et associées

3.1 Classe QStatusBar

3.2 Classe QMenuBar

3.3 Classe QMenu

3.4 Création de menus et barre d'outils

4 Classes graphiques : QWidget et dérivées

4.1 Classes Qwidget, Qlayout

4.1.1 Qwidget

4.1.2 QLayout

4.2 Quelques Widgets simples

4.2.1 Classe QLabel

4.2.2 Classe QLineEdit (Linge d'entrée)

4.2.3 Classes dérivées de QAbstractButton

4.2.4 Classe QPushButton

4.2.5 Classe QCheckBox (case à cocher)

4.2.6 Classe QSpinBox (sélecteur d'entier à molette)

4.2.7 Classe QDoubleSpinBox (sélecteur de double à molette)

4.2.8 Classe QSlider (sélecteur à curseur)

4.2.9 Classe QComboBox (Liste déroulante)

4.2.10 Classe QGroupBox

4.3 Quelques Widgets complexes

4.3.1 Classe QMessageBox

4.3.2 Classe QDialog

4.3.3 Classe QProgressDialog

4.3.4 Classe QinputDialog

4.3.4.1 Lecture d'un double

4.3.4.2 Lecture d'un int

4.3.4.3 Sélection d'un item dans une liste

4.3.4.4 Lecture d'un texte multi-lignes

4.3.4.5 Lecture d'un texte simple

4.3.5 Classe QFileDialog

4.4 Modifier l'apparence générale des Qwidgets

4.4.1 Avec une feuille de style

4.4.2 Avec des méthodes spécifiques

4.5 Dessiner dans un Qwidget

4.6 OpenGL - Classe QGLWidget

4.7 Animation dans un QWidget

4.8 Utiliser un Widget perso avec QtDesigner

5 Classes non graphiques

5.1 Classe QThread

5.2 Classe QVariant

5.3 Géométrie 2D

5.3.1 Classe QSize

5.3.2 Classe QPoint (2D en int)

5.3.3 Classe QPointF (2D en qreal)

5.4 Listes

5.4.1 Classe QList :

5.4.2 Classe QlistIterator

5.4.3 Classe QmutableListIterator

5.5 Caractères et textes

5.5.1 Classe QChar

5.5.2 Classe QString

5.5.3 Classe QStringList :

5.6 Classe QValidator

5.6.1 Classe QIntValidator

5.6.2 Classe QDoubleValidator

5.6.3 Classe QRegExpValidator

5.7 Classe Qevent

5.7.1 Classe QCloseEvent

5.7.2 QKeyEvent

5.7.3 Classe QMouseEvent

5.8 Accès aux fichiers

5.8.1 Classe QFile, QDir

5.8.2 Fichiers ressources

5.8.3 Classe QFileInfo

6 L'architecture modèle-vue-controleur (TREEVIEW)

7 Classes Multimedia

7.1 Classe QAudioOutput

7.1.1 PullMode

7.1.2 PushMode

 

1 Introduction

Qt est une bibliothèque C++ qui offre des primitives graphiques : fenêtres, menus, gestion des évènements, … indépendante de la machine et du système d'exploitation. Elle est à mon avis mieux structurée et plus simple à utiliser que GTK.

1.1 Récupérer Qt

Actuellement (2011) Qt est distribué gratuitement par Nokia.

Sous Windows on installe le SDK Qt qui contient un environnement MinGW/Msys (petit environnement de type linux) qui permet de compiler en ligne de commande et QtCreator qui un environnement de développement complet intégrant éditeur de texte avec auto-complétion,, système de compilation, linkage, exécution, debug, et surtout une superbe aide en ligne.

Sous Linux, je ne sais pas, mais cela doit être encore plus simple, car Qt fonctionne surtout avec des outils GNU (gcc, gdb, etc..).

1.2 Compilation

En mode console : Mettre les sources dans un dossier, nommé par exemple bidon.

Ensuite, on utilise l'utilitaire qmake, par la commande :

qmake -project

pour générer un fichier projet bidon.pro du type ci-dessous :

######################################################################

# Automatically generated by qmake (2.00a) ti 8. aug 18:09:23 2006

######################################################################

TEMPLATE = app

TARGET +=

DEPENDPATH += .

INCLUDEPATH += .

# Input

SOURCES += fichier.cpp

CONFIG += console

Ensuite la commande :

qmake

génère un fichier Makefile pour linux et deux fichiers Makefile.Debug et Makefile.Release pour windows, qui sont compilés par la commande :

make

Remarque : La dernière ligne "CONFIG += console" est à ajouter (au cas où elle serait absente) pour capturer les sorties vers stderr et stdout.

1.3 Exemple d'applications Qt minimales

Nous décrivons ci_après des applications qui utilisent la gestion des événement de Qt lancée dans le main par l'instruction app.exec(). En l'absence de cette instruction, on est dans le cadre d'une application ordinaire qui ne peut pas utiliser tous les mécanismes mis en œuvre dans Qt.

1.3.1 Applications minimales de type Qt-console

#include <QCoreApplication>

#include <stdio.h>

int main(int argc, char *argv[])

{

    QCoreApplication app(argc, argv); // Ini obligatoire pour gestion Qt

    puts("Hello Michel !");

    return app.exec();

}

L'instruction app.exec() lance la boucle de gestion des évènements qui permet à la console de durer jusqu'à ce qu'on la ferme.

1.3.2 Applications minimales de type Qt-window

#include <qapplication.h> //Obligatoire ou #include <QApplication>

#include <qmessagebox.h>  // ou bien       #include <QmessageBox>

int main(int argc, char* argv[])

{

    QApplication app(argc,argv); //Ini obligatoire pour gestion graphique

    QMessageBox boite;

    boite.information(0,"BOITE A MESSAGE","Bonjour Michel ");

    app.quit();

}

 

Dans cet exemple, après avoir examiné le QMessageBox::information(..), on quitte l'application. Si on mettait return app.exec() il n'y aurait pas de moyen de terminait l'application, car plus rien n'est affiché.

 

#include <QApplication>

#include <QLabel>

int main(int argc, char *argv[])

{

    QApplication app(argc, argv);

    QLabel *label = new QLabel("Hello Qt!");

    label->show();

    return app.exec();

}

Dans celui_ci, le label sans parent est une fenêtre principale avec X pour quitter. app.exec() lance la boucle de gestion des évènements dont on sort à la fin de l'appli.

Au lieu d'inclure <QLabel>, <QWidget>, ..., etc, on peut utiliser la ligne unique qui les inclut tous :

- avant QT5 :

#include <QtGui>

-depuis QT5

#include <QtWidgets>

 

Remarque : Pour appeler App.quit() depuis un autre module on peut utiliser qApp qui est une macro qui permet d'accéder à la QApplication. Il suffit alors d'appeler :

 qApp→quit(); // inclure <QApplication> pour accéder à la macro

1.3.3 Application Qt-console ou Qt-windows

Une application CONSOLE doit inclure <QCoreApplication> et la fonction

    int main(int argc, char *argv[])

doit débuter par :

    QCoreApplication app(argc, argv);

et terminer par :

    return app.exec();

Une application WINDOW doit inclure <QApplication> et la fonction

    int main(int argc, char *argv[])

doit avoir pour première instruction:

    QApplication app(argc, argv);

et pour dernière :

    return app.exec();

L'application se termine par l'appel de la méthode quit() de l'application.

1.4 Exemples de configuration du Project file *.pro

Dans QtCreator, les fichiers d'extension .c sont compilés en C et ceux d'extension c++ sont compilés en C++. On peut utiliser l'environnement QtCreator pour compiler du C/C++ ordinaire qui n'ont pas besoin des librairies Qt. Pour cela il faut adapter le project file *.pro dont voici un exemple qui compile le fichier main.c sans utiliser les librairies QT :

TEMPLATE  = app

QT       -= core

QT       -= gui

CONFIG   -= qt

CONFIG   += console

TARGET    = ctest

SOURCES  += main.c

La première ligne TEMPLATE spécifie le type de la cible a réaliser. Ici app pour  application, il y a également lib pour librairie.

La deuxième et troisième ligne QT -= core,gui spécifie qu'on ne veut pas réaliser une interface graphique QT (ce qui serait fait par défaut).

La quatrième ligne CONFIG   -= qt, spécifie qu'on ne veut pas utiliser les includes et librairies Qt qui sont incluses et utilisées par défaut (cependant semble redondant avec les 2 lignes précédentes).

La quatrième ligne TARGET spécifie qu'on veut réaliser une application de type console.

La cinquième ligne spécifie le nom donné à la cible (ctest.exe)

La dernière ligne SOURCES spécifie le nom du fichier à compiler (chemin par rapport au répertoire contenant *.pro).

Pour compiler avec les librairies statiques ajouter :

win32 {

QMAKE_LFLAGS += -static-libgcc

}

 

En savoir plus : Dans l'aide QtCreator : Qt Reference Documentation/QMake Manual/Manual/Qmake réference.

 

Pour ajouter des librairies, un clic droit dans le fichier *.pro ouvre un menu dans lequel en choisissant "add library" on obtient un wizard qui est bien utile.

 

A partir de QT4 ajouter la ligne suivante dans le fichier *.pro si nécessaire :

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets.

Pour passer au compilateur un niveau d'optimisatioin, on ajoute au *.pro une ligne de ce type :

QMAKE_CXXFLAGS += -O2.

Pour empêcher la compression des fichiers resources on ajoute au *.pro la ligne suivante :

QMAKE_RESOURCE_FLAGS += -no-compress

Pour compiler un fichier *.c en C99 (par exemple pour pouvoir écrire for(int i=0;i<3;i++)…) on ajoute au *.pro la ligne suivante :

QMAKE_CFLAGS += -std=c99

Pour utiliser la librairie openGL glut, il y a des problèmes à partir de la version 5.5 de Qt qui suppose qu'on utilise des fonctions openGL enrobées par Qt. Pour continuer à faire tourner les anciens codes, faire :

win32 {

greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32

}

else {

greaterThan(QT_VERSION, 5.5): LIBS += -lGLU

}

1.5 Transition de Qt 4.X à Qt5

Avant Qt5, il suffisait d'inclure <QtGui> pour que toutes les classes nécessaires soeint incluses, et rien de spécial dans le *.pro. Depuis Qt5, cela ne marche plus. Si on utilise les fonctions du gui Qt, il faut ajouter les lignes suivantes dans le *.pro :

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

Par ailleurs, dans le code, il ne faut plus mettre le #include <QtGui>. On peut soit le remplacer par #include <QtWidgets> , soit mettre tous les includes individuels nécessaires.

Des modules sont maintenant séparés et il faut signaler leur nécessité dans le *.pro, comme :

QT += webkitwidgets si dans les sources on utilise #include <QtWebKitWidgets>,

QT += printsupport si dans les sources on utilise #include <QPrinter> ou #include <QPrintDialog>.

1.6 Déploiement appli sur autre pc

Pour pouvoir éxécuter une application compilée en mode release sur un autre pc, il faut copier en plus de l'exécutable de l'application, dans le répertoire qui la contient, les dll suivantes provenant de ???/Qt/5.x/mingwXX_YYY/bin :

Qt5Core.dll, QtGui.dll, et éventuellement Qt5Widgets.dll, Qt5OpenGL.dll, Qt5PrintSupport.dll, …

En plus de ces dll, il faut copier d'autres dll provenant de répertoires en dessous du répertoire ???/Qt/5.x/mingwXX_YYY/plugins. Obligatoirement la dll qwindows.dll provenant du répertoire ???/Qt/5.x/mingwXX_YYY/plugins/platforms, et éventuellement d'autres dll provenant de répertoires ???/Qt/5.x/mingwXX_YYY/plugins/xxxxx. Ces dll ne doivent pas être doivent être copiées dans le même répertoire que l'exécutable de l'application, mais dans un sous-répertoire du même nom que celui qui les contenait : Ainsi qwindows.dll sera copiée dans ./platforms/qwindows.dll et par exemple la dll qjpeg.dll provenant de ???/Qt/5.x/mingwXX_YYY/plugins/imageformat sera copie dans ./imageformat/qjpeg.dll.

 

1.7 Divers

1.7.1 Type qreal

qreal est un typedef double sauf sur le cpu ARM où c'est un float.

1.7.2 Impression sur stderr :

qDebug(), qWarning(), QCritical() et QFatal() impriment sur le gestionnaire de

message par défaut, sinon sur stderr (et dans le debugger en mode debug). Sous windows qDebug() envoie les messages sur la console. La syntaxe est identique pour tous :

qDebug("Valeur =  %lf",val);

Dans cette première forme le format doit être en Latin-1.

Pour les formes suivantes il faut inclure #include <QtDebug>

qDebug() << "Valeur = " << val;

qDebug() << QDir::currentPath();

 

 

1.8 QscopedPointer et l'héritage des QObject

Cette partie peut-être sautée dans un premier temps, pour y revenir si on désire comprendre ce qu'est un QscopedPointer que l'on trouve dans certains codes à propos des QLayouts.

Quasiment toutes les classes Qt héritent de QObject.

Les méthodes de la classe QObject sont accessibles par toutes les classes qui en dérivent sans avoir à préciser d'objet propriétaire. Exemples :

    QString tr("Texte à internationaliser"); // mécanisme de traduction

Pour toutes les classes, il existe quasiment tout le temps un constructeur qui prend en argument un  QObject*parent (ou un  QWidget *parent pour les classes graphiques, QWidget héritant de QObject). Ce lien parental entraîne la destruction automatique de tous les enfants d'un parent quand celui-ci est détruit, ce qui est très pratique car il est alors inutile de faire un delete d'un enfant dans le destructeur de son parent, cela sera fait automatiquement.

A la sortie du constructeur, tous les objets créés dans ce constructeur par une déclaration simple (création directe dans la pile) deviennent inaccessibles, ce qui peut être gênant pour les objets passés renvoyés sous forme de pointeur, car ils n'existent plus. Ces objets doivent donc être créés par un new afin qu'ils perdurent aussi longtemps que nécessaire. Le mécanisme précédent permet leur destruction automatique sans avoir à faire de delete.

Pour certains objets (essentiellement les QLayout), il arrive qu'on ne puisse pas leur donner de parent (un QWidget ne peut être le parent que d'un seul Qlayout, ce qui fait que les autres sont orphelins). Il existe alors un autre mécanisme qui permet de transférer l'héritage QObject : Les QScopedPointer :

Les QScopedPointer sont des templates qui permettent de créer des pointeurs de n'importe quel objet et de transférer l'héritage "QObject" à un autre objet ce qui permettra leur destruction en chaîne.

Exemple :

   TClasse *pC = new TClasse(...); // Création du pointeur sur TClasse, sans parent

 QScopedPointer<TClasse> pS(pC); // Création d'un alias sur pC qui devient parent de pC.

   pS.take(); // Transfert de la propriété à this (instance de la classe en cours).

 pS->fonction1(...);

   pC->fonction1(...); // On accède aux membres de TClasse par les pointeurs pC ou pS.

   fonction2(pC);   // En argument de fonction, on utilise pC ou pS.data() (= pC) :

 fonction2(pS.data());

 

La procédure normale, plus compacte, mais moins intuitive est la manière suivante :

 QScopedPointer<TClasse> pS(new TClasse(...));

 TClasse *const pC = pS.take(); // Transfert de propriété et récupération d'un const pointeur.

   pC->fonction1(...); // Accès aux membres de TClasse par le const pointeur pC

   fonction2(pC);           // également en argument de fonction.

1.9 Ménage

Si un widget est explicitement détruit par le programmeur au moyen d'un "delete", le destructeur du widget est appelé et on peut faire le ménage dans ce destructeur, par exemple faire un close et un delete des QLabels pour lesquels on a fait un new et un show.

Mais si ce delete n'est pas explicitement programmé, il semble que le destructeur d'un widget indépendant (modeless) quelconque ne soit pas systématiquement appelé lors de sa fin de vie. Dans certains cas il est difficile de tuer des widgets affichés sans lien parental avec des widgets qui terminent leur vie. En particulier à la fin de vie d'une QMainwindow contenant un QGLWidget, le destructeur de ce QGLWidget n'est pas appelé, ni son closeEvent. Si des objets sans lien filial ont été créés dans ce QGLWidget, on ne pourra pas les détruire. Pour ce faire, il faut que la QMainWindow les connaisse, et elle pourra les fermer par un close() dans son protected void closeEvent. Exemple :

void myWindow::closeEvent(QCloseEvent *event)

{

    if (daccordPourFermer)

    {  

                        // Faire le ménage ;

        event→accept();

   }

   else // si pas d'accord pour fermer

     {

      // Faire autre chose

     event→ignore() ; // on n'accepte pas la demande de fermeture

     }

}

Mais attention, de ne pas faire de delete ici, car ce closeEvent peut être appelé sans que l'instance de la classe ne soit elle-même détruite, et si elle est ré-activée, il y aura un crash qund elle voudra accéder aux objets détruits.

Une autre possibilité, beaucoup plus simple, est d'en faire un widget lié à un parent qui sera détruit à la destruction de son parent. Mais dans ce cas le widget est modal, et pour le rendre modeless, il suffit d'appeler

this->setWindowFlags(Qt::Window) sur ce widget.

2 La communication entre objets

Avant de rentrer dans le graphique examinons des mécanismes particuliers, très performants offerts par Qt, en particulier pour répondre aux actions de l'usager (menus, boutons, etc...).

2.1 Signaux et slots

Les objets Qt (ou autres à condition de les faire dériver de QObject) peuvent communiquer par un système de signal-slot. Un slot est une fonction particulière qui peut être appelée, soit de manière ordinaire ou bien être réveillée lorsqu'un signal est émis au moyen de l'instruction emit monsignal.

Toute classe dérivant de QObject peut utiliser ce système.

Pour mettre en place ce mécanisme, Qt génère du code au moment de l'appel du précompilateur moc. Mais pour cela, les classes, par exemple Toto et Titi doivent être déclarées à part dans des entêtes toto.h et titi.h séparées des éventuels toto.cpp et titi.cpp et ces déclarations doivent être de la forme suivante :

#ifndef TOTO_H

#define TOTO_H

#include <QObject> // ou une classe plus appropriée qui en dérive

 

class Toto : public QObject { // doit dériver de QObject ou d'une classe dérivée

    Q_OBJECT                  // Macro obligatoire

public:

    Toto(...);                // Le constructeur

    ...

public slots:

    void mySlot(liste params); // Une méthode réceptrice

private:

    ...

};

#endif // TOTO_H

/*---------*/

#ifndef TITI_H

#define TITI_H

#include <QObject>

 

class Titi : public QObject {

    Q_OBJECT

public:

    Titi(...);                   // Le constructeur

    ...

signals:

    void mySignal(liste params); // Une méthode émettrice (sans code associé!)

private:

    ...

};

#endif // TITI_H

Le canal de communication entre un objet Titi et un objet Toto est déclarée par l'instruction

    QObject::connect(&titi, SIGNAL(mySignal(..)), &toto, SLOT(mySlot(..)));

Une méthode de Titi utilise l'instruction :

    emit mySignal(..);

pour émettre le signal.

Le contrôle passe alors à la méthode mySlot de l'objet toto (ou passera ?).

Les arguments de mySignal(..) sont transmis à mySlot(..). La fonction signal doit avoir (au moins) les mêmes arguments que la fonction slot et dans le même ordre.

La déconnexion peut être faite par :

    QObject::disconnect(orig, SIGNAL(signa1(..), dest, SLOT(callback(..)));

 

Remarques :

2.2 Classe QAction

C'est une classe interface qui permet d'associer une fonction de rappel (une callback) à différents widgets et en particulier pour les menus, boutons, barre d'outils, raccourcis clavier..). L'attribution de l'action au widget se fait par :

    QWidget::addAction(QAction *).

Une action peut être attribuée à plusieurs widgets lorsqu'il y a plusieurs moyens de déclencher la même action.

La création de l'action se fait par un des constructeurs suivants :

    QAction(QObject *parent)

    QAction(const QString &text, QObject *parent)

    QAction (const QIcon &icon, const QString &text, QObject *parent)

et peut être complétée par :

    setText (const QString &text)

    setIcon(const QIcon &icon)

    setShortcut(const QList<QKeySequence> &shortcuts) // genre tr("CTRL+N")

    setStatusTip(const QString & statusTip) // bulle explicative

    setCkeckable(bool) //Crée une action avec coche d'activation

    setChecked(bool)// Met ou enleve la coche d'activation

    setData(const QVariant &userData) // Y enregistre des données usager

L'association entre la QAction et la fonction de rappel se fait par :

    win.connect(action, SIGNAL(...), this, SLOT(...));

ou win est par exemple l'instance de QMainWindow (cf section suivante) où cette action intervient.

Signaux de QAction :

    triggered(bool checked=false)//emis qd l'action est activée par l'usager.

    toogled(bool checked)// emis qd une action checkable est modifiée

 

Slots de QAction :

    setVisible(bool)// Permet de la rendre visible ou invisible

3 Classe QMainWindow et associées

Nous présentons en premier la classe  QMainWindow et ses classes associées  QStatusBar,  QMenuBar, QMenu,  QToolbar,..

La classe  QMainWindow dérive de la classe générique QWidget présentée à la section suivante.

Une QMainWindow  est une fenêtre ayant un layout particulier avec un QMenuBar, un centralWidget, un QStatusBar...

Méthodes :

    QMenuBar *menuBar() // -> pointeur sur le QMenuBar de la fenêtre

    setcentralWidget(QWidget *w) // Le QWidget UNIQUE contenu dans la QMainWindow

    QWidget *menuWidget()

    QToolBar *addToolBar (const QString &title) // Ajoute une portion de ToolBar

    QStatusBar *statusBar() // -> sur le QStatusBar

Pour mettre plusieurs choses dans une QMainWindow, on y met un widget que l'on transforme en layout et c'est dans ce layout qu'on mettra plusieurs choses. Exemple :

  QWidget *centralWidget = new QWidget(this);

  setCentralWidget(centralWidget);

  QVBoxLayout *mainLayout = new QVBoxLayout(); // on le configure en QVBoxLayout

  centralWidget->setLayout(mainLayout);

  mainLayout->addWidget(wGL);

  QHBoxLayout *basLayout = new QHBoxLayout();

  mainLayout->addLayout(basLayout);

  basLayout->addWidget(play_pauseButton);

  basLayout->addWidget(initButton);

  basLayout->addWidget(label);

  basLayout→addWidget(slider);

Autre Approche équivalente (plus simple) :

  QWidget *centralWidget = new QWidget(this);

  setCentralWidget(centralWidget);

  mainLayout = new QVBoxLayout(centralWidget);

  mainLayout->addWidget(wGL);

  QHBoxLayout *basLayout = new QHBoxLayout();

  mainLayout->addLayout(basLayout);

  basLayout->addWidget(play_pauseButton);

  basLayout->addWidget(initButton);

  basLayout->addWidget(label);

  basLayout→addWidget(slider);

On peut obtenir la taille de la fenêtre pricipale par la méthode :

QSize size = qApp->screens()[0]->size();

3.1 Classe QStatusBar

La ligne d'état en bas d'une QMainWindow.

Méthodes :

    addWidget(QWidget *, int stretch = 0) // 0 taille minimale, 1 s'étire au max

pour insérer un contenu (un Qlabel par exemple) dans la ligne d'état.

Slot :

    showMessage(const QString &message, int timeout=0) //affiche un message temporairee pendant timeout ms (pas de timeout si 0)

    clearMessage() // Efface le message temporaire

3.2 Classe QMenuBar

La barre de menu horizontale en haut d'une QMainWindow.

Méthodes :

    QMenu *addMenu(const QString &title) // Titre du menu ajouté

    void addAction(QAction *action) // l'action effectuée qd le menu est cliqué

3.3 Classe QMenu

Méthode :

    QMenu *addMenu(const QString &title) //Titre du sous-menu ajouté

    void addAction(QAction *action)

    void addSeparator()

3.4 Création de menus et barre d'outils

Dans le langage Qt un controle Microsoft s'appelle une action.

1) Créer et configurer les QAction :

    Qaction("nomAction"), puis : setIcon, setShortCut, setStatusTip, connect.

2) Créer les menus et y introduire les actions :

   QMenu *f = menuBar()->addMenu(...),

 f->addAction(action1) ;....

3) Créer les barres d'outils et y introduire les actions :

   QToolBar *t = addToolBar(tr("Titre"));

 t->addAction(action1);...

Exemple :

       // Les actions

    QAction *openAction = new QAction(tr("&Open"), this);

    connect(openAction, SIGNAL(triggered()), this, SLOT(open()));

    .....

    QAction *saveAction = new QAction(tr("&Save"), this);

    connect(openAction, SIGNAL(triggered()), this, SLOT(save()));

  // Le menu

    QMenu *fileMenu = menuBar()->addMenu(tr("&Fichier"));

    fileMenu->addAction(openAction);

    .....

    fileMenu->addSeparator(); // ligne de séparation dans le menu

    .....

    fileMenu->addAction(saveAction);

    // La barre d'outil

    QToolBar *t = addToolBar(tr("Titre")); // clic-droit activer/désactiver

    t->addAction(action1);

    ...

    t->addSeparator()

    ...

Pour une MENU CONTEXTUEL c'est encore plus simple car le widget sert de menuBar :

    widget->setContextMenuPolicy(Qt::ActionsContextMenu); // autorise le contextuel

    widget->addAction(openAction);

    widget->addAction(saveAction);

 

4 Classes graphiques : QWidget et dérivées

4.1 Classes Qwidget, Qlayout

4.1.1 Qwidget

Tous les objets graphiques héritent de QWidget (qui hérite lui-même de QObject et donc du système de signaux et slots). Citons parmi les widgets déjà définis les QLabel, QPushButton, QSpinbox, QSlider, QCHeckBox, QDialog, QLineEdit,....

Le constructeur standard d'un QWidget est :

    QWidget::QWidget(QWidget *parent=0, Qt::WindowFlags f=0)

Un QWidget sans parent est une fenêtre de haut niveau (avec X de fermeture).

Un QWidget ayant un parent n'est visible qu'à l'intérieur de la fenêtre de son parent. De plus, il est automatiquement détruit quand son parent est détruit.

Le flag f permet de spécifier l'apparence à l'aide de constantes prédéfinies :

 Qt::Widget=0,Qt::Window=1,Qt::Dialog=2,.....

 

Slots de la classe QWidget :

    close()

    hide()

    show()

    setVisible(bool)

    setEnabled(bool) // si false ne peut recevoir le focus

    setFocus()

    lower()

    raise()

    repaint()   // Si le widget est visible envoie un évènement paint à effet immédiat

    update()    // Si le widget est visible envoie un évènement paint dans la queue

   Remarque : Plusieurs update() consécutifs en queue sont remplacés par un seul.

    setWindowTitle(const QString) // Mettre [*] à l'emplacement où on veut qu'apparaisse une * qd le contenu est à sauvegarder (cf utilisation méthode suivante)

    setWindowModified(bool) // -> Contenu à sauvegarder

    showFullScreen()

    showMaximized()

    showMinimized()

    showNormal()

Autres méthodes de la classe QWidget :

- méthodes de gestion fenêtre :

    setWindowIcon (const QIcon &icon )//dans le coin supérieur gauche de la fenêtre

    activateWindow() // Met le widget visible au premier plan et lui donne le focus

    bool isVisible(), isHidden(), isModal, ...... etc

    bool isWindowModified()

    setDefault(bool) // si true le widget sera activé par l'appui sur Entrée

    updateGeometry() // indique aux containers que la geometrie du widget a changé

    QSize sizeHint() // On surcharge cette fonction pour renvoyer la taille désirée pour le widget, taille qui sera + ou - prise en compte en fonction de la QSizePolicy.

    setFixedHeight(int) // fixe la hauteur

    setFixedWidth(int) // fixe la largeur

    setMinimumSize(int)

    addAction (QAction *action)// ajoute l'action à la liste des actions du widget

    setContextMenuPolicy(p) // avec p =

        Qt::PreventContextMenu    pas de menu contextuel

        Qt::NoContextMenu         menu contextuel délégué au parent

        Qt::ActionsContextMenu    menu contextuel à partir des Actions

        Qt::DefaultContextMenu

        Qt::CustomContextMenu

La méthode suivante (setSizePolicy) n'est prise en compte que si le widget ne contient pas de layout qui gère le dimensionnement de son contenu. La valeur par défaut est  Prefered,Prefered.

    setSizePolicy(QSizePolicy h, QSizePolicy v) // Politique d'étirement du widget.

        QSizePolicy::Minimum  : le sizeHint est un minimum, auquel il se limite a priori

        QSizePolicy::Maximum  : le sizeHint est un maximum

        QSizePolicy::Preferred : le sizehint est l'idéal, mais la taille peut varier

        QSizePolicy::Expanding : idem, mais tendance à occuper tout l'espace libre

        QSizePolicy::MinimumExpanding : le sizeHint est un minimum, mais tendance à occuper le max d'espace libre

        QSizePolicy::Ignored : le sizeHint est ignoré, le widget à tendance à occuper le maximum d'espace.        

    setAttribute(att, bool on=true) // ci-après qqs att :

        Qt::WA_DeleteOnClose      Le widget est détruit si l'évènement close est accepté

        Qt::WA_AcceptDrop

        Qt::WA_StaticContents     L'intérieur du widget n'est pas affecté par sa taille

            il n'est pas agrandi ou rétréci qd on change la geometry du widget

        ......

    setMouseTracking(bool enable) // si false, génère mouseMoveEvent que si un bouton est appuyé

- méthodes de dessin :

    const QPalette &palette() const // Renvoi une reference sur la palette

    palette().foreground.color() // get couleur de fond

protected :

    virtual void closeEvent(QCloseEvent *event)//A surcharger pour gérer la fermeture du widget

    virtual void mouseMoveEvent (QMouseEvent * event) // Voir QMouseEvent

    virtual void mousePressEvent (QMouseEvent * event) // ....

        void paintEvent(QPaintEvent *);

 

Remarque : A la plupart des fonctions setMembre(..) corespond souvent une fonction getMembre() qui dans Qt s'écrit simplement membre() sans le get.

Pour fixer la taille par défaut, ou préférée du widget ce n'est pas avec un setqqchose() mais (bizarrement) en surchargeant la méthode sizeHint() qui par défaut renvoie une taille (Qsize) invalide. Pour donner une taille par défaut, on la surcharge pour qu'elle renvoie la taille que l'on désire.

4.1.2 QLayout

La disposition des autres widgets à l'intérieur d'un widget est gérée par la classe abstraite QLayout qui est la classe mère de QHBoxLayout, QVBoxLayout, QGridLayout, etc...

Un layout est normalement créé et affecté à un widget par les instructions :

    QHBoxLayout *layout = new  QHBoxLayout(); // Par exemple

    leWidget->setLayout(layout);

mais, les constructeurs d'un Qlayout peuvent prendre un QWidget *parent comme en argument, ce qui est équivalent à affecter ce layout au Qwidget. Les deux instructions précédentes peuvent donc être remplacées par l'instruction unique suivante:

    QHBoxLayout *layout = new  QHBoxLayout(leWidget);

Méthodes de QLayout :

    addLayout(QLayout *)

    addwidget(QWidget *)

    addStetch() //ajout zone vierge d'étirement

    setSizeConstraint() // Cf ci-après :

    /* enum SizeConstraint { SetDefaultConstraint, SetFixedSize,

      SetMinimumSize, SetMaximumSize,

      SetMinAndMaxSize, SetNoConstraint } */

 

Un QWidget peut inclure plusieurs QLayout mais il ne peut avoir qu'un seul QLayout enfant. S'il en contient plusieurs (pour l'agencement des widgets internes), les autres seront sans parent, ce qui fait qu'ils ne seront pas automatiquement détruits quand le widget sera détruit. Pour que this soit leur parent en tant que QObject, on peut utiliser des QscopedPointer présentés dans la section sur l'héritage des QObjects. Par exemple :

 QScopedPointer<QVBoxLayout> scP(new QVBoxLayout());

 scP.take();

 scP->addWigdet(new QLabel("hello"),this));

 setlayout(scP.data());

Ou bien de la manière suivante (plus claire à mon avis) :

 QScopedPointer<QVBoxLayout> scP(new QVBoxLayout());

 QVBoxLayout *const vlayout = scP.take();

 vlayout->addWigdet(new QLabel("hello"),this));

 setlayout(vlayout);

 

Exemple de QGridLayout :

#include <QApplication>

#include <QtCore>

#include <QtGui>

#include <QPushButton>

#include <QGridLayout>

 

int main(int argc, char* argv[])

{

    QApplication app(argc, argv);

    // Grid layout with 3 buttons

    QGridLayout *gridLayout = new QGridLayout;

    QPushButton *b1 = new QPushButton("A");

    QPushButton *b2 = new QPushButton("B");

    QPushButton *b3 = new QPushButton("C");

    QPushButton *b4 = new QPushButton("D");

    QPushButton *b5 = new QPushButton("E");

    QPushButton *b6 = new QPushButton("F");

 

    // addWidget(*Widget, row, column, rowspan, colspan)

    // 0th row

    gridLayout->addWidget(b1,0,0,1,1);

    gridLayout->addWidget(b2,0,1,1,1);

    gridLayout->addWidget(b3,0,2,1,1);

 

    // 1st row

    gridLayout->addWidget(b4,1,0,1,1);

 

    // 2nd row with 2-column span

    gridLayout->addWidget(b5,2,0,1,2);

 

    // 3rd row with 3-column span

    gridLayout->addWidget(b6,3,0,1,3);

 

    // Create a widget

    QWidget *w = new QWidget();

 

    // Set the grid layout as a main layout

    w->setLayout(gridLayout);

 

    // Window title

    w->setWindowTitle("Grid Layouts (3x4)");

 

    // Display

    w->show();

 

    // Event loop

    return app.exec();

}

4.2 Quelques Widgets simples

4.2.1 Classe QLabel

Dérivant de QFrame permet de créer une window de 1er niveau si elle n'a pas de parent.

Constructeurs :

    QLabel(const QString &text, QWidget *parent=0, Qt::WindowFlags f=0)

Le caractère précédé d'un &, par exemple &Save permet de donner le focus au label en tapant le raccourci associé à la lettre qui suit l'&, ici CTRL+S.

Méthodes :

    setAlignment(Qt::Alignment)

        Qt::AlignLeft

        Qt::AlignRight

        Qt::AlignHCenter

        Qt::AlignJustify

        Qt::AlignTop

        Qt::AlignBottom

        Qt::AlignVCenter

        Qt::AlignCenter = Qt::AlignHCenter | Qt::AlignVCenter

    setIndent(int)// Fixe la marge gauche

    setBuddy(QWidget *w) // Transfère le focus à w qd le raccourci le label est activé

    setText (const QString &)

4.2.2 Classe QLineEdit (Linge d'entrée)

Signaux :

    cursorPositionChanged (int old, int new)

    editingFinished()

    returnPressed()

    selectionChanged()

    textChanged(const QString &text)

    textEdited(const QString &text)

Méthodes :

    QString text() // renvoi le texte contenu

    setValidator(const QValidator *v) // Permet de filtrer les entrées

    bool hasAcceptableInput() // true Si l'entrée est valide

4.2.3 Classes dérivées de QAbstractButton

Méthodes :

    bool isChecked()

    setText(const QString & text)

    QString text()

4.2.4 Classe QPushButton

Constructeurs :

    QPushButton(QWidget *parent=0)

    QPushButton(const QString &text, QWidget *parent=0)

    QPushButton(const QIcon &icon, const QString &text, QWidget *parent=0)

Signaux :

    clicked()

    pressed()

    released()

    toggled(bool)

4.2.5 Classe QCheckBox (case à cocher)

Constructeurs :

    QCheckBox(QWidget *parent=0)

    QCheckBox(const QString &text, QWidget *parent=0)

Signal :

    stateChanged(int)

4.2.6 Classe QSpinBox (sélecteur d'entier à molette)

Constructeurs :

    QSpinBox (QWidget *parent=0)

Méthodes :

    setRange(int min, int max)

Signaux :

    valueChanged(int), valueChanged(QString &).

Slots :

    setValue(int)

4.2.7 Classe QDoubleSpinBox (sélecteur de double à molette)

4.2.8 Classe QSlider (sélecteur à curseur)

Constructeurs :

    QSlider (Qt::Orientation orientation, QWidget *parent=0)

Méthodes :

    setRange(int min, int max)

Signaux :

    valueChanged(int), valueChanged(QString &)

Slots :

    setValue(int)

4.2.9 Classe QComboBox (Liste déroulante)

Slots :

    clear()//Efface le contenu de la liste

Méthodes :

    addItem (const QString &text, const QVariant &userData=QVariant())

4.2.10 Classe QGroupBox

4.3 Quelques Widgets complexes

Nous avons déjà traité la QMainWindow.

4.3.1 Classe QMessageBox

Elle hérite de QDialog et possède des méthodes statiques (appelables directement sous la forme QMessageBox::methode(...) très pratiques :

    QMessageBox::about(parent, title, text)

    StandardButton critical(parent, title, text,

                            StandardButtons buttons=Ok,

                            StandardButton defaultButton=NoButton)

    StandardButton information(parent, title, text,

                               StandardButtons buttons=Ok,

                               StandardButton defaultButton=NoButton)

    StandardButton question(parent, title, text,

                            StandardButtons buttons=Ok,

                            StandardButton defaultButton=NoButton)

    StandardButton warning(parent, title, text,

                           StandardButtons buttons=Ok,

                           StandardButton defaultButton=NoButton)

Toutes ces méthodes renvoient pour résultat une valeur StandardButton (celui qui a été appuyé) dont la liste est donnée ci-après. En argument, absent par défaut 1 seul bouton Ok. Si on en met 2, le 2ème sera pris comme bouton par défaut (sélectionné par Return), et on peut mettre plus de 2 boutons en ajoutant des arguments.

Les valeurs StandardButton renvoyées par la boite et utilisées en argument sont les suivantes:

        QMessageBox::Ok

        QMessageBox::Open

        QMessageBox::Save

        QMessageBox::Cancel

        QMessageBox::Close

        QMessageBox::Discard

        QMessageBox::Apply

        QMessageBox::Reset

        QMessageBox::RestoreDefault

        QMessageBox::Help

        QMessageBox::SaveAll

        QMessageBox::Yes

        QMessageBox::YesToAll

        QMessageBox::No

        QMessageBox::NoToAll

        QMessageBox::Abort

        QMessageBox::Retry

        QMessageBox::Ignore

        QmessageBox::NoButton

4.3.2 Classe QDialog

Ouverture en MODELESS : Un dialogue est activé en mode non-modal par void dialog->show() qui retourne immédiatement.

Ouverture en MODAL : Un dialogue ouvert en mode modal accapare les entrées de tous ses parents jusqu'à sa terminaison. On peut l'ouvrir en mode modal par void dialog->open(), mais le plus simple pour l'ouvrir en modal, c'est par la méthode int dialog->exec() qui renverra le résultat (Accepted=1, Rejected=0) à la fin de son exécution qui se produira produira lors de l'appel d'une des fonctions suivantes :

Slots :

    accept() // ferme le modal-dialog et met le résultat à Accepted

    done(int r) // ferme le dialog et met le résultat à r.

    reject() // ferme le dialog et met le résultat à Rejected

Généralement un dialogue est crée une fois pour toutes, puis activé (en modal ou en modeless) une ou plusieurs fois par l'appel d'un des slots show(), open(), exec(). S'il y a une réinitialisation à faire à chaque ré-activation, elle doit être appelée depuis ces slots.

4.3.3 Classe QProgressDialog

Pour faire patienter pendant une boucle durant longtemp, un dialogue modal est très pratique. On le définit avant la boucle, on appelle l'avancement dans la boucle, et on peut même faire un abort sur intervention de l'usager, et après la boucle un appel avec la valeur finale fait disparaître le dialogue.

QProgressDialog progress("Copying files...", "Abort Copy", 0, numFiles, this);

progress.setWindowModality(Qt::WindowModal);

for (int i = 0; i < numFiles; i++)

{

 progress.setValue(i);

 if (progress.wasCanceled()) break;

 //... copy one file

}

progress.setValue(numFiles);

Avant la boucle un appel de :

progress.setMinimumDuration(100);

retarde l'apparition du progress dialogue de 100 ms, ce qui fait que si la boucle est plus courte que 100 ms, il ne sera pas affiché (ce qui aurait été inutile).

4.3.4 Classe QinputDialog

 

Très utiles pour lire des données simples au moyen de routines statiques déjà prêtes :

4.3.4.1 Lecture d'un double

double QinputDialog::getDouble(QWidget *parent, const QString &title, const QString &label, double value=0, double min=-2147483647, double max=2147483647, int decimals=1, bool *ok=0, Qt::WindowFlags flags=0);

Si Ok est présent et non null, il reçoit en retour true si Ok a été pressé et false si Cancel a été pressé.

Exemple :

bool ok;

double d = QInputDialog::getDouble(this, tr("Titre"), tr("Valeur:"), 37.56, -10000, 10000, 2, &ok);

if (ok) doubleLabel->setText(QString("$%1").arg(d));

4.3.4.2 Lecture d'un int

int QinputDialog::getInt(QWidget *parent, const QString &title, const QString &label, int value=0, int min=-2147483647, int max=2147483647, int step=1, bool *ok=0, Qt::WindowFlags flags=0);

Exemple :

bool ok;

int i = QInputDialog::getInt(this, tr("Titre"), tr("Pourcentage:"), 25, 0, 100, 1, &ok);

if (ok) integerLabel->setText(tr("%1%").arg(i));

4.3.4.3 Sélection d'un item dans une liste

Qstring QinputDialog::getItem(QWidget *parent, const QString &title, const QString &label, const QStringList &items, int current=0, bool editable=true, bool *ok=0, Qt::WindowFlags flags=0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone);

Exemple :

QStringList items;

items << tr("Spring") << tr("Summer") << tr("Fall") << tr("Winter");

bool ok;

QString item = QInputDialog::getItem(this, tr("Titre"), tr("Season:"), items, 0, false, &ok);

if (ok &&!item.isEmpty()) itemLabel->setText(item);

 

4.3.4.4 Lecture d'un texte multi-lignes

Qstring QinputDialog::getMultiLineText(QWidget *parent, const QString &title, const QString &label, const QString &text=QString(), bool *ok= 0, Qt::WindowFlags flags= 0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone)

Exemple :

bool ok;

QString text = QInputDialog::getMultiLineText(this, tr("Titre"), tr("Address:"), "John Doe\nFreedom Street", &ok);

if (ok && !text.isEmpty()) multiLineTextLabel->setText(text);

4.3.4.5 Lecture d'un texte simple

 

Qstring QinputDialog::getText(QWidget *parent, const QString &title, const QString &label, QlineEdit::EchoMode mode= QLineEdit::Normal, const QString &text= QString(), bool *ok=0, Qt::WindowFlags flags=0, Qt::InputMethodHints inputMethodHints= Qt::ImhNone)

Exemple :

bool ok;

QString text = QInputDialog::getText(this, tr("Titre"), tr("User name:"), QLineEdit::Normal, QDir::home().dirName(), &ok);

if (ok && !text.isEmpty()) textLabel->setText(text);

 

4.3.5 Classe QFileDialog

Elle hérite de QDialog et possède des méthodes statiques très pratiques :

    QString QFileDialog::getExistingDirectory (

            QWidget *parent = 0,

            const QString &caption = QString(),

            const QString &dir = QString(),

            Options options = ShowDirsOnly)

    QString QFileDialog::getOpenFileName (

            QWidget *parent = 0,

            const QString &caption = QString(),

            const QString &dir = QString(),

            const QString &filter = QString(),

            QString *selectedFilter = 0,

            Options options = 0)

    QStringList getOpenFileNames (

            QWidget *parent = 0,

            const QString &caption = QString(),

            const QString &dir = QString(),

            const QString &filter = QString(),

            QString *selectedFilter = 0,

            Options options = 0)

    QString getSaveFileName (

            QWidget *parent = 0,

            const QString &caption = QString(),

            const QString &dir = QString(),

            const QString &filter = QString(),

            QString *selectedFilter = 0,

            Options options = 0)

Les options sont les suivantes :

        QFileDialog::ShowDirsOnly (pour getExistingDirectory uniquement)

        QFileDialog::DontResolveSymlink

        QFileDialog::DontConfirmOverwrite

        QFileDialog::ReadOnly

        QFileDialog::HideNameFilterDetails

et selectedFilter est par exemple :

    tr("Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)")

 

Le QString q renvoyé (ou QStringList dans le cas multiple) est valide si !q.isEmpty()

 

4.4 Modifier l'apparence générale des Qwidgets

Couleurs : On peut préciser des couleurs quelconques avec les deux typedef suivants :

 qRgb(int,int,int) et qRgba(int,int,int,int)

4.4.1 Avec une feuille de style

Un style regroupe un ensemble de paramètres décrivant l'apparence de l'application et de widgets. On peut modifier le style de l'ensemble de l'application en utilisant :

QApplication::setStyleSheet(),

ou d'un widget spécifique (ou de plusieurs) et de tous ses (leurs) widgets descendants en utilisant par exemple :

 QLineEdit, QPushButton { color: red; background-color: white} ,

ou d'une instance particulière de widget par exemple :

 monwidget→setStyleSheet("background-color:yellow;color:rgb(255,128,64);")

Voici quelques estyles supportés :

alternate-background-color,  background-color,  color (pour le texte),  font, …

4.4.2 Avec des méthodes spécifiques

QPalette  : L'aspect et certaines couleurs du QWidget sont fixées par sa QPalette qui a deux principaux attributs, le colorGroup et le colorRole.

On peut spécifier des aspects particuliers à la Palette pour ces différents états. Pour spécifier une couleur de fond particulière, on fera :

   setAutoFillBackground(true); // On demande le remplissage automatique du fond

   setBackgroundRole(QPalette::Base); // On spécifie un fond ordinaire

Pour modifier cette couleur on peut faire :

   QPalette pal = palette();

   pal.setColor(QPalette::Base, Qt::red); // On spécifie la couleur de ce fond ordinaire

   this→setPalette(pal);

Trouvé sur internet :

     QPalette pal = widget.palette();

     //couleur de fond de l'application

     pal.setColor(QPalette::Window, Qt::black);

     // Couleur du texte

     pal.setColor(QPalette::WindowText, Qt::white);

     widget.setPalette(pal);

     // applique le style

     app.setStyle(new MyStyle);

4.5 Dessiner dans un Qwidget

On dessine dans un Qwidget en redéfinissant la fonction protected :

        void paintEvent(QPaintEvent *);

Cette fonction est automatiquement appelée dès qu'il faut redessiner le contenu du Qwidget.

Tous les tracés se font à l'aide d'un objet Qpainter qui est créé comme ceci :

        QPainter painter(this);

On trace ensuite avec cet objet. Exemples :

        painter.drawLine(5,10,295,190);

        painter.drawPoint(150,100);

        painter.drawText(rectf, Qt::AlignCenter,"Bonjour");

 

L'origine des coordonnées est le coin Top-Left, x vers la droite et y vers le bas.

Les tracés dépendent des caractéristiques des propriétés du QPainter et en particulier de son crayon (QPen) pour l'épaisseur et la couleur des traits , de sa brosse (QBrush) pour le motif et la couleur de remplissage des surfaces, de sa police (QFon) pour l'écriture des textes...

La couleur du QPen et de la QBrush peuvent être modifiées directement sans passer par leur inermédiaire à l'aide des fonctions painter.setPen(Qt::laCouleur) et painter.setBrush(Qt::laCouleur). Mais pour d'autres caractéristiques telles que l'épaisseur, il faut créer un nouvel élément (à partir de celui qui existe pour l'initialiser plus rapidement), le modifier et l'affecter au QPainter. Exemple :

        QPen pen = painter.pen(); // On crée une copie du QPen en cours.

   pen.setColor(Qt::red);                // On modifie la couleur

   pen.setWidth(10);                        // On modifie l'épaisseur

   painter.setPen(pen);                // On l'affecte au QPainter

Les formes comme les rectangles, ellipses (et cercles), arcs, etc sont spécifiées par le rectangle qui les contient. Il est spécifié par les coordonnées (x,y) de son coin Top-Left, puis par sa taille (w,h). Une portion angulaire est précisée par l'angle de départ a par rapport à l'axe 0x, et sa taille angulaire D, le tout en degrés décimaux.

On peut déplacer l'origine des coordonnées à l'aide de la méthode

  painter.translate(dx,dy);

Le point Xo = dx, Yo = dy sera la nouvelle origine pour les arguments des primtives de trécé drawLine, drawText, etc...

On peut également tourner les axes du repère de coordonnées autour du point Xo,Yo (éventuellement translaté) par la méthode :

  painter.rotate(angDeg);

La rotation est comptée en degrés dans le sens des aiguilles d'une montre !!

Comme translation et rotation ne sont pas commutatives, il faut effectuer les opérations inverses dans l'ordre inverse ( comme on ferme les parenthèses) si on veut retrouver le fonctionnement initial.

Pour dessiner des surfaces complexe on dispose de la classe QPainterPath. On crée une instance :

QPainterPath qpp; On y ajoute des contours à l'aide des méthodes moveTo, lineTo, arcTo (ix, iy, w,h,startAngle, tailleAngle),... Si les contours sont disjoints, ils sont automatiquement reliés, ce qui parfois ne correspond pas à ce qu'on aurait voulu, et en particulier pour les arcTo, il faut penser à se placer par un moveTo au début de l'arc, sinon un segment de droite sera ajouté pour y parvenir, éventuellement depuis l'origine des coordonnées, si c'est le début du contour. Le QPainterPath est automatiquement cloturé et l'intérieur est rempli par la couleur de la brosse. Si le contour se replie sur lui-même il est difficile de prévoir ce qui est intérieur et ce qui ne l'est pas.

Remarque : painter.drawArc et painter.arcTo prennent les mêmes arguments, mais dans le premier les angles sont des doubles en degrés et dans le second ses ints en 1/16 de degrés (multiplier les degrés par 16).

 

Pour provoquer par programmation le dessin du widget, c'est-à-dire l'appel de la fonction paintEvent() on dispose des fonctions :

 leWidget->repaint() ; // appel direct de  paintEvent

 leWidget->update() ; // met un QpaintEvent dans la queue des évènements

La deuxième forme est préférable. De plus plusieurs QPaintEvents empilés se traduiront par un seul appel à la fonction paintEvent().

Si le Qwidget est caché ou si les updates sont interdits ces deux fonctions seront sans effet.

 

4.6 OpenGL - Classe QGLWidget

Les classes dérivées de QGLWidget permettent d'utiliser les fonctions de la bibliothèque openGL.

Il faut redéfinir les 3 fonctions protected suivantes :

protected:

 void initializeGL();

 void resizeGL(int w, int h);

 void paintGL();

Dans initializeGL() on place les initialisations, par exemple le choix de la couleur d'effacement :

 glClearColor(0.,0.,0.,0.);

Avec resizeGL(int w, int h) on récupère les dimensions effectives du viewport openGL.

Dans paintGL() on met tous les ordres de tracé.

On produit un nouveau tracé en appelant updateGL() qui génèrera l'évènement qui produira l'appel de paintGL().

L'utilisation des facilités openGL, pour le widget créé, doivent se faire après (ou dans) l'appel de initializeGL(), et en particulier la génération des DisplayList. Si elles sont créées avant, elles ne sont pas dessinées par les appels de glCallList(..).

Dans le *.pro, il faudra ajouter les lignes suivantes :

QT += opengl

greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32

Pour QT_VERSION ≤ 5.4 la première ligne suffit à provoquer l'inclusion des bibliothèques.

Sous Ubuntu la deuxième ligne sera :

greaterThan(QT_VERSION, 5.5): LIBS += -lGLU

Le tout peut être écrit sous la forme :

win32 {

greaterThan(QT_VERSION, 5.5): LIBS += -lopengl32 -lglu32

}

else {

greaterThan(QT_VERSION, 5.5): LIBS += -lGLU

}

 

4.7 Animation dans un QWidget

Pour faire une animation dans une classe héritée d'un QWidget, il suffit de créer un QTimer dans son constructeur et de le connecter à un slot comme ceci :

    timer = new QTimer(this);

    timer->setInterval(25);

    connect(timer, SIGNAL(timeout()), this, SLOT(timeOutSlot()));

    time = 0; maxtime = 1000;

    timer->start();

ce qui suppose que la ligne Q_OBJECT est placé au début de la déclaration de la classe dans le .h et qu'il y a les déclarations suivantes :

private:

    QTimer *timer;

    int time, maxtime;

private slots:

    void timeOutSlot();

La routine slot étant par exemple la suivante :

void MyWidget::timeOutSlot()

{

    update(); // Appel du paintEvent.

  if (time++ > maxtime) timer.stop();

}

4.8 Utiliser un Widget perso avec QtDesigner

Pour placer un widget perso, par exemple MyWidget, dans une fenêtre graphique conçue avec QtDesigner, placer à sa place un QWidget de base avec le QtDesigner et lui donner un nom quelconque, par exempe Widget_bid, puis dans le constructeur cpp de la fenêtre graphique, créer le widget MyWidget en lui donnant comme père ui->Widget_bid, comme ceci :

MyWidget *myWidget = new MyWidget(ui→widgete2D);

myWidget→show(); // ne pas oublier !!

5 Classes non graphiques

5.1 Classe QThread

On dérive un thread de la classe QThread. Exemple de déclaration d'une classe Thread, qui émet un signal d'avance au fur et a mesure de l'avancement des calculs, un signal chaque fois qu'elle a atteint un certain résultat.

#include <QThread>

class Bidon;

class MyThread : public QThread

{

    Q_OBJECT

public:

// Constructeur avec un arg pour récupérer des paramètres

    MyThread(Bidon *arg, QObject *parent = 0);

// Pour démarrer le Thread

    void run();

private:

    Bidon *arg;

signals:

    void avance(int i);  // Pour signaler l'avance

    void trouve(int i);  // Autre signal d'avance

public slots:

};

 

MyThread::MyThread(Bidon *arg, QObject *parent) : QThread(parent)

{this->arg = arg;}

 

void MyThread::run()

{

        int nbtrouv = 0;

    for (int j = 0; j < arg->number; j++)

    {

        .. .. ..

        emit avance(i);

        if (arg→cancel) break; // cancel usager depuis l'appelant

        .. .. ..

        emit trouve(++nbtrouv);

        .. .. ..

    }

    // Sortie du Thread

}

Ce thread est créé ailleurs dans son parent par :

    MyThread *job = new MyThread(bidon, this);

    connect(job, SIGNAL(avance(int)), this, SLOT(aff_avance(int)));

    connect(job, SIGNAL(trouve(int)), this,SLOT(aff_trouve(int)));

    connect(job, SIGNAL(finished()), this, SLOT(aff_solution()));

C'est par l'appel du slot aff_solution() qu'on sait que le thread a terminé son travail.

Il est activé, par exemple à la demande de l'usager par :

   if(!job→isRunning()) job→start();

pour éviter de l'activer s'il est déjà en cours.

 

Pour faire dormir le thread principal (non conseillé) pendant 10 ms, on peut faire :

        QThread *t = QApplication::instance()->thread();

        t->usleep(10000);

 

5.2 Classe QVariant

Classe permettant de voir ses objets sous différents types (comme une union en Fortran).

Constructeurs : Quantité de constructeurs avec beaucoup de types de bases.

Exemples de méthodes :

    isValid()

    bool canConvert(Type t) // vrai si la convesion vers le type t est possible

    ...

    int toInt() // Conversion vers un entier

    double toDouble()

    QString toString()

    ....

5.3 Géométrie 2D

5.3.1 Classe QSize

Utilisée pour préciser la taille d'objets 2D.

Constructeurs :

    QSize::QSize()

    QSize::QSize(int width, int height)

Méthodes :

    setHeight(int), setWidth(int),

    int height(), int width(),

    int &rheight(), int &rwidth() // référence qui permet de modifier les valeurs.

5.3.2 Classe QPoint (2D en int)

Constructeurs :

    QPoint ()

    QPoint ( int x, int y )

Méthodes :

    bool isNull () const

    int manhattanLength () const

    int &rx ()

    int &ry ()

    void setX ( int x )

    void setY ( int y )

    int x () const

    int y () const

    QPoint & operator*= ( qreal factor )

    QPoint & operator+= ( const QPoint & point )

    QPoint & operator-= ( const QPoint & point )

    QPoint & operator/= ( qreal divisor )

5.3.3 Classe QPointF (2D en qreal)

Constructeurs :

    QPointF ()

    QPointF ( const QPoint & point )

    QPointF ( qreal x, qreal y )

Méthodes :

    bool isNull () const

    qreal & rx ()

    qreal & ry ()

    void setX ( qreal x )

    void setY ( qreal y )

    QPoint toPoint () const

    qreal x () const

    qreal y () const

    QPointF & operator*= ( qreal factor )

    QPointF & operator+= ( const QPointF & point )

    QPointF & operator-= ( const QPointF & point )

    QPointF & operator/= ( qreal divisor )

5.4 Listes

5.4.1 Classe QList :

Méthodes :

    int removeAll(const T &value) // Enleve les éléments == à value

    append(const T &value)

    append(const QList<T> &value)

    prepend(const T &value)

 

5.4.2 Classe QlistIterator

Itérateur sur une liste qui ne sera pas modifiée. Si on désire modifier la liste utiliser un QmutableListIterator.

Constructeur :

    QListIterator (const QList<T> &list)

Méthodes :

    bool findNext( const T & value)

    bool findPrevious( const T & value)

    bool hasNext() const

    bool hasPrevious() const

    const T &next ()

    const T &peekNext () const

    const T &peekPrevious () const

    const T &previous ()

    void toBack()

    void toFront()

    QListIterator &operator= (const QList<T> &list)

 

5.4.3 Classe QmutableListIterator

Itérateur sur une liste que l'on va modifier. Si on ne fait que parcourir la liste, utiliser plutot un QlistIterator.

Constructeur :

    QMutableListIterator(QList<T> &list)

Méthodes : idem QListIterator plus les suivantes:

    void insert(const T &value)

    void remove()

    void setValue(const T &value) const

    const T &value() const

5.5 Caractères et textes

5.5.1 Classe QChar

Encapsule un caractère unicode

Méthodes :

    ushort &QChar::unicode () // La valeur numérique du caractère

        char toAscii() const // convertit en char

5.5.2 Classe QString

Encapsule un string de caractères 16 bits unicode.

Constructions :

    QString str = "Hello";

    QString str("Hello");

    QString *str = new QString("Hello");

Méthodes :

    QCharRef QString::operator[] (int index) // permet de faire str[1] = QChar('a');

    int size() // Nb de caractères.

    bool isEmpty()

 

    QString arg(const QString &a,int fieldWidth=0,const QChar &fillChar=QLatin1Char(' ')) const

Dans le QString this remplace %1 par a avec un minimum de fieldWidth caractères avec si length(a)<fieldWidth ajout de caractères fillChar.

Ex :

    tr("Nom %1, Prénom %2").arg("Dupont").arg("Jean");

 

    QString mid(int debut, int n=-1) const

Extraction d'une sous-chaine commençant à debut, de n caractères (ou tous si -1)

    QString left(int n) const // Renvoi les n 1er caractères

    QString right(int n) const // Renvoi les 5 dernier caractères

    QString toUpper () cons

    .....

    int toInt(bool *ok=0, int bas=10) const

    ....

Pour convertir un QString en char* , le convertir d'abord en QByteArray par les méthodes toLocal8bit(), toAscii() ou toUtf8(), ... et ensuite convertir le QByteArray en char* par les méthodes constData() ou data(), soit par exemple : qstring.toAscii().data().

 

Conversion de nombre en string : La classe QString offre plusieurs méthodes pour convertir des nombre en string, dont setnum, arg et number.

 

Méthodes statiques pour conversion nombres entiers, doubles etc. :

    QString QString::number(long n, int base = 10)// convertit l'entier n en string

    QString QString::number(double v, char [static] format = 'g', int precision =6 )

5.5.3 Classe QStringList :

Equivalent à Qlist<QString>

5.6 Classe QValidator

Classe mère de QIntValidator, QDoubleValidator et QregExpValidator utilisée trés simplement par Linedit par exemple. On lui affecte un QValidator (voir création plus loin) qui se chargera de vérifier la validité des entrées :

    LineEdit::setValidator(QValidator *);

et ensuite on utilisable la méthode suivante :

    LineEdit::hasAcceptableInput();

pour voir si l'entrée est conforme au QValidator.

Méthodes génériques aux QValidator :

    virtual State validate (QString & input, int & pos ) const = 0

pour interroger sur la validité d'un QString &input. Renvoi :

        Invalid     : si l'entree est incorrecte

        Acceptable  : si elle est valide

        Intermediate : partiellement valide

pos n'est inutilisé pour QRegExpValidator où pour une entrée incorrecte il renvoi le nombre de caractères d'input (utilité ???).

5.6.1 Classe QIntValidator

Hérite de QValidator

    QIntValidor(QObject *parent) //Accepte tout entier

    QIntValidator (int min, int max, QObject *parent)// min <= int <= max

5.6.2 Classe QDoubleValidator

Hérite de QValidator

    QDoubleValidator (QObject *parent ); // acceptable tout double

    QDoubleValidator(double bot, double top, int ndec, QObject *parent)

    // accepte un bot<=double<=top ave au plus ndec décimales après le point

5.6.3 Classe QRegExpValidator

Hérite de QValidator

    QRegExpValidator(const QRegExp &rx, QObject *parent )

5.7 Classe Qevent

Mère de QcloseEvent,  QmouseEvent,...

    accept() //l'évènement est propagé

    ignore() //l'évènement est supprimé

5.7.1 Classe QCloseEvent

Evènement généré lorsque l'usager veut fermer le widget (X par exemple)

5.7.2 QKeyEvent

Evènement généré par l'appui sur une touche du clavier, traité par le slot :

    void keyPressEvent(QKeyEvent *);

On accède à la touche et aux shift, Ctrl etc par :

    int key = event->key();

    bool majuscule = (Qt::SHIFT == event->modifiers());

etc. Pour qu'un widget reçoive les évènements du clavier (keyboard), il faut les autoriser par :

    setFocusPolicy(Qt::StrongFocus);

à mettre dans son contructeur (il y a d'autres options que  Qt::StrongFocus).

5.7.3 Classe QMouseEvent

Généré par un clic, un move souris ou un clic-move.

Traité par les slots suivants :

    void mousePressEvent(QMouseEvent *);

    void mouseReleaseEvent(QMouseEvent *);

    void mouseMoveEvent(QMouseEvent *);

Méthodes :

    Qt::MouseButton button() const // méthode get qui renvoi :

        Qt::NoButton

        Qt::LeftButton

        Qt::RightButton

        Qt::MidButton

        Qt::XButton1

        Qt::XButton2

    Qt::MouseButtons buttons() const // renvoi un OR des précédents

    const QPoint& globalPos() const

    int globalX() const

    int globalY() const

    const QPoint &pos() const

    QPointF posF() const

    int x() const

    int y() const

5.8 Accès aux fichiers

 

5.8.1 Classe QFile, QDir

Interface pour accéder à un fichier en lecture ou écriture

Méthodes statiques :

    bool QFile::copy(const QString &fileName, const QString &newName)

    bool QFile::exists(const QString &fileName)

    bool QFile::remove(const QString &fileName)

    bool QFile::rename(const QString &oldName, const QString &newName)

    bool QFile::resize(const QString &fileName, qint64 sz)

    ....... etc

 QDir QDir::Current() retourne le répertoire courant sous forme de QDir.

 QString QDir::CurrentPath() retourne le répertoire courant sous forme de String.

   QFileInfoList QDir::drives() renvoie la liste des racines

   QDir QDir::home() renvoie le répertoire home de l'usager sous forme de QDir

et pour avoir le nom de l'usager :

   QString QDir::home().dirName() renvoie le nom de l'usager

Remarque :

QDir::currentPath()==QDir::current().absolutePath()==QDir::current().path();

 

Récupérer le FILE pour les routines classiques C :

Mauvais exemple :

void foo(QString filename)

{

    QFile qf(filename);

    qf.open(QIODevice::ReadOnly);

    int fd = qf.handle();

    FILE* f = fdopen(fd, "rb");

    // Faire son travail avec f

    fclose(f); // !!! undefined behaviour !!!

}

Il faut détruire l'objet QFile avant le fclose. Si fclose(f) est appelé avant que l'objet QFile soit détruit, il y a : QTBUG-20372.

Si le Qfile est détruit (par ex si local à une routine utilisée pour l'ouverture) avant les lectures, celles-ci échoueront.

Pour éviter ces écueils, il faut dupliquer le descripteur de fichier retourné par QFile::handle():

Bon exemple :

void foo(QString filename)

{

    QFile qf(filename);

    qf.open(QIODevice::ReadOnly);

    int fd = qf.handle();

    FILE* f = fdopen(dup(fd), "rb"); // !!! use dup()

    // Faire son travail avec f

    fclose(f); // correct

}

5.8.2 Fichiers ressources

Qt permet d'intégrer des fichiers quelconques, par exemple data/tata.txt, dcin/titi.jpg et pict/toto.jpg dans le code C++ qui seront accessible au moyen de QFILE comme les fichiers normaux. Pour cela, il faut écrire un fichier XML avec l'extension .qrc, qui nous appellerons, par exemple, myres.qrc, qui précise comment accéder à ces fichiers. Supposons que toto.txt se situe dans

<RCC>
   <qresource prefix="/blabla">
       <file alias="readme">data/tata.txt</file>
   </qresource>
   <qresource prefix="/images">
       <file alias="bird">dcin/titi.jpg</file>

        <file alias="gars">pict/toto.jpg</file>
   </qresource>
</RCC>

et ajouter ce fichier au fichier projet .pro de Qt avec l'instruction :

RESOURCES += myres.qrc

Les fichiers seront accessibles par :

QFile  *file_readme = new QFile(":/blabla/readme",this);

QFile  *file_bird = new QFile(":/images/bird",this);

QFile  *file_gars = new QFile(":/images/gars",this);

Les attributs prefix et alias sont optionnels :

On peut se passer de ces attributts et mettre simplement :

<RCC>
   <qresource>
       <file>data/tata.txt</file>
       <file>dcin/titi.jpg</file>

        <file>pict/toto.jpg</file>
   </qresource>
</RCC>

et accéder aux fichiers par :

QFile  *file_readme = new QFile(":/data/tata.txt",this);

QFile  *file_bird = new QFile(":/images/bird",this);

QFile  *file_gars = new QFile(":/images/gars",this);

Remarque : Il semble que le / après le : ne soit pas significatif.

Exemple de lecture d'un fichier de type dictionnaire :

       QFile filein(":frdico/dicofr",this);

    if (!filein.open(QIODevice::ReadOnly | QIODevice::Text))

    {

        QMessageBox::warning(this,"ERREUR OPEN","dicofr absent");

        return;

    }

    QTextStream in(&filein);

    QString line;

    while (true)

    {

        line = in.readLine();

        if(line.isNull()) break;

        dico.append(line);

    }

    filein.close();

Les routines C standard d'accès aux fichiers ne peuvent accéder aux fichiers resources Qt. Pour leur permettre cet accès le plus simple est de copier la resource dans un autre fichier. Exemple :

QFile::copy(":frdico/dicofr","tempo.txt");

et pour empêcher la compression (en cas de complication ?) du fichier resource on ajoutera au *.pro la ligne suivante :

QMAKE_RESOURCE_FLAGS += -no-compress

Attention : QFile::copy crée un fichier en lecture seule qu'il est ensuite diffficile à détruire. Pour le rendre accessible en lecture et écriture pour tous on fera :

QFile::setPermissions("tempo.txt", (QFileDevice::Permission) 0x6666);

Une autre solution est de programmer la copie: par exmple :

QResource *qr = new QResource(":frdico/dicofr");

QFile qftempo("tempo.txt");

if (!qftempo.open(QIODevice::WriteOnly))

{QMessageBox::warning(0,"ERREUR OPEN","Echec ouverture tempo.txt"); return 1;}

qftempo.write((const char *) qr->data(), qr->size()) ; qftempo.close();

5.8.3 Classe QFileInfo

Informations sur un fichier

Constructeurs :

    QFileInfo()

    QFileInfo(const QString &file)

    QFileInfo(const QFile &file)

    QFileInfo(const QDir &dir, const QString &file)

    QFileInfo(const QFileInfo & fileinfo)

Méthodes :

Soit le fichier C:/rep/dir/truc.tar.gz alors :

    QString absoluteFilePath() const                C:/rep/dir/truc.tar.gz

    QString filePath() const                                        C:/rep/dir/truc.tar.gz

    QString absolutePath() const                        C:/rep/dir

    QString path() const                                                C:/rep/dir

    QString fileName() const                                        truc.tar.gz

    QString baseName() const                                        truc

    QString completeBaseName() const                truc.tar

    QString completeSuffix() const                        tar.gz

        QString suffix() const                                        gz

    QDir absoluteDir() const                                       

    QDir dir() const

    bool exists() const

    bool isDir() const

    bool isExecutable() const

    bool isFile() const

    bool isHidden() const

    bool isWritable() const

    qint64 size() const

    ..... etc

6 L'architecture modèle-vue-controleur (TREEVIEW)

Dans Qt l'architecture MVC (modèle-vue-controleur) est réduite à deux éléments : le modèle et le controleur+vue.

Qt offre 3 classes controleur+vue :

Les classes modèles héritent de QAbstractItemModel. Par exemple les 4 classes suivantes :

Un modèle est connecté à un controleur de vue par la méthode vue->setModel(modele).

Un item est affecté à un modèle par diverses méthode dont setItem :

    model->setItem(3,1, new QStandardItem("hello")); // cas d'un QStandardItemModel

On ajoute une ligne à un QStandardItemModel ou une sous-ligne à un QStandardItem par la méthode appendRow :

    QStandardItem *item = new  QStandardItem("Sylvie");

    model->appendRow(item);

    item->appendRow(new QStandardItem("blonde"));

On peut sélectionner un item d'un modèle par la méthode index :

 i = model->index("toto");

La vue commence à sa racine qui est l'élément racine du modèle (le premier) ou un élément quelconque. On fait commencer la vue à un item particulier par la méthode setRootIndex :

 vue->setRootIndex(model->index("toto");

Un modele peut être affiché par n'importe laquelle des 3 vues, mais certaines sont mieux adaptées que d'autre. Chaque vue ne montre que les éléments qu'elle sait afficher.

Exemple :

 Wppale::Wppale()

{

    QVBoxLayout *layout = new QVBoxLayout;

    QStringList listePays;

    listePays << "France" << "Espagne" << "Italie" << "Portugal";

    QStringListModel *modele = new QStringListModel(listePays);

 

    QListView *vue = new QListView;

    vue->setModel(modele);

 

    layout->addWidget(vue);

    setLayout(layout);

}

Pour supprimer l'en-tête :

    vue->header()->hide();

La sélection d'un élément se fait par un QItemSelectioModel. Par exemple, en réponse à un SIGNAL clicked(), on connectera le SLOT suivant :

    QItemSelectionModel *selmodel = vue->selectionModel();

    QModelIndex ixSel = selmodel->currentIndex();

    QVariant selvar = modele->data(ixSel, Qt::DisplayRole);

    QMessageBox::information(this, "Elément sélectionné", selvar.toString());

}

La méthode selectionModel d'une vue renvoie un QItemSelectionModel qui contient des infos sur la partie sélectionnée. On récupère son index sous forme d'un QModelIndex, puis l'élément sélectionné sous forme d'un QVariant par la méthode data du modèle, grace au paramètre Qt::DisplayRole. Puis il ne reste qu'à l'afficher sous la forme adéquate.

Pour qu'une vue autorise la sélection multiple, il faut l'indiquer , par la méthode selectionMode :

    vue->setSelectionMode(QAbstractItemView::ExtendedSelection);

puis pour récupérer les indices des éléments sélectionnés, on fera par exemple :

 QItemSelectionModel * selmodel = vue->selectionModel();

    QModelIndexList lixSel = selmodel->selectedIndexes();

    QString sels;

    for (int i = 0 ; i < lixSel.size() ; i++)

    {

        QVariant selvar = modele->data(lixSel[i], Qt::DisplayRole);

        sels += selvar.toString() + "<br />";

    }

    QMessageBox::information(this, "Eléments sélectionnés", sels);

Ainsi, au lieu d'appeler currentIndex(), on utilise selectedIndexes().

7 Classes Multimedia

7.1 Classe QAudioOutput

Elle fournit un moyen de sortie vers la carte son. Il y a deux modes d'utilisation le mode pull et le mode push. Le plus simple étant a priori le premier.

7.1.1 PullMode

En pullmode, il faut redéfinir la fonction virtuelle qint64 QIODevice::readData(char *data, qint64 maxlen), par exemple en faisant hériter notre source de son la classe  QIODevice. Cette fonction se comporte alors comme une callback, ou nous devons remplir le buffer "data" de "len" octets provenant de la source.

Notre source sera par exemple un objet de la classe SourceIO qui hérite de la classe QIODevice qui devra surcharger la fonction readData(char *data, qint64 maxlen).

Ensuite, il faudra appeler dans l'ordre :

 sourceio.open(QIODevice::ReadOnly);

 audioOutput->start(sourceio);

Le reste est automatique, dans la mesure ou notre fonction de rappel readData est bien écrite.

Pour lire un fichier wav, le fichier sous forme QFile dérivant d'un QIODevice, il n'y a rien à faire que l'ouvrir et le passer en argument de audioOutput->start(sourceio);

7.1.2 PushMode

Il me semble plus compliqué, avec a priori l'utilisation d'un Timer.