Utilisation de la bibliothèque Qt pour faire du graphisme en C
SAULNIER
Gwénolé
PFLIEGER Guillaume
DESS Technologies et
Stratégies Industrielles
Tuteur ULP : TRAU Patrick
1 Introduction
2 Fonctions de base
2.1 La classe QApplication
2.2 La classe QPushButton
2.3 Créer une QApplication
2.4 Gérer des évènements
2.5 Exemple commenté
2.6 Compilation
3 Fonctions graphiques
3.1 Généralité sur le dessin
3.2 Quelques fonctions graphiques de bases
3.3 Exemple d'application
4 Problèmes rencontrés
5 Conclusion
6 Références bibliographiques
Le présent document a été établi à la suite du projet informatique effectué durant le DESS TSI. Le sujet porte sur l'utilisation de la bibliothèque Qt pour faire du graphisme en C. Ce document est un recueil des bases de la programmation avec Qt. Il présente quelques fonctions simples ainsi que leur mise en application sur des machines de l'IPST (système LINUX).
Qt est une bibliothèque graphique écrite en C++, c'est-à-dire un ensemble de classes permettant de développer l'interface graphique d'un programme C++. Elle est développée par la société norvégienne Troll Tech. Qt est portable (utilisable à la fois sur les systèmes UNIX et Windows) et est utilisée dans le développement de nombreuses applications. KDE (K Desktop Environment), l'environnement graphique de bureau utilisé en particulier sous Linux, a été développé avec Qt.
Une application Qt est une application graphique qui va interagir avec l'utilisateur de cette application. Le concept de base d'une application graphique est le widget. Un widget est l'entité de base d'une interface graphique qui réagit aux actions d'un utilisateur : manipulation de la souris, du clavier et tout autre évènement du système graphique. Les widgets sont de formes rectangulaires et sont organisés hiérarchiquement. Un widget particulier appelé widget principal ou widget racine se trouve au sommet de cette hiérarchie.
Dans ce document nous présenterons dans un premier temps les bases de Qt. Puis nous aborderons quelques fonctions simples et essentielles au dessin. Enfin, au travers d'un exemple commenté, nous mettrons en applications ces quelques fonctions.
Du point de vue du programmeur, une application Qt est un objet de la classe QApplication. C'est cet objet qui se charge de la gestion des évènements d'une application donnée : c'est la classe centrale de Qt qui reçoit de l'environnement graphique des évènements et qui les transmet à un objet graphique particulier (widget).
Chaque programme ne comporte qu'un objet de type QApplication et la variable globale qApplication fait référence à cet objet (objet défini par extern QApplication *qApplication).
Les widgets de Qt sont des objets de la classe QWidget.
Toute application Qt est constituée d'un objet de type QApplication. Le fichier qapplication.h contient les déclarations de la classe QApplication. On inclura dans les programmes utilisant Qt, le fichier de déclaration de la classe QApplication.
#include <qapplication.h>
Le fichier qapplication.h ne contient pas tous ce dont une application Qt a besoin. Il est souvent nécessaire d'inclure d'autres fichiers d'interface, selon les besoins de l'application que l'on développe. Par exemple, si l'on veut utiliser d'autres objets graphiques, un bouton par exemple, on devra inclure le fichier qpushbutton.h.
#include <qpushbutton.h>
Un bouton est un objet de la classe QPushButton et possède par défaut un aspect prédéfini. Il existe encore d’autres formes de bouton. Ces dernières ont été employées dans le grand exemple commenté vers la fin du rapport.
Une application Qt commence toujours par la création d'un objet de la classe QApplication de la manière QApplication nom_de_l'apli (argc, argv );
Exemple
On a un objet application (nommé "a"), un widget (nommé "hello"). Le widget est affecté à l'application, puis l'application exécutée.
#include <qapplication.h>
#include <qpushbutton.h>
int main( int argc, char **argv )
{
QApplication a( argc, argv );
QPushButton hello( "Hello DESS TSI", 0 );
hello.resize( 300, 30 ); //taille de la fenêtre
a.setMainWidget( &hello );
hello.show();
return a.exec();
}
Qt propose un système d'appels entre objets original. Chaque objet Qt peut émettre des signaux, et peut être doté de slots. Le lien entre un signal et un slot se fait à l'aide de la simple instruction connect.
Par exemple, l'objet standard QButton est doté d'un signal nommé clicked() qui est déclenché lorsque l'utilisateur clique sur le bouton (soit à l'aide de la souris, soit en le validant au clavier, peu importe). De même, la fonction quit() de l'objet standard QApplication est en fait un slot. Il nous suffit donc d'ajouter juste avant return a.exec() la ligne de code suivante :
QObject : :connect( &hello, SIGNAL(clicked()), &a, SLOT(quit()) ) ;
Le système de communication établi en Qt est basée sur cette notion d'émission et réception de signaux. L'objet émetteur du signal n'a pas à se soucier de l'existence d'un objet susceptible de recevoir le signal émis. Ce qui permet une très bonne encapsulation et le développement totalement modulaire. Un signal peut être connecté à plusieurs slots et plusieurs signaux à un même slot.
Prenons l'exemple d'un programme permettant de créer une fenêtre dans laquelle se trouve un bouton.
#include <qapplication.h>
#include <qpushbutton.h>
#include <qfont.h>
class MyWidget : public QWidget
{ public:
MyWidget( QWidget *parent=0, const char *name=0 ); };
MyWidget::MyWidget( QWidget *parent, const char *name )
: QWidget( parent, name ) {
setMinimumSize( 200, 120 );
setMaximumSize( 200, 120 );
QPushButton *quit = new QPushButton( "Quit", this, "quit" );
quit->setGeometry( 62, 40, 75, 30 );
//Géométrie du bouton définie dans la fenêtre suivant (x,y,w,h)
quit->setFont( QFont( "Times", 18, QFont::Bold ) );
//définition de la police et taille du texte
connect( quit, SIGNAL(clicked()), qApp, SLOT(quit()) ); }
//Effectue la fonction quit en cliquant sur le bouton "quit".
int main( int argc, char **argv )
{ QApplication a( argc, argv );
MyWidget w;
w.setGeometry( 100, 100, 200, 120 );
//Géométrie de la fenêtre définie dans l'écran suivant (x,y,w,h)
a.setMainWidget( &w );
w.show();
return a.exec();
}
Les fichiers se compilent avec la commande de base suivante :
g++ (nom du fichier).cpp –o (nom du fichier)
A ceci il faut encore ajouter le chemin d'accès à la bibliothèque Qt (-lqt) ainsi qu'à de nombreuses autres bibliothèques (-lX11, -lXt, …). Pour simplifier l'opération de compilation on a créé un fichier exécutable regroupant les accès à toutes les bibliothèques. Ce fichier est nommé "c" (ou « compiler-avec-QT.bat ») est regroupe la ligne de commande suivante :
g++ $1.cpp -I /usr/lib/qt3/include -lqt -L /usr/lib/qt3/lib -lX11 -lXt -lGL -L /usr/X11/lib -lXrender -lpng -ljpeg -lXft -lXinerama -o $1
Pour compiler il suffit alors de taper la commande suivante :
c (nom du fichier)
Certains de nos fichiers ont également nécessité l'utilisation du métacompilateur (moc). Cette commande va créer un fichier (nom du fichier).moc à partir du fichier (nom du fichier).cpp ou (nom du fichier).h. La commande à utiliser est :
moc (nom du fichier).cpp –o (nom du fichier).moc
Pour que la commande s'exécute il faut indiquer l'emplacement de certains fichiers. La commande complète est :
usr / lib / qt -3.0.5 / bin / moc (nom du fichier).cpp –o (nom du fichier).moc
Les outils de bases pour dessiner sous Qt sont :
Le pen
Le Brush
Le pen ou le stylo définit les contours des formes (rectangles, ellipse…). On peut soit modifier sa forme soit sa couleur. Au cours d'un programme il est possible d'utiliser des stylos préalablement déclarés ou en spécifier un par fonction.
Le brush ou brosse permet de remplir une forme suivant un style ou une couleur différents.
Dessine un point à (x, y)
Dessine une ligne de (x1, y1) à (x2, y2) place le pen à (x2, y2)
Dessine une ligne de p1 à p2.
Dessine une ligne de la position initiale du pen à (x, y) et (x, y) devient la nouvelle position du pen.
Place le pen à la position (x, y)
Dessine un rectangle dont le coin supérieur gauche est à (x,y) de largeur w et de hauteur h
Dessine un rectangle aux coins arrondis à (x, y), de largeur w et de hauteur h.
xRnd et yRnd définissent les dimensions des arrondis des coins.
Définit un rectangle où tout est effacé dans son aire définit par x, y, w, h.
Dessine un camembert définit par le rectangle (x, y, w, h), l’angle d’ouverture alen et l’e décalage par rapport à l'horizontale a.
Les angles a et alen sont en 1/16ième de degré, c’est à dire qu’un cercle entier vaut 5760 (16*360). a, l’angle de positionnement de l’arc, peut être positif ou négatif. Si a est positif la part sera orientée dans le sens inverse des aiguilles d’une montre.
Dessine un arc défini par un rectangle (x, y, w, h), l’angle d’ouverture alen et l’angle de positionnement a.
Dessine une ellipse avec un centre à (x + w/2, y + h/2) et de taille (w, h).
Place l’image nommé pic à (x, y).
nbpoints définit un nombre de points définissant un polygone, x1, y1, x2, y2…les coordonnées de tous les points.
x,y,w,h, définit le rectangle où sera inscrit le texte.
Ces quelques fonctions de dessin sont issues de la documentation Troll Tech sur internet.
http://doc.trolltech.com/3.1/qpainter.html
#include <qmessagebox.h>
#include <qfile.h>
#include <ctype.h>
#include <qwidget.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qpushbutton.h>
#include <qradiobutton.h>
#include <qbuttongroup.h>
#include <qapplication.h>
#include <math.h>
#include <qpicture.h>
#include <qpixmap.h>
void drawFonts( QPainter *p ){
//définition des pinceaux :bleu, vert peu dense,transparent, quadrillé, jaune
QBrush b1( Qt::blue );
QBrush b2( Qt::green, Qt::Dense6Pattern );
QBrush b3( Qt::NoBrush );
QBrush b4( Qt::CrossPattern );
QBrush b5( Qt::yellow );
//texte crayon rouge
p->setPen( Qt::red );
p->drawText( 10, 10, 150, 70, Qt::AlignCenter, "VOICI\nQUELQUES FORMES" );
//rectangle, crayon noir, fond bleu
p->setBrush( b1 );
p->setPen( Qt::black );
p->drawRect( 150, 10, 150, 100);
//rectangle arrondi, crayon noir, fond vert peu dense
p->setBrush( b2 );
p->drawRoundRect( 350, 10, 200, 100, 50, 50 );
//polygone, intérieur jaune
p->setBrush( b5 );
//définition d'un groupe de points
QPointArray a;
a.setPoints( 6, 10,200, 160,150, 310,200, 310,300,160,350, 10, 300 );
p->drawPolygon( a );
//Rectangle vide dans l'hexagone
p->eraseRect (20,225, 280,50);
// Ellipse, contour vert, transparent
p->setPen( Qt::green );
p->setBrush( b3 );
p->drawEllipse( 350, 200, 200, 100 );
// Partie de camembert, contour noir, quadrillage noir
p->setPen( Qt::black );
p->setBrush( b4 );
p->drawPie(550, 200, 150, 150, 45*16, 90*16 );
//point
p->drawText( 10, 400, 150, 70, Qt::AlignCenter, "trois points" );
p->drawPoint (150,435);
p->drawPoint (200,435);
p->drawPoint (250,435);
//Ligne
p->setPen( Qt::red );
p->drawText( 10, 465, 150, 70, Qt::AlignCenter, "une ligne" );
p->drawLine (150,500,400,500);}
void drawShapes( QPainter *p ){
//arbres
p->setBrush( Qt::darkYellow );
p->drawRect( 37, 130, 25, 150 );
p->drawRect( 110, 130, 25, 150 );
p->drawRect( 185, 130, 25, 150 );
p->drawRect( 265, 130, 25, 150 );
p->drawRect( 335, 130, 25, 150 );
p->drawRect( 410, 130, 25, 150 );
p->setBrush( Qt::green );
p->drawEllipse( 0, 75, 110, 100 );
p->drawEllipse( 75, 75, 110, 100 );
p->drawEllipse( 150, 75, 110, 100 );
p->drawEllipse( 225, 75, 110, 100 );
p->drawEllipse( 300, 75, 110, 100 );
p->drawEllipse( 375, 75, 110, 100 );
//route
p->setPen( Qt::black );
p->drawLine (0,280,700,280);
p->drawLine (0,550,700,550);
//définition du pinceau
QBrush brush( Qt::red, Qt::SolidPattern );
p->setBrush( brush );
//définition de la forme de la voiture
QPointArray a;
a.setPoints( 5, 50,200, 350,200, 450,270, 450,400, 50,400 );
p->drawPolygon( a );
//police d'écriture
QFont f( "courier", 20, QFont::Bold );
p->setFont( f );
//Définition de la couleur bleu clair
QColor windowColor( 120, 120, 255 );
brush.setColor( windowColor );
p->setBrush( brush );
//définition de la fenêtre
p->drawRect( 80, 230, 250, 80 );
//définition du texte
p->drawText( 200, 230, 150, 70, Qt::AlignCenter, "DESS\nTSI" );
// charge une image
QPixmap pixmap;
if ( pixmap.load("ulp.gif"))
p->drawPixmap( 80, 220, pixmap );
p->setBackgroundMode( Qt::OpaqueMode ); // set opaque mode
// définition des roues
p->setBrush( Qt::black);
p->drawEllipse( 310, 360, 80, 80 );
p->drawEllipse( 90, 360, 80, 80 );
p->setBrush( Qt::CrossPattern);
p->drawEllipse( 320, 370, 60, 60 );
p->drawEllipse( 100, 370, 60, 60 );
//Soleil
p->setBrush( Qt::yellow );
p->drawEllipse( 500, 50, 150, 150 );}
typedef void (*draw_func)(QPainter*);
struct DrawThing {
draw_func f;
const char *name;};
//affichage du menu
DrawThing ourDrawFunctions[] = {
{ drawFonts, "Formes" },
{ drawShapes, "Application" },
{ 0, 0 } };
class DrawView : public QWidget {
Q_OBJECT
public:
DrawView();
~DrawView();
public slots:
void updateIt( int );
void printIt();
protected:
void drawIt( QPainter * );
void paintEvent( QPaintEvent * );
void resizeEvent( QResizeEvent * );
private:
QPrinter *printer;
QButtonGroup *bgroup;
QPushButton *print;
int drawindex;
int maxindex; };
DrawView::DrawView() {
//titre de la fenêtre
setCaption( "Applications Qt Saulnier Pflieger" );
setBackgroundColor( white );
// créer un groupe de bouton
bgroup = new QButtonGroup( this );
bgroup->resize( 200, 200 );
connect( bgroup, SIGNAL(clicked(int)), SLOT(updateIt(int)) );
// Taille des boutons
int maxwidth = 80;
int i;
const char *n;
QFontMetrics fm = bgroup->fontMetrics();
for ( i=0; (n=ourDrawFunctions[i].name) != 0; i++ ) {
int w = fm.width( n );
maxwidth = QMAX(w,maxwidth); }
maxwidth = maxwidth + 20;
for ( i=0; (n=ourDrawFunctions[i].name) != 0; i++ ) {
QRadioButton *rb = new QRadioButton( n, bgroup );
rb->setGeometry( 10, i*30+10, maxwidth, 30 );
if ( i == 0 )
rb->setChecked( TRUE ); }
drawindex = 0;
maxindex = i;
maxwidth += 40;
printer = new QPrinter;
// bouton imprimer
print = new QPushButton( "Print...", bgroup );
print->resize( 80, 30 );
print->move( maxwidth/2 - print->width()/2, maxindex*30+20 );
connect( print, SIGNAL(clicked()), SLOT(printIt()) );
bgroup->resize( maxwidth, print->y()+print->height()+10 );
resize( 700,600 ); // taille de la fenêtre }
DrawView::~DrawView() {
#ifndef QT_NO_PRINTER
delete printer;
#endif }
void DrawView::updateIt( int index ) {
if ( index < maxindex ) {
drawindex = index;
update(); } }
void DrawView::drawIt( QPainter *p )
{ (*ourDrawFunctions[drawindex].f)(p); }
void DrawView::printIt() {
#ifndef QT_NO_PRINTER
if ( printer->setup( this ) ) {
QPainter paint;
if ( !paint.begin( printer ) )
return;
drawIt( &paint ); }
#endif }
void DrawView::paintEvent( QPaintEvent * ) {
QPainter paint( this );
drawIt( &paint ); }
void DrawView::resizeEvent( QResizeEvent * ) {
bgroup->move( width()-bgroup->width(), 0 ); }
#include "demo.moc"
int main( int argc, char **argv ) {
QApplication app( argc, argv );
DrawView draw;
app.setMainWidget( &draw );
draw.setCaption("Applications Qt Saulnier Pflieger");
draw.show();
return app.exec(); }
La compilation de certains programmes débouche sur des erreurs récurrentes. Ces dernières surviennent dans le cas où l'on fait appel à plusieurs programmes (.cpp) ainsi que (.h).
Nous avons effectué plusieurs recherches sur internet pour tenter de remédier au problème mais ce dernier persiste.
Une méthode décrite sur le site http://www.linuxfrench.net/article.php3?id_article=535 paraît tout de même intéressante. Pour notre part, nous n'avons plus eu le temps de l'étudier en profondeur. Les premiers tests ont également aboutis sur une liste d'erreur. Toutefois, cette piste n'est pas à négliger et permettrait peut être d'obtenir les résultats escomptés. La méthode est la suivante :
d'abord générer la source du méta-objet
moc -o mybutton.moc.cpp mybutton.h
Dans notre cas la commande moc à utiliser est définie précédemment dans le rapport.
compiler la source du méta-objet généré
g++ -I /home/qt/qt-2.3.1/include/ -c -o mybutton.moc.o mybutton.moc.cpp
La compilation doit pouvoir se faire en utilisant notre fichier “c”. Quelques modifications seront à apporter toutefois.
compiler les autres .cpp
g++ -I /home/qt/qt-2.3.1/include/ -c -o mybutton.o mybutton.cpp
g++ -I /home/qt/qt-2.3.1/include/ -c -o hello.o hello.cpp
linker
g++ -I /home/qt/qt-2.3.1/include/ -L /usr/lib/qt2/lib -lqt -o hello hello.o mybutton.o mybutton.moc.o
En quelques semaines d'utilisation des bibliothèques graphiques Qt, nous avons apprécié la grande diversité des applications possibles. Nous n'avons d'ailleurs pu qu'aborder et nous familiariser qu'avec les fonctions de bases (formes simples de dessins, gestion de fenêtres).
Les fonctions abordées dans ce rapport devraient apporter quelques premiers éléments à la reprogrammation du logiciel par élément finis Mailman en utilisant les fonctionnalités de QT. Toutefois, nous n'avons pu y joindre la gestion de la souris. Ce point reste inachevé en partie à cause des problèmes de compilation que l'on a rencontré avec les programmes d'exemples disponibles sur internet.
D'autres difficultés encore nous ont empêché de progresser à notre guise. Notamment des disparités entre les différentes versions des bibliothèques Qt (v2 et v3) ainsi que la recherche de tous les éléments de bibliothèque nécessaires à une compilation des exemples de base sans erreurs.
Ce projet a été intéressant, car il nous a permis de découvrir l'utilisation de Qt., dans un langage connu : C. Enfin, le projet avait l’avantage d’être concret et les différentes applications d’être visualisées graphiquement..
http://www.esil.univ-mrs.fr/~tourai/Qt/
http://www.univ-pau.fr/~artouste/fr/composants/Qt/ManuelutilisateurQt.htm
http://www.linuxfrench.net/article.php?id_article=527
http://www.linuxfrench.net/article.php3?id_article=535
http://www.linuxfrench.net/article.php?id_article=545
http://www.eyrolles.com/php.informatique/Ouvrages/liste_ouvrages.php3?noeud_id=1431090&xd=a13578bbb7057baed3daaaea34769348 (littérature dédié à la programmation Qt)
fichiers sources à télécharger :