1 #include "DrawingWindow.h"
2 #include <QApplication>
12 /*! \class DrawingWindow
13 * \brief Fenêtre de dessin.
15 * \author Arnaud Giersch <arnaud.giersch@iut-bm.univ-fcomte.fr>
18 * Cette classe décrit un widget Qt permettant d'écrire des
19 * applications graphiques simples. Pour cela, il faut définir une
20 * fonction de dessin. Cette fonction ne retourne rien et prend comme
21 * unique paramètre une référence vers un objet de class
24 * La fonction devra ensuite être passée en paramètre pour les
25 * constructeurs de la classe, ainsi que les dimension requises pour
26 * la fenêtre graphique. Le programme est ensuite compilé comme
27 * n'importe programme Qt.
29 * Concrètement, la fonction sera exécutée dans un nouveau thread,
30 * tandis que le thread principal s'occupera de la gestion des
31 * évènements et du rendu.
34 /*! \example hello.cpp
36 * Voir le code source à la fin de la page. Pour compiler et exécuter
37 * ce programme, il faut :
39 * <b>1. Créer le fichier \c hello.pro</b>
41 * Pour simplifier, ce fichier contient la liste des fichiers sources
42 * composant le programme.
46 * <b>2. Créer le fichier \c Makefile avec la commande :</b>
48 * \verbatim qmake-qt4 hello.pro \endverbatim
49 * ou tout simplement :
50 * \verbatim qmake-qt4 \endverbatim
52 * <b>3. Compiler le programme avec la commande :</b>
54 * \verbatim make hello \endverbatim
55 * ou tout simplement :
56 * \verbatim make \endverbatim
58 * <b>4. Exécuter le programme avec la commande :</b>
60 * \verbatim ./exemple \endverbatim
62 * <b>Code source de l'exemple</b>
65 /*! \example exemple.cpp
67 * Un exemple un peu plus sophistiqué.
71 class DrawingThread: public QThread {
73 DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
74 void start_once(Priority priority = InheritPriority);
80 DrawingWindow &drawingWindow;
81 DrawingWindow::ThreadFunction threadFunction;
84 friend class DrawingWindow;
88 SyncRequest = QEvent::User, //!< Demande de synchronisation.
89 CloseRequest, //!< Demande de fermeture de la fenêtre.
90 DrawTextRequest, //!< Demande d'écriture de texte.
93 //! Demande de synchronisation.
94 class SyncRequestEvent: public QEvent {
96 SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
100 //! Demande de fermeture de fenêtre.
101 class CloseRequestEvent: public QEvent {
103 CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
107 //! Demande de tracé de texte.
108 class DrawTextEvent: public QEvent {
114 DrawTextEvent(int x_, int y_, const char* text_, int flags_)
115 : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
116 , x(x_), y(y_), text(text_), flags(flags_)
120 //--- DrawingWindow ----------------------------------------------------
122 /*! \file DrawingWindow.h
123 * \brief Classe DrawingWindow.
126 /*! \typedef DrawingWindow::ThreadFunction
127 * \brief Type de la fonction de dessin, passée en paramètre de construction.
129 /*! \var DrawingWindow::DEFAULT_WIDTH
130 * \brief Largeur par défaut de la fenêtre.
132 /*! \var DrawingWindow::DEFAULT_HEIGHT
133 * \brief Hauteur par défaut de la fenêtre.
135 /*! \var DrawingWindow::width
136 * \brief Largeur de la fenêtre.
138 /*! \var DrawingWindow::height
139 * \brief Hauteur de la fenêtre.
141 /*! \var DrawingWindow::paintInterval
142 * \brief Intervalle de temps entre deux rendus (ms).
147 * Construit une nouvelle fenêtre de dessin avec les dimensions
148 * passées en paramètres. La fonction fun sera exécutée dans un
151 * \param fun fonction de dessin
152 * \param width_ largeur de la fenêtre
153 * \param height_ hauteur de la fenêtre
157 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
167 * Construit un nouveau widget de dessin avec les dimensions passées
168 * en paramètres. La fonction fun sera exécutée dans un nouveau
171 * \param parent widget parent
172 * \param fun fonction de dessin
173 * \param width_ largeur de la fenêtre
174 * \param height_ hauteur de la fenêtre
178 DrawingWindow::DrawingWindow(QWidget *parent,
179 ThreadFunction fun, int width_, int height_)
189 * Construit un nouveau widget de dessin avec les dimensions passées
190 * en paramètres. La fonction fun sera exécutée dans un nouveau
193 * \param parent widget parent
194 * \param flags flags passés au constructeur de QWidget
195 * \param fun fonction de dessin
196 * \param width_ largeur de la fenêtre
197 * \param height_ hauteur de la fenêtre
201 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
202 ThreadFunction fun, int width_, int height_)
203 : QWidget(parent, flags)
211 DrawingWindow::~DrawingWindow()
218 //! Change la couleur de dessin.
220 * La couleur est un entier, tel que retourné par getPointColor.
221 * Normalement de la forme #00RRGGBB.
223 * \param color couleur
225 * \see setColor(const char *), setColor(float, float, float),
226 * setBgColor(unsigned int),
229 void DrawingWindow::setColor(unsigned int color)
231 setColor(QColor::fromRgb(color));
234 //! Change la couleur de dessin.
236 * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
238 * \param name nom de couleur
240 * \see setColor(unsigned int), setColor(float, float, float),
241 * setBgColor(const char *)
242 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
244 void DrawingWindow::setColor(const char *name)
246 setColor(QColor(name));
249 //! Change la couleur de dessin.
251 * Les composantes de rouge, vert et bleu de la couleur doivent être
252 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
253 * du noir; si les trois composantes sont à 1, on obtient du blanc.
255 * \param red composante de rouge
256 * \param green composante de vert
257 * \param blue composante de bleu
259 * \see setColor(unsigned int), setColor(const char *),
260 * setBgColor(float, float, float)
262 void DrawingWindow::setColor(float red, float green, float blue)
264 setColor(QColor::fromRgbF(red, green, blue));
267 //! Change la couleur de fond.
269 * \param color couleur
271 * \see setBgColor(const char *), setBgColor(float, float, float),
272 * setColor(unsigned int),
276 void DrawingWindow::setBgColor(unsigned int color)
278 setBgColor(QColor::fromRgb(color));
281 //! Change la couleur de fond.
283 * \param name nom de couleur
285 * \see setBgColor(unsigned int), setBgColor(float, float, float),
286 * setColor(const char *),
288 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
290 void DrawingWindow::setBgColor(const char *name)
292 setBgColor(QColor(name));
295 //! Change la couleur de fond.
297 * \param red composante de rouge
298 * \param green composante de vert
299 * \param blue composante de bleu
301 * \see setBgColor(unsigned int), setBgColor(const char *),
302 * setColor(float, float, float),
305 void DrawingWindow::setBgColor(float red, float green, float blue)
307 setBgColor(QColor::fromRgbF(red, green, blue));
310 //! Efface la fenêtre.
312 * La fenêtre est effacée avec la couleur de fond courante.
316 void DrawingWindow::clearGraph()
318 safeLock(imageMutex);
319 painter->fillRect(image->rect(), getBgColor());
321 safeUnlock(imageMutex);
324 //! Dessine un point.
326 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
329 * \param x, y coordonnées du point
333 void DrawingWindow::drawPoint(int x, int y)
335 safeLock(imageMutex);
336 painter->drawPoint(x, y);
338 safeUnlock(imageMutex);
341 //! Dessine un segment.
343 * Dessine un segement de droite entre les coordonnées (x1, y1) et
344 * (x2, y2), avec la couleur de dessin courante.
346 * \param x1, y1 coordonnées d'une extrémité du segment
347 * \param x2, y2 coordonnées de l'autre extrémité du segment
351 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
353 safeLock(imageMutex);
354 painter->drawLine(x1, y1, x2, y2);
355 dirty(x1, y1, x2, y2);
356 safeUnlock(imageMutex);
359 //! Dessine un rectangle.
361 * Dessine le rectangle parallèle aux axes et défini par les
362 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
363 * la couleur de dessin courante.
365 * \param x1, y1 coordonnées d'un sommet du rectangle
366 * \param x2, y2 coordonnées du sommet opposé du rectangle
368 * \see fillRect, setColor
370 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
373 r.setCoords(x1, y1, x2 - 1, y2 - 1);
375 safeLock(imageMutex);
376 painter->drawRect(r);
377 r.adjust(0, 0, 1, 1);
379 safeUnlock(imageMutex);
382 //! Dessine un rectangle plein.
384 * Dessine le rectangle plein parallèle aux axes et défini par les
385 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
386 * la couleur de dessin courante.
388 * \param x1, y1 coordonnées d'un sommet du rectangle
389 * \param x2, y2 coordonnées du sommet opposé du rectangle
391 * \see drawRect, setColor
393 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
395 painter->setBrush(getColor());
396 drawRect(x1, y1, x2, y2);
397 painter->setBrush(Qt::NoBrush);
400 //! Dessine un cercle.
402 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
403 * couleur de dessin courante.
405 * \param x, y coordonnées du centre du cercle
406 * \param r rayon du cercle
408 * \see fillCircle, setColor
410 void DrawingWindow::drawCircle(int x, int y, int r)
413 rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
414 safeLock(imageMutex);
415 painter->drawEllipse(rect);
416 rect.adjust(0, 0, 1, 1);
418 safeUnlock(imageMutex);
421 //! Dessine un disque.
423 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
424 * Utilise la couleur de dessin courante.
426 * \param x, y coordonnées du centre du disque
427 * \param r rayon du disque
429 * \see drawCircle, setColor
431 void DrawingWindow::fillCircle(int x, int y, int r)
433 painter->setBrush(getColor());
435 painter->setBrush(Qt::NoBrush);
440 * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
441 * d'alignement flags. Le texte est écrit avec la couleur de dessin
442 * courante. Les flags sont une combinaison (ou binaire) de
443 * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
444 * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
445 * texte est aligné en haut à gauche.
447 * \param x, y coordonnées du texte
448 * \param text texte à écrire
449 * \param flags paramètres d'alignement
451 * \see drawTextBg, setColor
452 * \see QPainter::drawText
454 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
457 if (!terminateThread) {
458 qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
459 syncCondition.wait(&syncMutex);
461 safeUnlock(syncMutex);
464 //! Écrit du texte sur fond coloré.
466 * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
467 * la couleur de fond courante.
469 * \param x, y coordonnées du texte
470 * \param text texte à écrire
471 * \param flags paramètres d'alignement
473 * \see drawText, setColor, setColorBg
475 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
477 painter->setBackgroundMode(Qt::OpaqueMode);
478 drawText(x, y, text, flags);
479 painter->setBackgroundMode(Qt::TransparentMode);
483 //! Retourne la couleur d'un pixel.
485 * Retourne la couleur du pixel de coordonnées (x, y). La valeur
486 * retournée peut servir de paramètres à setColor(unsigned int) ou
487 * setBgColor(unsigned int).
489 * \param x, y coordonnées du pixel
490 * \return couleur du pixel
492 * \see setColor(unsigned int), setBgColor(unsigned int)
494 unsigned int DrawingWindow::getPointColor(int x, int y)
496 return image->pixel(x, y);
499 //! Synchronise le contenu de la fenêtre.
501 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
502 * n'est pas affiché immédiatement. L'appel à sync permet de
503 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
504 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
507 * \param time durée maximale de l'attente
508 * \return true si la fenêtre a pu être synchronisée
510 bool DrawingWindow::sync(unsigned long time)
514 if (terminateThread) {
517 qApp->postEvent(this, new SyncRequestEvent());
518 synced = syncCondition.wait(&syncMutex, time);
520 safeUnlock(syncMutex);
524 //! Ferme la fenêtre graphique.
525 void DrawingWindow::closeGraph()
527 qApp->postEvent(this, new CloseRequestEvent());
530 //! Suspend l'exécution pendant un certain temps.
532 * \param secs temps d'attente en seconde
534 void DrawingWindow::sleep(unsigned long secs)
536 DrawingThread::sleep(secs);
539 //! Suspend l'exécution pendant un certain temps.
541 * \param msecs temps d'attente en millisecondes
543 void DrawingWindow::msleep(unsigned long msecs)
545 DrawingThread::msleep(msecs);
548 //! Suspend l'exécution pendant un certain temps.
550 * \param usecs temps d'attente en microsecondes
552 void DrawingWindow::usleep(unsigned long usecs)
554 DrawingThread::usleep(usecs);
560 void DrawingWindow::closeEvent(QCloseEvent *ev)
565 terminateThread = true; // this flag is needed for the case
566 // where the following wakeAll() call
567 // occurs between the
568 // setTerminationEnable(false) and the
569 // mutex lock in safeLock() called
571 syncCondition.wakeAll();
573 QWidget::closeEvent(ev);
580 void DrawingWindow::customEvent(QEvent *ev)
582 switch ((int )ev->type()) {
589 case DrawTextRequest:
590 DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
591 realDrawText(tev->x, tev->y, tev->text, tev->flags);
599 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
617 void DrawingWindow::paintEvent(QPaintEvent *ev)
619 QPainter widgetPainter(this);
621 QImage imageCopy(*image);
623 QRect rect = ev->rect();
624 widgetPainter.drawImage(rect, imageCopy, rect);
630 void DrawingWindow::showEvent(QShowEvent *ev)
632 QWidget::showEvent(ev);
635 timer.start(paintInterval, this);
636 thread->start_once(QThread::IdlePriority);
642 void DrawingWindow::timerEvent(QTimerEvent *ev)
644 if (ev->timerId() == timer.timerId()) {
646 timer.start(paintInterval, this);
648 QWidget::timerEvent(ev);
652 //--- DrawingWindow (private methods) ----------------------------------
654 //! Fonction d'initialisation.
656 * Fonction appelée par les différents constructeurs.
658 * \param fun fonction de dessin
660 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
662 terminateThread = false;
664 image = new QImage(width, height, QImage::Format_RGB32);
665 painter = new QPainter(image);
666 thread = new DrawingThread(*this, fun);
668 setFocusPolicy(Qt::StrongFocus);
669 setFixedSize(image->size());
670 setAttribute(Qt::WA_OpaquePaintEvent);
680 //! Change la couleur de dessin.
682 * \param color couleur
685 void DrawingWindow::setColor(const QColor& color)
687 QPen pen(painter->pen());
689 painter->setPen(pen);
692 //! Change la couleur de fond.
694 * \param color couleur
697 void DrawingWindow::setBgColor(const QColor& color)
699 painter->setBackground(color);
702 //! Retourne la couleur de dessin courante.
704 * \return couleur de dessin courante
707 QColor DrawingWindow::getColor()
709 return painter->pen().color();
712 //! Retourne la couleur de fond courante.
714 * \return couleur de fond courante
717 QColor DrawingWindow::getBgColor()
719 return painter->background().color();
722 //! Verrouille un mutex.
724 * S'assure que le thread courant ne peut pas être terminé s'il
725 * détient un mutex. Pendant de safeUnlock.
727 * \param mutex le mutex à verrouiller
732 void DrawingWindow::safeLock(QMutex &mutex)
734 if (lockCount++ == 0)
735 thread->setTerminationEnabled(false);
739 //! Déverrouille un mutex.
741 * S'assure que le thread courant ne peut pas être terminé s'il
742 * détient un mutex. Pendant de safeLock.
744 * \param mutex le mutex à déverrouiller
749 void DrawingWindow::safeUnlock(QMutex &mutex)
752 if (--lockCount == 0)
753 thread->setTerminationEnabled(true);
756 //! Marque l'image entière comme non à jour.
758 void DrawingWindow::dirty()
761 dirtyRect = image->rect();
764 //! Marque un point de l'image comme non à jour.
766 * \param x, y coordonnées du point
769 void DrawingWindow::dirty(int x, int y)
771 dirty(QRect(x, y, 1, 1));
774 //! Marque une zone de l'image comme non à jour.
776 * La zone est définie par un rectangle dont les coordonnées de deux
777 * sommets oppposés sont données.
779 * \param x1, y1 coordonnées d'un sommet du rectangle
780 * \param x2, y2 coordonnées du sommet opposé du rectangle
783 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
786 r.setCoords(x1, y1, x2, y2);
787 dirty(r.normalized());
790 //! Marque une zone de l'image comme non à jour.
792 * \param rect rectangle délimitant la zone
794 void DrawingWindow::dirty(const QRect &rect)
804 //! Génère un update si besoin.
806 * Génère une demande de mise à jour de la fenêtre (appel à update)
807 * s'il y en a besoin.
809 void DrawingWindow::mayUpdate()
812 bool dirty = dirtyFlag;
813 QRect rect = dirtyRect;
820 //! Fonction bas-niveau pour sync.
822 * Fonction de synchronisation dans le thread principal.
824 * \see sync, customEvent
826 void DrawingWindow::realSync()
829 qApp->sendPostedEvents(this, QEvent::UpdateLater);
830 qApp->sendPostedEvents(this, QEvent::UpdateRequest);
831 qApp->sendPostedEvents(this, QEvent::Paint);
832 qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
833 QEventLoop::ExcludeSocketNotifiers |
834 QEventLoop::DeferredDeletion |
835 QEventLoop::X11ExcludeTimers);
839 syncCondition.wakeAll();
843 //! Fonction bas-niveau pour drawText.
845 * Le rendu de texte doit être fait dans le thread principal. D'où
846 * les manipulations tordues et la synchronisation qui s'en suit.
848 * \param x, y, text, flags cf. drawText
850 * \see drawText, customEvent
852 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
854 QRect r(image->rect());
855 switch (flags & Qt::AlignHorizontal_Mask) {
859 case Qt::AlignHCenter:
861 r.setLeft(2 * x - width + 1);
868 switch (flags & Qt::AlignVertical_Mask) {
869 case Qt::AlignBottom:
872 case Qt::AlignVCenter:
874 r.setTop(2 * y - height + 1);
882 painter->drawText(r, flags, text, &r);
884 syncCondition.wakeAll();
888 //--- DrawingThread ----------------------------------------------------
891 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
894 , started_once(false)
898 //! Démarre le thread si ce n'a pas encore été fait.
899 void DrawingThread::start_once(Priority priority)
907 //! La vraie fonction pour le thread.
908 void DrawingThread::run()
910 threadFunction(drawingWindow);