2 * Copyright (c) 2007, Arnaud Giersch <arnaud.giersch@iut-bm.univ-fcomte.fr>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote
14 * products derived from this software without specific prior
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
18 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "DrawingWindow.h"
31 #include <QApplication>
32 #include <QPaintEvent>
34 #include <QTimerEvent>
36 /*! \class DrawingWindow
37 * \brief Fenêtre de dessin.
39 * \author Arnaud Giersch <arnaud.giersch@iut-bm.univ-fcomte.fr>
42 * Cette classe décrit un widget Qt permettant d'écrire des
43 * applications graphiques simples. Pour cela, il faut définir une
44 * fonction de dessin. Cette fonction ne retourne rien et prend comme
45 * unique paramètre une référence vers un objet de class
48 * La fonction devra ensuite être passée en paramètre pour les
49 * constructeurs de la classe, ainsi que les dimension requises pour
50 * la fenêtre graphique. Le programme est ensuite compilé comme
51 * n'importe programme Qt.
53 * Concrètement, la fonction sera exécutée dans un nouveau thread,
54 * tandis que le thread principal s'occupera de la gestion des
55 * évènements et du rendu.
57 * <b>NB.</b> Pour toutes les méthodes de dessin, le coin en haut à gauche
58 * de la fenêtre a les coordonnées (0, 0). Le coin en bas à droite de
59 * la fenêtre a les coordonnées (largeur - 1, hauteur - 1), si la
60 * fenêtre est de dimension largeur × hauteur.
63 /*! \example hello.cpp
65 * Voir le code source à la fin de la page. Pour compiler et exécuter
66 * ce programme, il faut :
68 * <b>1. Créer le fichier \c hello.pro</b>
70 * Pour simplifier, ce fichier contient la liste des fichiers sources
71 * composant le programme.
75 * <b>2. Créer le fichier \c Makefile avec la commande :</b>
77 * \verbatim qmake-qt4 hello.pro \endverbatim
78 * ou tout simplement :
79 * \verbatim qmake-qt4 \endverbatim
81 * <b>3. Compiler le programme avec la commande :</b>
83 * \verbatim make hello \endverbatim
84 * ou tout simplement :
85 * \verbatim make \endverbatim
87 * <b>4. Exécuter le programme avec la commande :</b>
89 * \verbatim ./exemple \endverbatim
91 * <b>Code source de l'exemple</b>
94 /*! \example exemple.cpp
96 * Un exemple un peu plus sophistiqué.
100 class DrawingThread: public QThread {
102 DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
103 void start_once(Priority priority = InheritPriority);
109 DrawingWindow &drawingWindow;
110 DrawingWindow::ThreadFunction threadFunction;
113 friend class DrawingWindow;
117 SyncRequest = QEvent::User, //!< Demande de synchronisation.
118 CloseRequest, //!< Demande de fermeture de la fenêtre.
119 DrawTextRequest, //!< Demande d'écriture de texte.
122 //! Demande de synchronisation.
123 class SyncRequestEvent: public QEvent {
125 SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
129 //! Demande de fermeture de fenêtre.
130 class CloseRequestEvent: public QEvent {
132 CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
136 //! Demande de tracé de texte.
137 class DrawTextEvent: public QEvent {
143 DrawTextEvent(int x_, int y_, const char* text_, int flags_)
144 : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
145 , x(x_), y(y_), text(text_), flags(flags_)
149 //--- DrawingWindow ----------------------------------------------------
151 /*! \file DrawingWindow.h
152 * \brief Classe DrawingWindow.
155 /*! \typedef DrawingWindow::ThreadFunction
156 * \brief Type de la fonction de dessin, passée en paramètre de construction.
158 /*! \var DrawingWindow::DEFAULT_WIDTH
159 * \brief Largeur par défaut de la fenêtre.
161 /*! \var DrawingWindow::DEFAULT_HEIGHT
162 * \brief Hauteur par défaut de la fenêtre.
164 /*! \var DrawingWindow::width
165 * \brief Largeur de la fenêtre.
167 /*! \var DrawingWindow::height
168 * \brief Hauteur de la fenêtre.
170 /*! \var DrawingWindow::paintInterval
171 * \brief Intervalle de temps entre deux rendus (ms).
176 * Construit une nouvelle fenêtre de dessin avec les dimensions
177 * passées en paramètres. La fonction fun sera exécutée dans un
180 * \param fun fonction de dessin
181 * \param width_ largeur de la fenêtre
182 * \param height_ hauteur de la fenêtre
186 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
196 * Construit un nouveau widget de dessin avec les dimensions passées
197 * en paramètres. La fonction fun sera exécutée dans un nouveau
200 * \param parent widget parent
201 * \param fun fonction de dessin
202 * \param width_ largeur de la fenêtre
203 * \param height_ hauteur de la fenêtre
207 DrawingWindow::DrawingWindow(QWidget *parent,
208 ThreadFunction fun, int width_, int height_)
218 * Construit un nouveau widget de dessin avec les dimensions passées
219 * en paramètres. La fonction fun sera exécutée dans un nouveau
222 * \param parent widget parent
223 * \param flags flags passés au constructeur de QWidget
224 * \param fun fonction de dessin
225 * \param width_ largeur de la fenêtre
226 * \param height_ hauteur de la fenêtre
230 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
231 ThreadFunction fun, int width_, int height_)
232 : QWidget(parent, flags)
240 DrawingWindow::~DrawingWindow()
247 //! Change la couleur de dessin.
249 * La couleur est un entier, tel que retourné par getPointColor.
250 * Normalement de la forme #00RRGGBB.
252 * \param color couleur
254 * \see setColor(const char *), setColor(float, float, float),
255 * setBgColor(unsigned int),
258 void DrawingWindow::setColor(unsigned int color)
260 setColor(QColor::fromRgb(color));
263 //! Change la couleur de dessin.
265 * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
267 * \param name nom de couleur
269 * \see setColor(unsigned int), setColor(float, float, float),
270 * setBgColor(const char *)
271 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
273 void DrawingWindow::setColor(const char *name)
275 setColor(QColor(name));
278 //! Change la couleur de dessin.
280 * Les composantes de rouge, vert et bleu de la couleur doivent être
281 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
282 * du noir; si les trois composantes sont à 1, on obtient du blanc.
284 * \param red composante de rouge
285 * \param green composante de vert
286 * \param blue composante de bleu
288 * \see setColor(unsigned int), setColor(const char *),
289 * setBgColor(float, float, float)
291 void DrawingWindow::setColor(float red, float green, float blue)
293 setColor(QColor::fromRgbF(red, green, blue));
296 //! Change la couleur de fond.
298 * \param color couleur
300 * \see setBgColor(const char *), setBgColor(float, float, float),
301 * setColor(unsigned int),
305 void DrawingWindow::setBgColor(unsigned int color)
307 setBgColor(QColor::fromRgb(color));
310 //! Change la couleur de fond.
312 * \param name nom de couleur
314 * \see setBgColor(unsigned int), setBgColor(float, float, float),
315 * setColor(const char *),
317 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
319 void DrawingWindow::setBgColor(const char *name)
321 setBgColor(QColor(name));
324 //! Change la couleur de fond.
326 * \param red composante de rouge
327 * \param green composante de vert
328 * \param blue composante de bleu
330 * \see setBgColor(unsigned int), setBgColor(const char *),
331 * setColor(float, float, float),
334 void DrawingWindow::setBgColor(float red, float green, float blue)
336 setBgColor(QColor::fromRgbF(red, green, blue));
339 //! Efface la fenêtre.
341 * La fenêtre est effacée avec la couleur de fond courante.
345 void DrawingWindow::clearGraph()
347 safeLock(imageMutex);
348 painter->fillRect(image->rect(), getBgColor());
350 safeUnlock(imageMutex);
353 //! Dessine un point.
355 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
358 * \param x, y coordonnées du point
362 void DrawingWindow::drawPoint(int x, int y)
364 safeLock(imageMutex);
365 painter->drawPoint(x, y);
367 safeUnlock(imageMutex);
370 //! Dessine un segment.
372 * Dessine un segement de droite entre les coordonnées (x1, y1) et
373 * (x2, y2), avec la couleur de dessin courante.
375 * \param x1, y1 coordonnées d'une extrémité du segment
376 * \param x2, y2 coordonnées de l'autre extrémité du segment
380 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
382 safeLock(imageMutex);
383 painter->drawLine(x1, y1, x2, y2);
384 dirty(x1, y1, x2, y2);
385 safeUnlock(imageMutex);
388 //! Dessine un rectangle.
390 * Dessine le rectangle parallèle aux axes et défini par les
391 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
392 * la couleur de dessin courante.
394 * \param x1, y1 coordonnées d'un sommet du rectangle
395 * \param x2, y2 coordonnées du sommet opposé du rectangle
397 * \see fillRect, setColor
399 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
402 r.setCoords(x1, y1, x2 - 1, y2 - 1);
404 safeLock(imageMutex);
405 painter->drawRect(r);
406 r.adjust(0, 0, 1, 1);
408 safeUnlock(imageMutex);
411 //! Dessine un rectangle plein.
413 * Dessine le rectangle plein parallèle aux axes et défini par les
414 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
415 * la couleur de dessin courante.
417 * \param x1, y1 coordonnées d'un sommet du rectangle
418 * \param x2, y2 coordonnées du sommet opposé du rectangle
420 * \see drawRect, setColor
422 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
424 painter->setBrush(getColor());
425 drawRect(x1, y1, x2, y2);
426 painter->setBrush(Qt::NoBrush);
429 //! Dessine un cercle.
431 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
432 * couleur de dessin courante.
434 * \param x, y coordonnées du centre du cercle
435 * \param r rayon du cercle
437 * \see fillCircle, setColor
439 void DrawingWindow::drawCircle(int x, int y, int r)
442 rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
443 safeLock(imageMutex);
444 painter->drawEllipse(rect);
445 rect.adjust(0, 0, 1, 1);
447 safeUnlock(imageMutex);
450 //! Dessine un disque.
452 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
453 * Utilise la couleur de dessin courante.
455 * \param x, y coordonnées du centre du disque
456 * \param r rayon du disque
458 * \see drawCircle, setColor
460 void DrawingWindow::fillCircle(int x, int y, int r)
462 painter->setBrush(getColor());
464 painter->setBrush(Qt::NoBrush);
469 * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
470 * d'alignement flags. Le texte est écrit avec la couleur de dessin
471 * courante. Les flags sont une combinaison (ou binaire) de
472 * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
473 * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
474 * texte est aligné en haut à gauche.
476 * \param x, y coordonnées du texte
477 * \param text texte à écrire
478 * \param flags paramètres d'alignement
480 * \see drawTextBg, setColor
481 * \see QPainter::drawText
483 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
486 if (!terminateThread) {
487 qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
488 syncCondition.wait(&syncMutex);
490 safeUnlock(syncMutex);
493 //! Écrit du texte sur fond coloré.
495 * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
496 * la couleur de fond courante.
498 * \param x, y coordonnées du texte
499 * \param text texte à écrire
500 * \param flags paramètres d'alignement
502 * \see drawText, setColor, setColorBg
504 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
506 painter->setBackgroundMode(Qt::OpaqueMode);
507 drawText(x, y, text, flags);
508 painter->setBackgroundMode(Qt::TransparentMode);
512 //! Retourne la couleur d'un pixel.
514 * Retourne la couleur du pixel de coordonnées (x, y). La valeur
515 * retournée peut servir de paramètres à setColor(unsigned int) ou
516 * setBgColor(unsigned int).
518 * \param x, y coordonnées du pixel
519 * \return couleur du pixel
521 * \see setColor(unsigned int), setBgColor(unsigned int)
523 unsigned int DrawingWindow::getPointColor(int x, int y)
525 return image->pixel(x, y);
528 //! Synchronise le contenu de la fenêtre.
530 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
531 * n'est pas affiché immédiatement. L'appel à sync permet de
532 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
533 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
536 * \param time durée maximale de l'attente
537 * \return true si la fenêtre a pu être synchronisée
539 bool DrawingWindow::sync(unsigned long time)
543 if (terminateThread) {
546 qApp->postEvent(this, new SyncRequestEvent());
547 synced = syncCondition.wait(&syncMutex, time);
549 safeUnlock(syncMutex);
553 //! Ferme la fenêtre graphique.
554 void DrawingWindow::closeGraph()
556 qApp->postEvent(this, new CloseRequestEvent());
559 //! Suspend l'exécution pendant un certain temps.
561 * \param secs temps d'attente en seconde
563 void DrawingWindow::sleep(unsigned long secs)
565 DrawingThread::sleep(secs);
568 //! Suspend l'exécution pendant un certain temps.
570 * \param msecs temps d'attente en millisecondes
572 void DrawingWindow::msleep(unsigned long msecs)
574 DrawingThread::msleep(msecs);
577 //! Suspend l'exécution pendant un certain temps.
579 * \param usecs temps d'attente en microsecondes
581 void DrawingWindow::usleep(unsigned long usecs)
583 DrawingThread::usleep(usecs);
589 void DrawingWindow::closeEvent(QCloseEvent *ev)
594 terminateThread = true; // this flag is needed for the case
595 // where the following wakeAll() call
596 // occurs between the
597 // setTerminationEnable(false) and the
598 // mutex lock in safeLock() called
600 syncCondition.wakeAll();
602 QWidget::closeEvent(ev);
609 void DrawingWindow::customEvent(QEvent *ev)
611 switch ((int )ev->type()) {
618 case DrawTextRequest:
619 DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
620 realDrawText(tev->x, tev->y, tev->text, tev->flags);
628 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
646 void DrawingWindow::paintEvent(QPaintEvent *ev)
648 QPainter widgetPainter(this);
650 QImage imageCopy(*image);
652 QRect rect = ev->rect();
653 widgetPainter.drawImage(rect, imageCopy, rect);
659 void DrawingWindow::showEvent(QShowEvent *ev)
661 QWidget::showEvent(ev);
664 timer.start(paintInterval, this);
665 thread->start_once(QThread::IdlePriority);
671 void DrawingWindow::timerEvent(QTimerEvent *ev)
673 if (ev->timerId() == timer.timerId()) {
675 timer.start(paintInterval, this);
677 QWidget::timerEvent(ev);
681 //--- DrawingWindow (private methods) ----------------------------------
683 //! Fonction d'initialisation.
685 * Fonction appelée par les différents constructeurs.
687 * \param fun fonction de dessin
689 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
691 terminateThread = false;
693 image = new QImage(width, height, QImage::Format_RGB32);
694 painter = new QPainter(image);
695 thread = new DrawingThread(*this, fun);
697 setFocusPolicy(Qt::StrongFocus);
698 setFixedSize(image->size());
699 setAttribute(Qt::WA_OpaquePaintEvent);
709 //! Change la couleur de dessin.
711 * \param color couleur
714 void DrawingWindow::setColor(const QColor& color)
716 QPen pen(painter->pen());
718 painter->setPen(pen);
721 //! Change la couleur de fond.
723 * \param color couleur
726 void DrawingWindow::setBgColor(const QColor& color)
728 painter->setBackground(color);
731 //! Retourne la couleur de dessin courante.
733 * \return couleur de dessin courante
736 QColor DrawingWindow::getColor()
738 return painter->pen().color();
741 //! Retourne la couleur de fond courante.
743 * \return couleur de fond courante
746 QColor DrawingWindow::getBgColor()
748 return painter->background().color();
751 //! Verrouille un mutex.
753 * S'assure que le thread courant ne peut pas être terminé s'il
754 * détient un mutex. Pendant de safeUnlock.
756 * \param mutex le mutex à verrouiller
761 void DrawingWindow::safeLock(QMutex &mutex)
763 if (lockCount++ == 0)
764 thread->setTerminationEnabled(false);
768 //! Déverrouille un mutex.
770 * S'assure que le thread courant ne peut pas être terminé s'il
771 * détient un mutex. Pendant de safeLock.
773 * \param mutex le mutex à déverrouiller
778 void DrawingWindow::safeUnlock(QMutex &mutex)
781 if (--lockCount == 0)
782 thread->setTerminationEnabled(true);
785 //! Marque l'image entière comme non à jour.
787 void DrawingWindow::dirty()
790 dirtyRect = image->rect();
793 //! Marque un point de l'image comme non à jour.
795 * \param x, y coordonnées du point
798 void DrawingWindow::dirty(int x, int y)
800 dirty(QRect(x, y, 1, 1));
803 //! Marque une zone de l'image comme non à jour.
805 * La zone est définie par un rectangle dont les coordonnées de deux
806 * sommets oppposés sont données.
808 * \param x1, y1 coordonnées d'un sommet du rectangle
809 * \param x2, y2 coordonnées du sommet opposé du rectangle
812 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
815 r.setCoords(x1, y1, x2, y2);
816 dirty(r.normalized());
819 //! Marque une zone de l'image comme non à jour.
821 * \param rect rectangle délimitant la zone
823 void DrawingWindow::dirty(const QRect &rect)
833 //! Génère un update si besoin.
835 * Génère une demande de mise à jour de la fenêtre (appel à update)
836 * s'il y en a besoin.
838 void DrawingWindow::mayUpdate()
841 bool dirty = dirtyFlag;
842 QRect rect = dirtyRect;
849 //! Fonction bas-niveau pour sync.
851 * Fonction de synchronisation dans le thread principal.
853 * \see sync, customEvent
855 void DrawingWindow::realSync()
858 qApp->sendPostedEvents(this, QEvent::UpdateLater);
859 qApp->sendPostedEvents(this, QEvent::UpdateRequest);
860 qApp->sendPostedEvents(this, QEvent::Paint);
861 qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
862 QEventLoop::ExcludeSocketNotifiers |
863 QEventLoop::DeferredDeletion |
864 QEventLoop::X11ExcludeTimers);
868 syncCondition.wakeAll();
872 //! Fonction bas-niveau pour drawText.
874 * Le rendu de texte doit être fait dans le thread principal. D'où
875 * les manipulations tordues et la synchronisation qui s'en suit.
877 * \param x, y, text, flags cf. drawText
879 * \see drawText, customEvent
881 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
883 QRect r(image->rect());
884 switch (flags & Qt::AlignHorizontal_Mask) {
888 case Qt::AlignHCenter:
890 r.setLeft(2 * x - width + 1);
897 switch (flags & Qt::AlignVertical_Mask) {
898 case Qt::AlignBottom:
901 case Qt::AlignVCenter:
903 r.setTop(2 * y - height + 1);
911 painter->drawText(r, flags, text, &r);
913 syncCondition.wakeAll();
917 //--- DrawingThread ----------------------------------------------------
920 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
923 , started_once(false)
927 //! Démarre le thread si ce n'a pas encore été fait.
928 void DrawingThread::start_once(Priority priority)
936 //! La vraie fonction pour le thread.
937 void DrawingThread::run()
939 threadFunction(drawingWindow);