1 #include "DrawingWindow.h"
2 #include <QApplication>
8 class DrawingThread: public QThread {
10 DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
11 void start_once(Priority priority = InheritPriority);
17 DrawingWindow &drawingWindow;
18 DrawingWindow::ThreadFunction threadFunction;
21 friend class DrawingWindow;
25 SyncRequest = QEvent::User, //!< Demande de synchronisation.
26 CloseRequest, //!< Demande de fermeture de la fenêtre.
27 DrawTextRequest, //!< Demande d'écriture de texte.
30 //! Demande de synchronisation.
31 class SyncRequestEvent: public QEvent {
33 SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
37 //! Demande de fermeture de fenêtre.
38 class CloseRequestEvent: public QEvent {
40 CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
44 //! Demande de tracé de texte.
45 class DrawTextEvent: public QEvent {
51 DrawTextEvent(int x_, int y_, const char* text_, int flags_)
52 : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
53 , x(x_), y(y_), text(text_), flags(flags_)
57 //--- DrawingWindow ----------------------------------------------------
59 /*! \class DrawingWindow
60 * \brief Fenêtre de dessin.
65 /*! \typedef DrawingWindow::ThreadFunction
66 * \brief Type de la fonction de dessin, passée en paramètre de construction.
68 /*! \var DrawingWindow::DEFAULT_WIDTH
69 * \brief Largeur par défaut de la fenêtre.
71 /*! \var DrawingWindow::DEFAULT_HEIGHT
72 * \brief Hauteur par défaut de la fenêtre.
74 /*! \var DrawingWindow::width
75 * \brief Largeur de la fenêtre.
77 /*! \var DrawingWindow::height
78 * \brief Hauteur de la fenêtre.
80 /*! \var DrawingWindow::paintInterval
81 * \brief Intervalle de temps entre deux rendus (ms).
86 * Construit une nouvelle fenêtre de dessin avec les dimensions
87 * passées en paramètres. La fonction fun sera exécutée dans un
90 * \param fun fonction de dessin
91 * \param width_ largeur de la fenêtre
92 * \param height_ hauteur de la fenêtre
96 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
106 * Construit un nouveau widget de dessin avec les dimensions passées
107 * en paramètres. La fonction fun sera exécutée dans un nouveau
110 * \param parent widget parent
111 * \param fun fonction de dessin
112 * \param width_ largeur de la fenêtre
113 * \param height_ hauteur de la fenêtre
117 DrawingWindow::DrawingWindow(QWidget *parent,
118 ThreadFunction fun, int width_, int height_)
128 * Construit un nouveau widget de dessin avec les dimensions passées
129 * en paramètres. La fonction fun sera exécutée dans un nouveau
132 * \param parent widget parent
133 * \param flags flags passés au constructeur de QWidget
134 * \param fun fonction de dessin
135 * \param width_ largeur de la fenêtre
136 * \param height_ hauteur de la fenêtre
140 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
141 ThreadFunction fun, int width_, int height_)
142 : QWidget(parent, flags)
150 DrawingWindow::~DrawingWindow()
157 //! Change la couleur de dessin.
159 * La couleur est un entier, tel que retourné par getPointColor.
160 * Normalement de la forme #00RRGGBB.
162 * \param color couleur
164 * \see setColor(const char *), setColor(float, float, float),
165 * setBgColor(unsigned int),
168 void DrawingWindow::setColor(unsigned int color)
170 setColor(QColor::fromRgb(color));
173 //! Change la couleur de dessin.
175 * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
177 * \param name nom de couleur
179 * \see setColor(unsigned int), setColor(float, float, float),
180 * setBgColor(const char *)
181 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
183 void DrawingWindow::setColor(const char *name)
185 setColor(QColor(name));
188 //! Change la couleur de dessin.
190 * Les composantes de rouge, vert et bleu de la couleur doivent être
191 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
192 * du noir; si les trois composantes sont à 1, on obtient du blanc.
194 * \param red composante de rouge
195 * \param green composante de vert
196 * \param blue composante de bleu
198 * \see setColor(unsigned int), setColor(const char *),
199 * setBgColor(float, float, float)
201 void DrawingWindow::setColor(float red, float green, float blue)
203 setColor(QColor::fromRgbF(red, green, blue));
206 //! Change la couleur de fond.
208 * \param color couleur
210 * \see setBgColor(const char *), setBgColor(float, float, float),
211 * setColor(unsigned int),
215 void DrawingWindow::setBgColor(unsigned int color)
217 setBgColor(QColor::fromRgb(color));
220 //! Change la couleur de fond.
222 * \param name nom de couleur
224 * \see setBgColor(unsigned int), setBgColor(float, float, float),
225 * setColor(const char *),
227 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
229 void DrawingWindow::setBgColor(const char *name)
231 setBgColor(QColor(name));
234 //! Change la couleur de fond.
236 * \param red composante de rouge
237 * \param green composante de vert
238 * \param blue composante de bleu
240 * \see setBgColor(unsigned int), setBgColor(const char *),
241 * setColor(float, float, float),
244 void DrawingWindow::setBgColor(float red, float green, float blue)
246 setBgColor(QColor::fromRgbF(red, green, blue));
249 //! Efface la fenêtre.
251 * La fenêtre est effacée avec la couleur de fond courante.
255 void DrawingWindow::clearGraph()
257 safeLock(imageMutex);
258 painter->fillRect(image->rect(), getBgColor());
260 safeUnlock(imageMutex);
263 //! Dessine un point.
265 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
268 * \param x, y coordonnées du point
272 void DrawingWindow::drawPoint(int x, int y)
274 safeLock(imageMutex);
275 painter->drawPoint(x, y);
277 safeUnlock(imageMutex);
280 //! Dessine un segment.
282 * Dessine un segement de droite entre les coordonnées (x1, y1) et
283 * (x2, y2), avec la couleur de dessin courante.
285 * \param x1, y1 coordonnées d'une extrémité du segment
286 * \param x2, y2 coordonnées de l'autre extrémité du segment
290 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
292 safeLock(imageMutex);
293 painter->drawLine(x1, y1, x2, y2);
294 dirty(x1, y1, x2, y2);
295 safeUnlock(imageMutex);
298 //! Dessine un rectangle.
300 * Dessine le rectangle parallèle aux axes et défini par les
301 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
302 * la couleur de dessin courante.
304 * \param x1, y1 coordonnées d'un sommet du rectangle
305 * \param x2, y2 coordonnées du sommet opposé du rectangle
307 * \see fillRect, setColor
309 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
312 r.setCoords(x1, y1, x2 - 1, y2 - 1);
314 safeLock(imageMutex);
315 painter->drawRect(r);
316 r.adjust(0, 0, 1, 1);
318 safeUnlock(imageMutex);
321 //! Dessine un rectangle plein.
323 * Dessine le rectangle plein parallèle aux axes et défini par les
324 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
325 * la couleur de dessin courante.
327 * \param x1, y1 coordonnées d'un sommet du rectangle
328 * \param x2, y2 coordonnées du sommet opposé du rectangle
330 * \see drawRect, setColor
332 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
334 painter->setBrush(getColor());
335 drawRect(x1, y1, x2, y2);
336 painter->setBrush(Qt::NoBrush);
339 //! Dessine un cercle.
341 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
342 * couleur de dessin courante.
344 * \param x, y coordonnées du centre du cercle
345 * \param r rayon du cercle
347 * \see fillCircle, setColor
349 void DrawingWindow::drawCircle(int x, int y, int r)
352 rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
353 safeLock(imageMutex);
354 painter->drawEllipse(rect);
355 rect.adjust(0, 0, 1, 1);
357 safeUnlock(imageMutex);
360 //! Dessine un disque.
362 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
363 * Utilise la couleur de dessin courante.
365 * \param x, y coordonnées du centre du disque
366 * \param r rayon du disque
368 * \see drawCircle, setColor
370 void DrawingWindow::fillCircle(int x, int y, int r)
372 painter->setBrush(getColor());
374 painter->setBrush(Qt::NoBrush);
379 * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
380 * d'alignement flags. Le texte est écrit avec la couleur de dessin
381 * courante. Les flags sont une combinaison (ou binaire) de
382 * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
383 * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
384 * texte est aligné en haut à gauche.
386 * \param x, y coordonnées du texte
387 * \param text texte à écrire
388 * \param flags paramètres d'alignement
390 * \see drawTextBg, setColor
391 * \see QPainter::drawText
393 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
396 if (!terminateThread) {
397 qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
398 syncCondition.wait(&syncMutex);
400 safeUnlock(syncMutex);
403 //! Écrit du texte sur fond coloré.
405 * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
406 * la couleur de fond courante.
408 * \param x, y coordonnées du texte
409 * \param text texte à écrire
410 * \param flags paramètres d'alignement
412 * \see drawText, setColor, setColorBg
414 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
416 painter->setBackgroundMode(Qt::OpaqueMode);
417 drawText(x, y, text, flags);
418 painter->setBackgroundMode(Qt::TransparentMode);
422 //! Retourne la couleur d'un pixel.
424 * Retourne la couleur du pixel de coordonnées (x, y). La valeur
425 * retournée peut servir de paramètres à setColor(unsigned int) ou
426 * setBgColor(unsigned int).
428 * \param x, y coordonnées du pixel
429 * \return couleur du pixel
431 * \see setColor(unsigned int), setBgColor(unsigned int)
433 unsigned int DrawingWindow::getPointColor(int x, int y)
435 return image->pixel(x, y);
438 //! Synchronise le contenu de la fenêtre.
440 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
441 * n'est pas affiché immédiatement. L'appel à sync permet de
442 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
443 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
446 * \param time durée maximale de l'attente
447 * \return true si la fenêtre a pu être synchronisée
449 bool DrawingWindow::sync(unsigned long time)
453 if (terminateThread) {
456 qApp->postEvent(this, new SyncRequestEvent());
457 synced = syncCondition.wait(&syncMutex, time);
459 safeUnlock(syncMutex);
463 //! Ferme la fenêtre graphique.
464 void DrawingWindow::closeGraph()
466 qApp->postEvent(this, new CloseRequestEvent());
469 //! Suspend l'exécution pendant un certain temps.
471 * \param secs temps d'attente en seconde
473 void DrawingWindow::sleep(unsigned long secs)
475 DrawingThread::sleep(secs);
478 //! Suspend l'exécution pendant un certain temps.
480 * \param msecs temps d'attente en millisecondes
482 void DrawingWindow::msleep(unsigned long msecs)
484 DrawingThread::msleep(msecs);
487 //! Suspend l'exécution pendant un certain temps.
489 * \param usecs temps d'attente en microsecondes
491 void DrawingWindow::usleep(unsigned long usecs)
493 DrawingThread::usleep(usecs);
499 void DrawingWindow::closeEvent(QCloseEvent *ev)
504 terminateThread = true; // this flag is needed for the case
505 // where the following wakeAll() call
506 // occurs between the
507 // setTerminationEnable(false) and the
508 // mutex lock in safeLock() called
510 syncCondition.wakeAll();
512 QWidget::closeEvent(ev);
519 void DrawingWindow::customEvent(QEvent *ev)
521 switch ((int )ev->type()) {
528 case DrawTextRequest:
529 DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
530 realDrawText(tev->x, tev->y, tev->text, tev->flags);
538 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
556 void DrawingWindow::paintEvent(QPaintEvent *ev)
558 QPainter widgetPainter(this);
560 QImage imageCopy(*image);
562 QRect rect = ev->rect();
563 widgetPainter.drawImage(rect, imageCopy, rect);
569 void DrawingWindow::showEvent(QShowEvent *ev)
571 QWidget::showEvent(ev);
574 timer.start(paintInterval, this);
575 thread->start_once(QThread::IdlePriority);
581 void DrawingWindow::timerEvent(QTimerEvent *ev)
583 if (ev->timerId() == timer.timerId()) {
585 timer.start(paintInterval, this);
587 QWidget::timerEvent(ev);
591 //--- DrawingWindow (private methods) ----------------------------------
593 //! Fonction d'initialisation.
595 * Fonction appelée par les différents constructeurs.
597 * \param fun fonction de dessin
599 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
601 terminateThread = false;
603 image = new QImage(width, height, QImage::Format_RGB32);
604 painter = new QPainter(image);
605 thread = new DrawingThread(*this, fun);
607 setFocusPolicy(Qt::StrongFocus);
608 setFixedSize(image->size());
609 setAttribute(Qt::WA_OpaquePaintEvent);
619 //! Change la couleur de dessin.
621 * \param color couleur
624 void DrawingWindow::setColor(const QColor& color)
626 QPen pen(painter->pen());
628 painter->setPen(pen);
631 //! Change la couleur de fond.
633 * \param color couleur
636 void DrawingWindow::setBgColor(const QColor& color)
638 painter->setBackground(color);
641 //! Retourne la couleur de dessin courante.
643 * \return couleur de dessin courante
646 QColor DrawingWindow::getColor()
648 return painter->pen().color();
651 //! Retourne la couleur de fond courante.
653 * \return couleur de fond courante
656 QColor DrawingWindow::getBgColor()
658 return painter->background().color();
661 //! Verrouille un mutex.
663 * S'assure que le thread courant ne peut pas être terminé s'il
664 * détient un mutex. Pendant de safeUnlock.
666 * \param mutex le mutex à verrouiller
671 void DrawingWindow::safeLock(QMutex &mutex)
673 if (lockCount++ == 0)
674 thread->setTerminationEnabled(false);
678 //! Déverrouille un mutex.
680 * S'assure que le thread courant ne peut pas être terminé s'il
681 * détient un mutex. Pendant de safeLock.
683 * \param mutex le mutex à déverrouiller
688 void DrawingWindow::safeUnlock(QMutex &mutex)
691 if (--lockCount == 0)
692 thread->setTerminationEnabled(true);
695 //! Marque l'image entière comme non à jour.
697 void DrawingWindow::dirty()
700 dirtyRect = image->rect();
703 //! Marque un point de l'image comme non à jour.
705 * \param x, y coordonnées du point
708 void DrawingWindow::dirty(int x, int y)
710 dirty(QRect(x, y, 1, 1));
713 //! Marque une zone de l'image comme non à jour.
715 * La zone est définie par un rectangle dont les coordonnées de deux
716 * sommets oppposés sont données.
718 * \param x1, y1 coordonnées d'un sommet du rectangle
719 * \param x2, y2 coordonnées du sommet opposé du rectangle
722 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
725 r.setCoords(x1, y1, x2, y2);
726 dirty(r.normalized());
729 //! Marque une zone de l'image comme non à jour.
731 * \param rect rectangle délimitant la zone
733 void DrawingWindow::dirty(const QRect &rect)
743 //! Génère un update si besoin.
745 * Génère une demande de mise à jour de la fenêtre (appel à update)
746 * s'il y en a besoin.
748 void DrawingWindow::mayUpdate()
751 bool dirty = dirtyFlag;
752 QRect rect = dirtyRect;
759 //! Fonction bas-niveau pour sync.
761 * Fonction de synchronisation dans le thread principal.
763 * \see sync, customEvent
765 void DrawingWindow::realSync()
768 qApp->sendPostedEvents(this, QEvent::UpdateLater);
769 qApp->sendPostedEvents(this, QEvent::UpdateRequest);
770 qApp->sendPostedEvents(this, QEvent::Paint);
771 qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
772 QEventLoop::ExcludeSocketNotifiers |
773 QEventLoop::DeferredDeletion |
774 QEventLoop::X11ExcludeTimers);
778 syncCondition.wakeAll();
782 //! Fonction bas-niveau pour drawText.
784 * Le rendu de texte doit être fait dans le thread principal. D'où
785 * les manipulations tordues et la synchronisation qui s'en suit.
787 * \param x, y, text, flags cf. drawText
789 * \see drawText, customEvent
791 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
793 QRect r(image->rect());
794 switch (flags & Qt::AlignHorizontal_Mask) {
798 case Qt::AlignHCenter:
800 r.setLeft(2 * x - width + 1);
807 switch (flags & Qt::AlignVertical_Mask) {
808 case Qt::AlignBottom:
811 case Qt::AlignVCenter:
813 r.setTop(2 * y - height + 1);
821 painter->drawText(r, flags, text, &r);
823 syncCondition.wakeAll();
827 //--- DrawingThread ----------------------------------------------------
830 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
833 , started_once(false)
837 //! Démarre le thread si ce n'a pas encore été fait.
838 void DrawingThread::start_once(Priority priority)
846 //! La vraie fonction pour le thread.
847 void DrawingThread::run()
849 threadFunction(drawingWindow);