1 #include "DrawingWindow.h"
2 #include <QApplication>
9 class DrawingThread: public QThread {
11 DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
12 void start_once(Priority priority = InheritPriority);
18 DrawingWindow &drawingWindow;
19 DrawingWindow::ThreadFunction threadFunction;
22 friend class DrawingWindow;
26 SyncRequest = QEvent::User, //!< Demande de synchronisation.
27 CloseRequest, //!< Demande de fermeture de la fenêtre.
28 DrawTextRequest, //!< Demande d'écriture de texte.
31 /*! Demande de synchronisation.
33 class SyncRequestEvent: public QEvent {
35 SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
39 /*! Demande de fermeture de fenêtre.
41 class CloseRequestEvent: public QEvent {
43 CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
47 /*! Demande de tracé de texte.
49 class DrawTextEvent: public QEvent {
55 DrawTextEvent(int x_, int y_, const char* text_, int flags_)
56 : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
57 , x(x_), y(y_), text(text_), flags(flags_)
61 //--- DrawingWindow ----------------------------------------------------
65 * Construit une nouvelle fenêtre de dessin avec les dimensions
66 * passées en paramètres. La fonction fun sera exécutée dans un
69 * \param fun fonction de dessin
70 * \param width_ largeur de la fenêtre
71 * \param height_ hauteur de la fenêtre
75 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
85 * Construit un nouveau widget de dessin avec les dimensions passées
86 * en paramètres. La fonction fun sera exécutée dans un nouveau
89 * \param parent widget parent
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(QWidget *parent,
97 ThreadFunction fun, int width_, int height_)
107 * Construit un nouveau widget de dessin avec les dimensions passées
108 * en paramètres. La fonction fun sera exécutée dans un nouveau
111 * \param parent widget parent
112 * \param flags flags passés au constructeur de QWidget
113 * \param fun fonction de dessin
114 * \param width_ largeur de la fenêtre
115 * \param height_ hauteur de la fenêtre
119 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
120 ThreadFunction fun, int width_, int height_)
121 : QWidget(parent, flags)
130 DrawingWindow::~DrawingWindow()
137 /*! Change la couleur de dessin.
139 * La couleur est un entier, tel que retourné par getPointColor.
140 * Normalement de la forme #00RRGGBB.
142 * \param color couleur
144 * \see setColor(const char *), setColor(float, float, float),
145 * setBgColor(unsigned int),
148 void DrawingWindow::setColor(unsigned int color)
150 setColor(QColor::fromRgb(color));
153 /*! Change la couleur de dessin.
155 * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
157 * \param name nom de couleur
159 * \see setColor(unsigned int), setColor(float, float, float),
160 * setBgColor(const char *)
161 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
163 void DrawingWindow::setColor(const char *name)
165 setColor(QColor(name));
168 /*! Change la couleur de dessin.
170 * Les composantes de rouge, vert et bleu de la couleur doivent être
171 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
172 * du noir; si les trois composantes sont à 1, on obtient du blanc.
174 * \param red composante de rouge
175 * \param green composante de vert
176 * \param blue composante de bleu
178 * \see setColor(unsigned int), setColor(const char *),
179 * setBgColor(float, float, float)
181 void DrawingWindow::setColor(float red, float green, float blue)
183 setColor(QColor::fromRgbF(red, green, blue));
186 /*! Change la couleur de fond.
188 * \param color couleur
190 * \see setBgColor(const char *), setBgColor(float, float, float),
191 * setColor(unsigned int),
195 void DrawingWindow::setBgColor(unsigned int color)
197 setBgColor(QColor::fromRgb(color));
200 /*! Change la couleur de fond.
202 * \param name nom de couleur
204 * \see setBgColor(unsigned int), setBgColor(float, float, float),
205 * setColor(const char *),
207 * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
209 void DrawingWindow::setBgColor(const char *name)
211 setBgColor(QColor(name));
214 /*! Change la couleur de fond.
216 * \param red composante de rouge
217 * \param green composante de vert
218 * \param blue composante de bleu
220 * \see setBgColor(unsigned int), setBgColor(const char *),
221 * setColor(float, float, float),
224 void DrawingWindow::setBgColor(float red, float green, float blue)
226 setBgColor(QColor::fromRgbF(red, green, blue));
229 /*! Efface la fenêtre.
231 * La fenêtre est effacée avec la couleur de fond courante.
235 void DrawingWindow::clearGraph()
237 safeLock(imageMutex);
238 painter->fillRect(image->rect(), getBgColor());
240 safeUnlock(imageMutex);
243 /*! Dessine un point.
245 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
248 * \param x, y coordonnées du point
252 void DrawingWindow::drawPoint(int x, int y)
254 safeLock(imageMutex);
255 painter->drawPoint(x, y);
257 safeUnlock(imageMutex);
260 /*! Dessine un segment.
262 * Dessine un segement de droite entre les coordonnées (x1, y1) et
263 * (x2, y2), avec la couleur de dessin courante.
265 * \param x1, y1 coordonnées d'une extrémité du segment
266 * \param x2, y2 coordonnées de l'autre extrémité du segment
270 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
272 safeLock(imageMutex);
273 painter->drawLine(x1, y1, x2, y2);
274 dirty(x1, y1, x2, y2);
275 safeUnlock(imageMutex);
278 /*! Dessine un rectangle.
280 * Dessine le rectangle parallèle aux axes et défini par les
281 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
282 * la couleur de dessin courante.
284 * \param x1, y1 coordonnées d'un sommet du rectangle
285 * \param x2, y2 coordonnées du sommet opposé du rectangle
287 * \see fillRect, setColor
289 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
292 r.setCoords(x1, y1, x2 - 1, y2 - 1);
294 safeLock(imageMutex);
295 painter->drawRect(r);
296 r.adjust(0, 0, 1, 1);
298 safeUnlock(imageMutex);
301 /*! Dessine un rectangle plein.
303 * Dessine le rectangle plein parallèle aux axes et défini par les
304 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
305 * la couleur de dessin courante.
307 * \param x1, y1 coordonnées d'un sommet du rectangle
308 * \param x2, y2 coordonnées du sommet opposé du rectangle
310 * \see drawRect, setColor
312 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
314 painter->setBrush(getColor());
315 drawRect(x1, y1, x2, y2);
316 painter->setBrush(Qt::NoBrush);
319 /*! Dessine un cercle.
321 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
322 * couleur de dessin courante.
324 * \param x, y coordonnées du centre du cercle
325 * \param r rayon du cercle
327 * \see fillCircle, setColor
329 void DrawingWindow::drawCircle(int x, int y, int r)
332 rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
333 safeLock(imageMutex);
334 painter->drawEllipse(rect);
335 rect.adjust(0, 0, 1, 1);
337 safeUnlock(imageMutex);
340 /*! Dessine un disque.
342 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
343 * Utilise la couleur de dessin courante.
345 * \param x, y coordonnées du centre du disque
346 * \param r rayon du disque
348 * \see drawCircle, setColor
350 void DrawingWindow::fillCircle(int x, int y, int r)
352 painter->setBrush(getColor());
354 painter->setBrush(Qt::NoBrush);
359 * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
360 * d'alignement flags. Le texte est écrit avec la couleur de dessin
361 * courante. Les flags sont une combinaison (ou binaire) de
362 * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
363 * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
364 * texte est aligné en haut à gauche.
366 * \param x, y coordonnées du texte
367 * \param text texte à écrire
368 * \param flags paramètres d'alignement
370 * \see drawTextBg, setColor
371 * \see QPainter::drawText
373 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
376 if (!terminateThread) {
377 qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
378 syncCondition.wait(&syncMutex);
380 safeUnlock(syncMutex);
383 /*! Écrit du texte sur fond coloré.
385 * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
386 * la couleur de fond courante.
388 * \param x, y coordonnées du texte
389 * \param text texte à écrire
390 * \param flags paramètres d'alignement
392 * \see drawText, setColor, setColorBg
394 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
396 painter->setBackgroundMode(Qt::OpaqueMode);
397 drawText(x, y, text, flags);
398 painter->setBackgroundMode(Qt::TransparentMode);
402 /*! Retourne la couleur d'un pixel.
404 * Retourne la couleur du pixel de coordonnées (x, y). La valeur
405 * retournée peut servir de paramètres à setColor(unsigned int) ou
406 * setBgColor(unsigned int).
408 * \param x, y coordonnées du pixel
409 * \return couleur du pixel
411 * \see setColor(unsigned int), setBgColor(unsigned int)
413 unsigned int DrawingWindow::getPointColor(int x, int y)
415 return image->pixel(x, y);
418 /*! Synchronise le contenu de la fenêtre.
420 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
421 * n'est pas affiché immédiatement. L'appel à sync permet de
422 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
423 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
426 * \param time durée maximale de l'attente
427 * \return true si la fenêtre a pu être synchronisée
429 bool DrawingWindow::sync(unsigned long time)
433 if (terminateThread) {
436 qApp->postEvent(this, new SyncRequestEvent());
437 synced = syncCondition.wait(&syncMutex, time);
439 safeUnlock(syncMutex);
443 /*! Ferme la fenêtre graphique.
445 void DrawingWindow::closeGraph()
447 qApp->postEvent(this, new CloseRequestEvent());
450 /*! Suspend l'exécution pendant un certain temps.
452 * \param secs temps d'attente en seconde
454 void DrawingWindow::sleep(unsigned long secs)
456 DrawingThread::sleep(secs);
459 /*! Suspend l'exécution pendant un certain temps.
461 * \param msecs temps d'attente en millisecondes
463 void DrawingWindow::msleep(unsigned long msecs)
465 DrawingThread::msleep(msecs);
468 /*! Suspend l'exécution pendant un certain temps.
470 * \param usecs temps d'attente en microsecondes
472 void DrawingWindow::usleep(unsigned long usecs)
474 DrawingThread::usleep(usecs);
479 void DrawingWindow::closeEvent(QCloseEvent *ev)
484 terminateThread = true; // this flag is needed for the case
485 // where the following wakeAll() call
486 // occurs between the
487 // setTerminationEnable(false) and the
488 // mutex lock in safeLock() called
490 syncCondition.wakeAll();
492 QWidget::closeEvent(ev);
498 void DrawingWindow::customEvent(QEvent *ev)
500 switch ((int )ev->type()) {
507 case DrawTextRequest:
508 DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
509 realDrawText(tev->x, tev->y, tev->text, tev->flags);
516 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
533 void DrawingWindow::paintEvent(QPaintEvent *ev)
535 QPainter widgetPainter(this);
537 QImage imageCopy(*image);
539 QRect rect = ev->rect();
540 widgetPainter.drawImage(rect, imageCopy, rect);
545 void DrawingWindow::showEvent(QShowEvent *ev)
547 QWidget::showEvent(ev);
550 timer.start(paintInterval, this);
551 thread->start_once(QThread::IdlePriority);
556 void DrawingWindow::timerEvent(QTimerEvent *ev)
558 if (ev->timerId() == timer.timerId()) {
560 timer.start(paintInterval, this);
562 QWidget::timerEvent(ev);
566 //--- DrawingWindow (private methods) ----------------------------------
568 /*! Fonction d'initialisation.
570 * Fonction appelée par les différents constructeurs.
572 * \param fun fonction de dessin
574 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
576 terminateThread = false;
578 image = new QImage(width, height, QImage::Format_RGB32);
579 painter = new QPainter(image);
580 thread = new DrawingThread(*this, fun);
582 setFocusPolicy(Qt::StrongFocus);
583 setFixedSize(image->size());
584 setAttribute(Qt::WA_OpaquePaintEvent);
594 /*! Change la couleur de dessin.
596 * \param color couleur
599 void DrawingWindow::setColor(const QColor& color)
601 QPen pen(painter->pen());
603 painter->setPen(pen);
606 /*! Change la couleur de fond.
608 * \param color couleur
611 void DrawingWindow::setBgColor(const QColor& color)
613 painter->setBackground(color);
616 /*! Retourne la couleur de dessin courante.
618 * \return couleur de dessin courante
621 QColor DrawingWindow::getColor()
623 return painter->pen().color();
626 /*! Retourne la couleur de fond courante.
628 * \return couleur de fond courante
631 QColor DrawingWindow::getBgColor()
633 return painter->background().color();
636 /*! Verrouille un mutex.
638 * S'assure que le thread courant ne peut pas être terminé s'il
639 * détient un mutex. Pendant de safeUnlock.
641 * \param mutex le mutex à verrouiller
646 void DrawingWindow::safeLock(QMutex &mutex)
648 if (lockCount++ == 0)
649 thread->setTerminationEnabled(false);
653 /*! Déverrouille un mutex.
655 * S'assure que le thread courant ne peut pas être terminé s'il
656 * détient un mutex. Pendant de safeLock.
658 * \param mutex le mutex à déverrouiller
663 void DrawingWindow::safeUnlock(QMutex &mutex)
666 if (--lockCount == 0)
667 thread->setTerminationEnabled(true);
670 /*! Marque l'image entière comme non à jour.
673 void DrawingWindow::dirty()
676 dirtyRect = image->rect();
679 /*! Marque un point de l'image comme non à jour.
681 * \param x, y coordonnées du point
684 void DrawingWindow::dirty(int x, int y)
686 dirty(QRect(x, y, 1, 1));
689 /*! Marque une zone de l'image comme non à jour.
691 * La zone est définie par un rectangle dont les coordonnées de deux
692 * sommets oppposés sont données.
694 * \param x1, y1 coordonnées d'un sommet du rectangle
695 * \param x2, y2 coordonnées du sommet opposé du rectangle
698 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
701 r.setCoords(x1, y1, x2, y2);
702 dirty(r.normalized());
705 /*! Marque une zone de l'image comme non à jour.
707 * \param rect rectangle délimitant la zone
709 void DrawingWindow::dirty(const QRect &rect)
719 /*! Génère un update si besoin.
721 * Génère une demande de mise à jour de la fenêtre (appel à update)
722 * s'il y en a besoin.
724 void DrawingWindow::mayUpdate()
727 bool dirty = dirtyFlag;
728 QRect rect = dirtyRect;
735 /*! Fonction bas-niveau pour sync.
737 * Fonction de synchronisation dans le thread principal.
739 * \see sync, customEvent
741 void DrawingWindow::realSync()
744 qApp->sendPostedEvents(this, QEvent::UpdateLater);
745 qApp->sendPostedEvents(this, QEvent::UpdateRequest);
746 qApp->sendPostedEvents(this, QEvent::Paint);
747 qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
748 QEventLoop::ExcludeSocketNotifiers |
749 QEventLoop::DeferredDeletion |
750 QEventLoop::X11ExcludeTimers);
754 syncCondition.wakeAll();
758 /*! Fonction bas-niveau pour drawText.
760 * Le rendu de texte doit être fait dans le thread principal. D'où
761 * les manipulations tordues et la synchronisation qui s'en suit.
763 * \param x, y, text, flags cf. drawText
765 * \see drawText, customEvent
767 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
769 QRect r(image->rect());
770 switch (flags & Qt::AlignHorizontal_Mask) {
774 case Qt::AlignHCenter:
776 r.setLeft(2 * x - width + 1);
783 switch (flags & Qt::AlignVertical_Mask) {
784 case Qt::AlignBottom:
787 case Qt::AlignVCenter:
789 r.setTop(2 * y - height + 1);
797 painter->drawText(r, flags, text, &r);
799 syncCondition.wakeAll();
803 //--- DrawingThread ----------------------------------------------------
807 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
810 , started_once(false)
814 /*! Démarre le thread si ce n'a pas encore été fait.
816 void DrawingThread::start_once(Priority priority)
824 /*! La vraie fonction pour le thread.
826 void DrawingThread::run()
828 threadFunction(drawingWindow);