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.
58 /*! \example hello.cpp
60 * Voir le code source à la fin de la page. Pour compiler et exécuter
61 * ce programme, il faut :
63 * <b>1. Créer le fichier \c hello.pro</b>
65 * Pour simplifier, ce fichier contient la liste des fichiers sources
66 * composant le programme.
70 * <b>2. Créer le fichier \c Makefile avec la commande :</b>
72 * \verbatim qmake-qt4 hello.pro \endverbatim
73 * ou tout simplement :
74 * \verbatim qmake-qt4 \endverbatim
76 * <b>3. Compiler le programme avec la commande :</b>
78 * \verbatim make hello \endverbatim
79 * ou tout simplement :
80 * \verbatim make \endverbatim
82 * <b>4. Exécuter le programme avec la commande :</b>
84 * \verbatim ./exemple \endverbatim
86 * <b>Code source de l'exemple</b>
89 /*! \example exemple.cpp
91 * Un exemple un peu plus sophistiqué.
95 class DrawingThread: public QThread {
97 DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
98 void start_once(Priority priority = InheritPriority);
104 DrawingWindow &drawingWindow;
105 DrawingWindow::ThreadFunction threadFunction;
108 friend class DrawingWindow;
112 SyncRequest = QEvent::User, //!< Demande de synchronisation.
113 CloseRequest, //!< Demande de fermeture de la fenêtre.
114 DrawTextRequest, //!< Demande d'écriture de texte.
117 //! Demande de synchronisation.
118 class SyncRequestEvent: public QEvent {
120 SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
124 //! Demande de fermeture de fenêtre.
125 class CloseRequestEvent: public QEvent {
127 CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
131 //! Demande de tracé de texte.
132 class DrawTextEvent: public QEvent {
138 DrawTextEvent(int x_, int y_, const char* text_, int flags_)
139 : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
140 , x(x_), y(y_), text(text_), flags(flags_)
144 //--- DrawingWindow ----------------------------------------------------
146 /*! \file DrawingWindow.h
147 * \brief Classe DrawingWindow.
150 /*! \typedef DrawingWindow::ThreadFunction
151 * \brief Type de la fonction de dessin, passée en paramètre de construction.
153 /*! \var DrawingWindow::DEFAULT_WIDTH
154 * \brief Largeur par défaut de la fenêtre.
156 /*! \var DrawingWindow::DEFAULT_HEIGHT
157 * \brief Hauteur par défaut de la fenêtre.
159 /*! \var DrawingWindow::width
160 * \brief Largeur de la fenêtre.
162 /*! \var DrawingWindow::height
163 * \brief Hauteur de la fenêtre.
165 /*! \var DrawingWindow::paintInterval
166 * \brief Intervalle de temps entre deux rendus (ms).
171 * Construit une nouvelle fenêtre de dessin avec les dimensions
172 * passées en paramètres. La fonction fun sera exécutée dans un
175 * \param fun fonction de dessin
176 * \param width_ largeur de la fenêtre
177 * \param height_ hauteur de la fenêtre
181 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
191 * Construit un nouveau widget de dessin avec les dimensions passées
192 * en paramètres. La fonction fun sera exécutée dans un nouveau
195 * \param parent widget parent
196 * \param fun fonction de dessin
197 * \param width_ largeur de la fenêtre
198 * \param height_ hauteur de la fenêtre
202 DrawingWindow::DrawingWindow(QWidget *parent,
203 ThreadFunction fun, int width_, int height_)
213 * Construit un nouveau widget de dessin avec les dimensions passées
214 * en paramètres. La fonction fun sera exécutée dans un nouveau
217 * \param parent widget parent
218 * \param flags flags passés au constructeur de QWidget
219 * \param fun fonction de dessin
220 * \param width_ largeur de la fenêtre
221 * \param height_ hauteur de la fenêtre
225 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
226 ThreadFunction fun, int width_, int height_)
227 : QWidget(parent, flags)
235 DrawingWindow::~DrawingWindow()
242 //! Change la couleur de dessin.
244 * La couleur est un entier, tel que retourné par getPointColor.
245 * Normalement de la forme #00RRGGBB.
247 * \param color couleur
249 * \see setColor(const char *), setColor(float, float, float),
250 * setBgColor(unsigned int),
253 void DrawingWindow::setColor(unsigned int color)
255 setColor(QColor::fromRgb(color));
258 //! Change la couleur de dessin.
260 * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
262 * \param name nom de couleur
264 * \see setColor(unsigned int), setColor(float, float, float),
265 * setBgColor(const char *)
266 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
268 void DrawingWindow::setColor(const char *name)
270 setColor(QColor(name));
273 //! Change la couleur de dessin.
275 * Les composantes de rouge, vert et bleu de la couleur doivent être
276 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
277 * du noir; si les trois composantes sont à 1, on obtient du blanc.
279 * \param red composante de rouge
280 * \param green composante de vert
281 * \param blue composante de bleu
283 * \see setColor(unsigned int), setColor(const char *),
284 * setBgColor(float, float, float)
286 void DrawingWindow::setColor(float red, float green, float blue)
288 setColor(QColor::fromRgbF(red, green, blue));
291 //! Change la couleur de fond.
293 * \param color couleur
295 * \see setBgColor(const char *), setBgColor(float, float, float),
296 * setColor(unsigned int),
300 void DrawingWindow::setBgColor(unsigned int color)
302 setBgColor(QColor::fromRgb(color));
305 //! Change la couleur de fond.
307 * \param name nom de couleur
309 * \see setBgColor(unsigned int), setBgColor(float, float, float),
310 * setColor(const char *),
312 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
314 void DrawingWindow::setBgColor(const char *name)
316 setBgColor(QColor(name));
319 //! Change la couleur de fond.
321 * \param red composante de rouge
322 * \param green composante de vert
323 * \param blue composante de bleu
325 * \see setBgColor(unsigned int), setBgColor(const char *),
326 * setColor(float, float, float),
329 void DrawingWindow::setBgColor(float red, float green, float blue)
331 setBgColor(QColor::fromRgbF(red, green, blue));
334 //! Efface la fenêtre.
336 * La fenêtre est effacée avec la couleur de fond courante.
340 void DrawingWindow::clearGraph()
342 safeLock(imageMutex);
343 painter->fillRect(image->rect(), getBgColor());
345 safeUnlock(imageMutex);
348 //! Dessine un point.
350 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
353 * \param x, y coordonnées du point
357 void DrawingWindow::drawPoint(int x, int y)
359 safeLock(imageMutex);
360 painter->drawPoint(x, y);
362 safeUnlock(imageMutex);
365 //! Dessine un segment.
367 * Dessine un segement de droite entre les coordonnées (x1, y1) et
368 * (x2, y2), avec la couleur de dessin courante.
370 * \param x1, y1 coordonnées d'une extrémité du segment
371 * \param x2, y2 coordonnées de l'autre extrémité du segment
375 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
377 safeLock(imageMutex);
378 painter->drawLine(x1, y1, x2, y2);
379 dirty(x1, y1, x2, y2);
380 safeUnlock(imageMutex);
383 //! Dessine un rectangle.
385 * Dessine le rectangle parallèle aux axes et défini par les
386 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
387 * la couleur de dessin courante.
389 * \param x1, y1 coordonnées d'un sommet du rectangle
390 * \param x2, y2 coordonnées du sommet opposé du rectangle
392 * \see fillRect, setColor
394 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
397 r.setCoords(x1, y1, x2 - 1, y2 - 1);
399 safeLock(imageMutex);
400 painter->drawRect(r);
401 r.adjust(0, 0, 1, 1);
403 safeUnlock(imageMutex);
406 //! Dessine un rectangle plein.
408 * Dessine le rectangle plein parallèle aux axes et défini par les
409 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
410 * la couleur de dessin courante.
412 * \param x1, y1 coordonnées d'un sommet du rectangle
413 * \param x2, y2 coordonnées du sommet opposé du rectangle
415 * \see drawRect, setColor
417 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
419 painter->setBrush(getColor());
420 drawRect(x1, y1, x2, y2);
421 painter->setBrush(Qt::NoBrush);
424 //! Dessine un cercle.
426 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
427 * couleur de dessin courante.
429 * \param x, y coordonnées du centre du cercle
430 * \param r rayon du cercle
432 * \see fillCircle, setColor
434 void DrawingWindow::drawCircle(int x, int y, int r)
437 rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
438 safeLock(imageMutex);
439 painter->drawEllipse(rect);
440 rect.adjust(0, 0, 1, 1);
442 safeUnlock(imageMutex);
445 //! Dessine un disque.
447 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
448 * Utilise la couleur de dessin courante.
450 * \param x, y coordonnées du centre du disque
451 * \param r rayon du disque
453 * \see drawCircle, setColor
455 void DrawingWindow::fillCircle(int x, int y, int r)
457 painter->setBrush(getColor());
459 painter->setBrush(Qt::NoBrush);
464 * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
465 * d'alignement flags. Le texte est écrit avec la couleur de dessin
466 * courante. Les flags sont une combinaison (ou binaire) de
467 * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
468 * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
469 * texte est aligné en haut à gauche.
471 * \param x, y coordonnées du texte
472 * \param text texte à écrire
473 * \param flags paramètres d'alignement
475 * \see drawTextBg, setColor
476 * \see QPainter::drawText
478 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
481 if (!terminateThread) {
482 qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
483 syncCondition.wait(&syncMutex);
485 safeUnlock(syncMutex);
488 //! Écrit du texte sur fond coloré.
490 * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
491 * la couleur de fond courante.
493 * \param x, y coordonnées du texte
494 * \param text texte à écrire
495 * \param flags paramètres d'alignement
497 * \see drawText, setColor, setColorBg
499 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
501 painter->setBackgroundMode(Qt::OpaqueMode);
502 drawText(x, y, text, flags);
503 painter->setBackgroundMode(Qt::TransparentMode);
507 //! Retourne la couleur d'un pixel.
509 * Retourne la couleur du pixel de coordonnées (x, y). La valeur
510 * retournée peut servir de paramètres à setColor(unsigned int) ou
511 * setBgColor(unsigned int).
513 * \param x, y coordonnées du pixel
514 * \return couleur du pixel
516 * \see setColor(unsigned int), setBgColor(unsigned int)
518 unsigned int DrawingWindow::getPointColor(int x, int y)
520 return image->pixel(x, y);
523 //! Synchronise le contenu de la fenêtre.
525 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
526 * n'est pas affiché immédiatement. L'appel à sync permet de
527 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
528 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
531 * \param time durée maximale de l'attente
532 * \return true si la fenêtre a pu être synchronisée
534 bool DrawingWindow::sync(unsigned long time)
538 if (terminateThread) {
541 qApp->postEvent(this, new SyncRequestEvent());
542 synced = syncCondition.wait(&syncMutex, time);
544 safeUnlock(syncMutex);
548 //! Ferme la fenêtre graphique.
549 void DrawingWindow::closeGraph()
551 qApp->postEvent(this, new CloseRequestEvent());
554 //! Suspend l'exécution pendant un certain temps.
556 * \param secs temps d'attente en seconde
558 void DrawingWindow::sleep(unsigned long secs)
560 DrawingThread::sleep(secs);
563 //! Suspend l'exécution pendant un certain temps.
565 * \param msecs temps d'attente en millisecondes
567 void DrawingWindow::msleep(unsigned long msecs)
569 DrawingThread::msleep(msecs);
572 //! Suspend l'exécution pendant un certain temps.
574 * \param usecs temps d'attente en microsecondes
576 void DrawingWindow::usleep(unsigned long usecs)
578 DrawingThread::usleep(usecs);
584 void DrawingWindow::closeEvent(QCloseEvent *ev)
589 terminateThread = true; // this flag is needed for the case
590 // where the following wakeAll() call
591 // occurs between the
592 // setTerminationEnable(false) and the
593 // mutex lock in safeLock() called
595 syncCondition.wakeAll();
597 QWidget::closeEvent(ev);
604 void DrawingWindow::customEvent(QEvent *ev)
606 switch ((int )ev->type()) {
613 case DrawTextRequest:
614 DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
615 realDrawText(tev->x, tev->y, tev->text, tev->flags);
623 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
641 void DrawingWindow::paintEvent(QPaintEvent *ev)
643 QPainter widgetPainter(this);
645 QImage imageCopy(*image);
647 QRect rect = ev->rect();
648 widgetPainter.drawImage(rect, imageCopy, rect);
654 void DrawingWindow::showEvent(QShowEvent *ev)
656 QWidget::showEvent(ev);
659 timer.start(paintInterval, this);
660 thread->start_once(QThread::IdlePriority);
666 void DrawingWindow::timerEvent(QTimerEvent *ev)
668 if (ev->timerId() == timer.timerId()) {
670 timer.start(paintInterval, this);
672 QWidget::timerEvent(ev);
676 //--- DrawingWindow (private methods) ----------------------------------
678 //! Fonction d'initialisation.
680 * Fonction appelée par les différents constructeurs.
682 * \param fun fonction de dessin
684 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
686 terminateThread = false;
688 image = new QImage(width, height, QImage::Format_RGB32);
689 painter = new QPainter(image);
690 thread = new DrawingThread(*this, fun);
692 setFocusPolicy(Qt::StrongFocus);
693 setFixedSize(image->size());
694 setAttribute(Qt::WA_OpaquePaintEvent);
704 //! Change la couleur de dessin.
706 * \param color couleur
709 void DrawingWindow::setColor(const QColor& color)
711 QPen pen(painter->pen());
713 painter->setPen(pen);
716 //! Change la couleur de fond.
718 * \param color couleur
721 void DrawingWindow::setBgColor(const QColor& color)
723 painter->setBackground(color);
726 //! Retourne la couleur de dessin courante.
728 * \return couleur de dessin courante
731 QColor DrawingWindow::getColor()
733 return painter->pen().color();
736 //! Retourne la couleur de fond courante.
738 * \return couleur de fond courante
741 QColor DrawingWindow::getBgColor()
743 return painter->background().color();
746 //! Verrouille un mutex.
748 * S'assure que le thread courant ne peut pas être terminé s'il
749 * détient un mutex. Pendant de safeUnlock.
751 * \param mutex le mutex à verrouiller
756 void DrawingWindow::safeLock(QMutex &mutex)
758 if (lockCount++ == 0)
759 thread->setTerminationEnabled(false);
763 //! Déverrouille un mutex.
765 * S'assure que le thread courant ne peut pas être terminé s'il
766 * détient un mutex. Pendant de safeLock.
768 * \param mutex le mutex à déverrouiller
773 void DrawingWindow::safeUnlock(QMutex &mutex)
776 if (--lockCount == 0)
777 thread->setTerminationEnabled(true);
780 //! Marque l'image entière comme non à jour.
782 void DrawingWindow::dirty()
785 dirtyRect = image->rect();
788 //! Marque un point de l'image comme non à jour.
790 * \param x, y coordonnées du point
793 void DrawingWindow::dirty(int x, int y)
795 dirty(QRect(x, y, 1, 1));
798 //! Marque une zone de l'image comme non à jour.
800 * La zone est définie par un rectangle dont les coordonnées de deux
801 * sommets oppposés sont données.
803 * \param x1, y1 coordonnées d'un sommet du rectangle
804 * \param x2, y2 coordonnées du sommet opposé du rectangle
807 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
810 r.setCoords(x1, y1, x2, y2);
811 dirty(r.normalized());
814 //! Marque une zone de l'image comme non à jour.
816 * \param rect rectangle délimitant la zone
818 void DrawingWindow::dirty(const QRect &rect)
828 //! Génère un update si besoin.
830 * Génère une demande de mise à jour de la fenêtre (appel à update)
831 * s'il y en a besoin.
833 void DrawingWindow::mayUpdate()
836 bool dirty = dirtyFlag;
837 QRect rect = dirtyRect;
844 //! Fonction bas-niveau pour sync.
846 * Fonction de synchronisation dans le thread principal.
848 * \see sync, customEvent
850 void DrawingWindow::realSync()
853 qApp->sendPostedEvents(this, QEvent::UpdateLater);
854 qApp->sendPostedEvents(this, QEvent::UpdateRequest);
855 qApp->sendPostedEvents(this, QEvent::Paint);
856 qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
857 QEventLoop::ExcludeSocketNotifiers |
858 QEventLoop::DeferredDeletion |
859 QEventLoop::X11ExcludeTimers);
863 syncCondition.wakeAll();
867 //! Fonction bas-niveau pour drawText.
869 * Le rendu de texte doit être fait dans le thread principal. D'où
870 * les manipulations tordues et la synchronisation qui s'en suit.
872 * \param x, y, text, flags cf. drawText
874 * \see drawText, customEvent
876 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
878 QRect r(image->rect());
879 switch (flags & Qt::AlignHorizontal_Mask) {
883 case Qt::AlignHCenter:
885 r.setLeft(2 * x - width + 1);
892 switch (flags & Qt::AlignVertical_Mask) {
893 case Qt::AlignBottom:
896 case Qt::AlignVCenter:
898 r.setTop(2 * y - height + 1);
906 painter->drawText(r, flags, text, &r);
908 syncCondition.wakeAll();
912 //--- DrawingThread ----------------------------------------------------
915 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
918 , started_once(false)
922 //! Démarre le thread si ce n'a pas encore été fait.
923 void DrawingThread::start_once(Priority priority)
931 //! La vraie fonction pour le thread.
932 void DrawingThread::run()
934 threadFunction(drawingWindow);