Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
e5556c44e10cda0a4fa13968dd4aaa673c5616d0
[graphlib.git] / DrawingWindow.cpp
1 /*
2  * Copyright (c) 2007, Arnaud Giersch <arnaud.giersch@iut-bm.univ-fcomte.fr>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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
15  *    written permission.
16  *
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.
28  */
29
30 #include "DrawingWindow.h"
31 #include <QApplication>
32 #include <QPaintEvent>
33 #include <QThread>
34 #include <QTimerEvent>
35
36 /*! \class DrawingWindow
37  *  \brief Fenêtre de dessin.
38  *
39  * \author Arnaud Giersch <arnaud.giersch@iut-bm.univ-fcomte.fr>
40  * \date 2007-2010
41  *
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
46  * DrawingWindow.
47  *
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 quel programme Qt.
52  *
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 dans la fenêtre.
56  *
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.
61  *
62  * Un appui sur la touche &lt;Esc&gt; provoque la fermeture de la fenêtre.
63  * Comme pour la plupart des applications, il est également possible
64  * de fermer la fenêtre via le gestionnaire de fenêtres.
65  *
66  * Il est possible, dans une application, d'ouvrir plusieurs fenêtres,
67  * avec des fonctions de dessin éventuellement différentes.
68  * L'application se terminera normalement lorsque la dernière fenêtre
69  * sera fermée.
70  */
71
72 /*! \example hello.cpp
73  *
74  * Voir le code source à la fin de la page.  Pour compiler et exécuter
75  * ce programme, il faut :
76  *
77  * <b>1. Créer le fichier \c hello.pro</b>
78  *
79  * Pour simplifier, ce fichier contient la liste des fichiers sources
80  * composant le programme.
81  *
82  * \include hello.pro
83  *
84  * <b>2. Créer le fichier \c Makefile avec la commande :</b>
85  *
86  * \verbatim qmake-qt4 hello.pro \endverbatim
87  * ou tout simplement :
88  * \verbatim qmake-qt4 \endverbatim
89  *
90  * <b>3. Compiler le programme avec la commande :</b>
91  *
92  * \verbatim make hello \endverbatim
93  * ou tout simplement :
94  * \verbatim make \endverbatim
95  *
96  * <b>4. Exécuter le programme avec la commande :</b>
97  *
98  * \verbatim ./hello \endverbatim
99  *
100  * <b>Code source de l'exemple</b>
101  */
102
103 /*! \example exemple.cpp
104  *
105  * Un exemple un peu plus sophistiqué.
106  */
107
108 //! Classe de thread.
109 class DrawingThread: public QThread {
110 public:
111     DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
112     void start_once(Priority priority = InheritPriority);
113
114 protected:
115     void run();
116
117 private:
118     DrawingWindow &drawingWindow;
119     DrawingWindow::ThreadFunction threadFunction;
120     bool started_once;
121
122     friend class DrawingWindow;
123 };
124
125 enum UserEvents {
126     SyncRequest = QEvent::User, //!< Demande de synchronisation.
127     CloseRequest,               //!< Demande de fermeture de la fenêtre.
128     DrawTextRequest,            //!< Demande d'écriture de texte.
129 };
130
131 //! Demande de synchronisation.
132 class SyncRequestEvent: public QEvent {
133 public:
134     SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
135     { }
136 };
137
138 //! Demande de fermeture de fenêtre.
139 class CloseRequestEvent: public QEvent {
140 public:
141     CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
142     { }
143 };
144
145 //! Demande de tracé de texte. 
146 class DrawTextEvent: public QEvent {
147 public:
148     const int x;
149     const int y;
150     const char *text;
151     const int flags;
152     DrawTextEvent(int x_, int y_, const char* text_, int flags_)
153         : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
154         , x(x_), y(y_), text(text_), flags(flags_)
155     { }
156 };
157
158 //--- DrawingWindow ----------------------------------------------------
159
160 /*! \file DrawingWindow.h
161  *  \brief Classe DrawingWindow.
162  */
163
164 /*! \typedef DrawingWindow::ThreadFunction
165  *  \brief Type de la fonction de dessin, passée en paramètre de construction.
166  */
167 /*! \var DrawingWindow::DEFAULT_WIDTH
168  *  \brief Largeur par défaut de la fenêtre.
169  */
170 /*! \var DrawingWindow::DEFAULT_HEIGHT
171  *  \brief Hauteur par défaut de la fenêtre.
172  */
173 /*! \var DrawingWindow::width
174  *  \brief Largeur de la fenêtre.
175  */
176 /*! \var DrawingWindow::height
177  *  \brief Hauteur de la fenêtre.
178  */
179 /*! \var DrawingWindow::paintInterval
180  *  \brief Intervalle de temps entre deux rendus (ms).
181  */
182
183 //! Constructeur.
184 /*!
185  * Construit une nouvelle fenêtre de dessin avec les dimensions
186  * passées en paramètres.  La fonction fun sera exécutée dans un
187  * nouveau thread.
188  *
189  * \param fun           fonction de dessin
190  * \param width_        largeur de la fenêtre
191  * \param height_       hauteur de la fenêtre
192  *
193  * \see QWidget
194  */
195 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
196     : QWidget()
197     , width(width_)
198     , height(height_)
199 {
200     initialize(fun);
201 }
202
203 //! Constructeur.
204 /*!
205  * Construit un nouveau widget de dessin avec les dimensions passées
206  * en paramètres.  La fonction fun sera exécutée dans un nouveau
207  * thread.
208  *
209  * \param parent        widget parent
210  * \param fun           fonction de dessin
211  * \param width_        largeur de la fenêtre
212  * \param height_       hauteur de la fenêtre
213  *
214  * \see QWidget
215  */
216 DrawingWindow::DrawingWindow(QWidget *parent,
217                              ThreadFunction fun, int width_, int height_)
218     : QWidget(parent)
219     , width(width_)
220     , height(height_)
221 {
222     initialize(fun);
223 }
224
225 //! Constructeur.
226 /*!
227  * Construit un nouveau widget de dessin avec les dimensions passées
228  * en paramètres.  La fonction fun sera exécutée dans un nouveau
229  * thread.
230  *
231  * \param parent        widget parent
232  * \param flags         flags passés au constructeur de QWidget
233  * \param fun           fonction de dessin
234  * \param width_        largeur de la fenêtre
235  * \param height_       hauteur de la fenêtre
236  *
237  * \see QWidget
238  */
239 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
240                              ThreadFunction fun, int width_, int height_)
241     : QWidget(parent, flags)
242     , width(width_)
243     , height(height_)
244 {
245     initialize(fun);
246 }
247
248 //! Destructeur.
249 DrawingWindow::~DrawingWindow()
250 {
251     delete thread;
252     delete painter;
253     delete image;
254 }
255
256 //! Change la couleur de dessin.
257 /*!
258  * La couleur est un entier, tel que retourné par getPointColor.
259  * Normalement de la forme #00RRGGBB.
260  *
261  * \param color         couleur
262  *
263  * \see setColor(const char *), setColor(float, float, float),
264  *      setBgColor(unsigned int),
265  *      getPointColor
266  */
267 void DrawingWindow::setColor(unsigned int color)
268 {
269     setColor(QColor::fromRgb(color));
270 }
271
272 //! Change la couleur de dessin.
273 /*!
274  * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
275  *
276  * \param name          nom de couleur
277  *
278  * \see setColor(unsigned int), setColor(float, float, float),
279  *      setBgColor(const char *)
280  * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
281  */
282 void DrawingWindow::setColor(const char *name)
283 {
284     setColor(QColor(name));
285 }
286
287 //! Change la couleur de dessin.
288 /*!
289  * Les composantes de rouge, vert et bleu de la couleur doivent être
290  * compris entre 0 et 1.  Si le trois composantes sont à 0, on obtient
291  * du noir; si les trois composantes sont à 1, on obtient du blanc.
292  *
293  * \param red           composante de rouge
294  * \param green         composante de vert
295  * \param blue          composante de bleu
296  *
297  * \see setColor(unsigned int), setColor(const char *),
298  *      setBgColor(float, float, float)
299  */
300 void DrawingWindow::setColor(float red, float green, float blue)
301 {
302     setColor(QColor::fromRgbF(red, green, blue));
303 }
304
305 //! Change la couleur de fond.
306 /*!
307  * \param color         couleur
308  *
309  * \see setBgColor(const char *), setBgColor(float, float, float),
310  *      setColor(unsigned int),
311  *      getPointColor,
312  *      clearGraph
313  */
314 void DrawingWindow::setBgColor(unsigned int color)
315 {
316     setBgColor(QColor::fromRgb(color));
317 }
318
319 //! Change la couleur de fond.
320 /*!
321  * \param name          nom de couleur
322  *
323  * \see setBgColor(unsigned int), setBgColor(float, float, float),
324  *      setColor(const char *),
325  *      clearGraph
326  * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
327  */
328 void DrawingWindow::setBgColor(const char *name)
329 {
330     setBgColor(QColor(name));
331 }
332
333 //! Change la couleur de fond.
334 /*!
335  * \param red           composante de rouge
336  * \param green         composante de vert
337  * \param blue          composante de bleu
338  *
339  * \see setBgColor(unsigned int), setBgColor(const char *),
340  *      setColor(float, float, float),
341  *      clearGraph
342  */
343 void DrawingWindow::setBgColor(float red, float green, float blue)
344 {
345     setBgColor(QColor::fromRgbF(red, green, blue));
346 }
347
348 //! Efface la fenêtre.
349 /*!
350  * La fenêtre est effacée avec la couleur de fond courante.
351  *
352  * \see setBgColor
353  */
354 void DrawingWindow::clearGraph()
355 {
356     safeLock(imageMutex);
357     painter->fillRect(image->rect(), getBgColor());
358     dirty();
359     safeUnlock(imageMutex);
360 }
361
362 //! Dessine un point.
363 /*!
364  * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
365  * dessin courante.
366  *
367  * \param x, y          coordonnées du point
368  *
369  * \see setColor
370  */
371 void DrawingWindow::drawPoint(int x, int y)
372 {
373     safeLock(imageMutex);
374     painter->drawPoint(x, y);
375     dirty(x, y);
376     safeUnlock(imageMutex);
377 }
378
379 //! Dessine un segment.
380 /*!
381  * Dessine un segement de droite entre les coordonnées (x1, y1) et
382  * (x2, y2), avec la couleur de dessin courante.
383  *
384  * \param x1, y1        coordonnées d'une extrémité du segment
385  * \param x2, y2        coordonnées de l'autre extrémité du segment
386  *
387  * \see setColor
388  */
389 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
390 {
391     safeLock(imageMutex);
392     painter->drawLine(x1, y1, x2, y2);
393     dirty(x1, y1, x2, y2);
394     safeUnlock(imageMutex);
395 }
396
397 //! Dessine un rectangle.
398 /*!
399  * Dessine le rectangle parallèle aux axes et défini par les
400  * coordonnées de deux sommets opposés (x1, y1) et (x2, y2).  Utilise
401  * la couleur de dessin courante.
402  *
403  * \param x1, y1        coordonnées d'un sommet du rectangle
404  * \param x2, y2        coordonnées du sommet opposé du rectangle
405  *
406  * \see fillRect, setColor
407  */
408 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
409 {
410     QRect r;
411     r.setCoords(x1, y1, x2 - 1, y2 - 1);
412     r = r.normalized();
413     safeLock(imageMutex);
414     painter->drawRect(r);
415     r.adjust(0, 0, 1, 1);
416     dirty(r);
417     safeUnlock(imageMutex);
418 }
419
420 //! Dessine un rectangle plein.
421 /*!
422  * Dessine le rectangle plein parallèle aux axes et défini par les
423  * coordonnées de deux sommets opposés (x1, y1) et (x2, y2).  Utilise
424  * la couleur de dessin courante.
425  *
426  * \param x1, y1        coordonnées d'un sommet du rectangle
427  * \param x2, y2        coordonnées du sommet opposé du rectangle
428  *
429  * \see drawRect, setColor
430  */
431 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
432 {
433     painter->setBrush(getColor());
434     drawRect(x1, y1, x2, y2);
435     painter->setBrush(Qt::NoBrush);
436 }
437
438 //! Dessine un cercle.
439 /*!
440  * Dessine un cercle de centre (x, y) et de rayon r.  Utilise la
441  * couleur de dessin courante.
442  *
443  * \param x, y          coordonnées du centre du cercle
444  * \param r             rayon du cercle
445  *
446  * \see fillCircle, setColor
447  */
448 void DrawingWindow::drawCircle(int x, int y, int r)
449 {
450     QRect rect;
451     rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
452     safeLock(imageMutex);
453     painter->drawEllipse(rect);
454     rect.adjust(0, 0, 1, 1);
455     dirty(rect);
456     safeUnlock(imageMutex);
457 }
458
459 //! Dessine un disque.
460 /*!
461  * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
462  * Utilise la couleur de dessin courante.
463  *
464  * \param x, y          coordonnées du centre du disque
465  * \param r             rayon du disque
466  *
467  * \see drawCircle, setColor
468  */
469 void DrawingWindow::fillCircle(int x, int y, int r)
470 {
471     painter->setBrush(getColor());
472     drawCircle(x, y, r);
473     painter->setBrush(Qt::NoBrush);
474 }
475
476 //! Écrit du texte.
477 /*!
478  * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
479  * d'alignement flags.  Le texte est écrit avec la couleur de dessin
480  * courante.  Les flags sont une combinaison (ou binaire) de
481  * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
482  * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter.  Par défaut, le
483  * texte est aligné en haut à gauche.
484  *
485  * \param x, y          coordonnées du texte
486  * \param text          texte à écrire
487  * \param flags         paramètres d'alignement
488  *
489  * \see drawTextBg, setColor
490  * \see QPainter::drawText
491  */
492 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
493 {
494     safeLock(syncMutex);
495     if (!terminateThread) {
496         qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
497         syncCondition.wait(&syncMutex);
498     }
499     safeUnlock(syncMutex);
500 }
501
502 //! Écrit du texte sur fond coloré.
503 /*!
504  * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
505  * la couleur de fond courante.
506  *
507  * \param x, y          coordonnées du texte
508  * \param text          texte à écrire
509  * \param flags         paramètres d'alignement
510  *
511  * \see drawText, setColor, setColorBg
512  */
513 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
514 {
515     painter->setBackgroundMode(Qt::OpaqueMode);
516     drawText(x, y, text, flags);
517     painter->setBackgroundMode(Qt::TransparentMode);
518 }
519
520
521 //! Retourne la couleur d'un pixel.
522 /*!
523  * Retourne la couleur du pixel de coordonnées (x, y).  La valeur
524  * retournée peut servir de paramètres à setColor(unsigned int) ou
525  * setBgColor(unsigned int).
526  *
527  * \param x, y          coordonnées du pixel
528  * \return              couleur du pixel
529  *
530  * \see setColor(unsigned int), setBgColor(unsigned int)
531  */
532 unsigned int DrawingWindow::getPointColor(int x, int y)
533 {
534     return image->pixel(x, y);
535 }
536
537 //! Attend l'appui sur un des boutons de la souris.
538 /*!
539  * Attend l'appui sur un des boutons de la souris.  Retourne le bouton
540  * qui a été pressé et les coordonnées du pointeur de souris à ce
541  * moment-là.
542  *
543  * \param x, y          coordonnées du pointeur de souris
544  * \param button        numéro du bouton qui a été pressé
545  * \param time          durée maximale de l'attente
546  * \return              true si un bouton a été pressé
547  */
548 bool DrawingWindow::waitMousePress(int &x, int &y, int &button,
549                                    unsigned long time)
550 {
551     bool pressed;
552     safeLock(mouseMutex);
553     if (terminateThread) {
554         pressed = false;
555     } else {
556         pressed = mouseCondition.wait(&mouseMutex, time);
557         if (pressed) {
558             x = mousePos.x();
559             y = mousePos.y();
560             if (mouseButton & Qt::LeftButton)
561                 button = 1;
562             else if (mouseButton & Qt::RightButton)
563                 button = 2;
564             else if (mouseButton & Qt::MidButton)
565                 button = 3;
566             else
567                 button = 0;
568         }
569     }
570     safeUnlock(mouseMutex);
571     return pressed;
572 }
573
574 //! Synchronise le contenu de la fenêtre.
575 /*!
576  * Pour des raisons d'efficacités, le résultat des fonctions de dessin
577  * n'est pas affiché immédiatement.  L'appel à sync permet de
578  * synchroniser le contenu de la fenêtre.  Autrement dit, cela bloque
579  * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
580  * soit à jour.
581  *
582  * \param time          durée maximale de l'attente
583  * \return              true si la fenêtre a pu être synchronisée
584  */
585 bool DrawingWindow::sync(unsigned long time)
586 {
587     bool synced;
588     safeLock(syncMutex);
589     if (terminateThread) {
590         synced = false;
591     } else {
592         qApp->postEvent(this, new SyncRequestEvent());
593         synced = syncCondition.wait(&syncMutex, time);
594     }
595     safeUnlock(syncMutex);
596     return synced;
597 }
598
599 //! Ferme la fenêtre graphique.
600 void DrawingWindow::closeGraph()
601 {
602     qApp->postEvent(this, new CloseRequestEvent());
603 }
604
605 //! Suspend l'exécution pendant un certain temps.
606 /*!
607  * \param secs          temps d'attente en seconde
608  */
609 void DrawingWindow::sleep(unsigned long secs)
610 {
611     DrawingThread::sleep(secs);
612 }
613
614 //! Suspend l'exécution pendant un certain temps.
615 /*!
616  * \param msecs          temps d'attente en millisecondes
617  */
618 void DrawingWindow::msleep(unsigned long msecs)
619 {
620     DrawingThread::msleep(msecs);
621 }
622
623 //! Suspend l'exécution pendant un certain temps.
624 /*!
625  * \param usecs          temps d'attente en microsecondes
626  */
627 void DrawingWindow::usleep(unsigned long usecs)
628 {
629     DrawingThread::usleep(usecs);
630 }
631
632 /*!
633  * \see QWidget
634  */
635 void DrawingWindow::closeEvent(QCloseEvent *ev)
636 {
637     timer.stop();
638     thread->terminate();
639     syncMutex.lock();
640     mouseMutex.lock();
641     terminateThread = true;     // this flag is needed for the case
642                                 // where the following wakeAll() call
643                                 // occurs between the
644                                 // setTerminationEnable(false) and the
645                                 // mutex lock in safeLock() called
646                                 // from sync()
647     syncCondition.wakeAll();
648     mouseCondition.wakeAll();
649     mouseMutex.unlock();
650     syncMutex.unlock();
651     QWidget::closeEvent(ev);
652     thread->wait();
653 }
654
655 /*!
656  * \see QWidget
657  */
658 void DrawingWindow::customEvent(QEvent *ev)
659 {
660     switch ((int )ev->type()) {
661     case SyncRequest:
662         realSync();
663         break;
664     case CloseRequest:
665         close();
666         break;
667     case DrawTextRequest:
668         DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
669         realDrawText(tev->x, tev->y, tev->text, tev->flags);
670         break;
671     }
672 }
673
674 /*!
675  * \see QWidget
676  */
677 void DrawingWindow::mousePressEvent(QMouseEvent *ev)
678 {
679     mouseMutex.lock();
680     mousePos = ev->pos();
681     mouseButton = ev->button();
682     ev->accept();
683     mouseCondition.wakeAll();
684     mouseMutex.unlock();
685 }
686
687 /*!
688  * \see QWidget
689  */
690 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
691 {
692     bool accept = true;
693     switch (ev->key()) {
694     case Qt::Key_Escape:
695         close();
696         break;
697     default:
698         accept = false;
699         break;
700     }
701     if (accept)
702         ev->accept();
703 }
704
705 /*!
706  * \see QWidget
707  */
708 void DrawingWindow::paintEvent(QPaintEvent *ev)
709 {
710     QPainter widgetPainter(this);
711     imageMutex.lock();
712     QImage imageCopy(*image);
713     imageMutex.unlock();
714     QRect rect = ev->rect();
715     widgetPainter.drawImage(rect, imageCopy, rect);
716 }
717
718 /*!
719  * \see QWidget
720  */
721 void DrawingWindow::showEvent(QShowEvent *ev)
722 {
723     QWidget::showEvent(ev);
724     qApp->flush();
725     qApp->syncX();
726     timer.start(paintInterval, this);
727     thread->start_once(QThread::IdlePriority);
728 }
729
730 /*!
731  * \see QWidget
732  */
733 void DrawingWindow::timerEvent(QTimerEvent *ev)
734 {
735     if (ev->timerId() == timer.timerId()) {
736         mayUpdate();
737         timer.start(paintInterval, this);
738     } else {
739         QWidget::timerEvent(ev);
740     }
741 }
742
743 //--- DrawingWindow (private methods) ----------------------------------
744
745 //! Fonction d'initialisation.
746 /*!
747  * Fonction appelée par les différents constructeurs.
748  *
749  * \param fun           fonction de dessin
750  */
751 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
752 {
753     terminateThread = false;
754     lockCount = 0;
755     image = new QImage(width, height, QImage::Format_RGB32);
756     painter = new QPainter(image);
757     thread = new DrawingThread(*this, fun);
758
759     setFocusPolicy(Qt::StrongFocus);
760     setFixedSize(image->size());
761     setAttribute(Qt::WA_OpaquePaintEvent);
762     setFocus();
763
764     setColor("black");
765     setBgColor("white");
766     clearGraph();
767
768     dirtyFlag = false;
769 }
770
771 //! Change la couleur de dessin.
772 /*!
773  * \param color                 couleur
774  */
775 inline
776 void DrawingWindow::setColor(const QColor& color)
777 {
778     QPen pen(painter->pen());
779     pen.setColor(color);
780     painter->setPen(pen);
781 }
782
783 //! Change la couleur de fond.
784 /*!
785  * \param color                 couleur
786  */
787 inline
788 void DrawingWindow::setBgColor(const QColor& color)
789 {
790     painter->setBackground(color);
791 }
792
793 //! Retourne la couleur de dessin courante.
794 /*!
795  * \return              couleur de dessin courante
796  */
797 inline
798 QColor DrawingWindow::getColor()
799 {
800     return painter->pen().color();
801 }
802
803 //! Retourne la couleur de fond courante.
804 /*!
805  * \return              couleur de fond courante
806  */
807 inline
808 QColor DrawingWindow::getBgColor()
809 {
810     return painter->background().color();
811 }
812
813 //! Verrouille un mutex.
814 /*!
815  * S'assure que le thread courant ne peut pas être terminé s'il
816  * détient un mutex.  Pendant de safeUnlock.
817  *
818  * \param mutex         le mutex à verrouiller
819  *
820  * \see safeUnlock
821  */
822 inline
823 void DrawingWindow::safeLock(QMutex &mutex)
824 {
825     if (lockCount++ == 0)
826         thread->setTerminationEnabled(false);
827     mutex.lock();
828 }
829
830 //! Déverrouille un mutex.
831 /*!
832  * S'assure que le thread courant ne peut pas être terminé s'il
833  * détient un mutex.  Pendant de safeLock.
834  *
835  * \param mutex         le mutex à déverrouiller
836  *
837  * \see safeLock
838  */
839 inline
840 void DrawingWindow::safeUnlock(QMutex &mutex)
841 {
842     mutex.unlock();
843     if (--lockCount == 0)
844         thread->setTerminationEnabled(true);
845 }
846
847 //! Marque l'image entière comme non à jour.
848 inline
849 void DrawingWindow::dirty()
850 {
851     dirtyFlag = true;
852     dirtyRect = image->rect();
853 }
854
855 //! Marque un point de l'image comme non à jour.
856 /*!
857  * \param x, y          coordonnées du point
858  */
859 inline
860 void DrawingWindow::dirty(int x, int y)
861 {
862     dirty(QRect(x, y, 1, 1));
863 }
864
865 //! Marque une zone de l'image comme non à jour.
866 /*!
867  * La zone est définie par un rectangle dont les coordonnées de deux
868  * sommets oppposés sont données.
869  *
870  * \param x1, y1        coordonnées d'un sommet du rectangle
871  * \param x2, y2        coordonnées du sommet opposé du rectangle
872  */
873 inline
874 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
875 {
876     QRect r;
877     r.setCoords(x1, y1, x2, y2);
878     dirty(r.normalized());
879 }
880
881 //! Marque une zone de l'image comme non à jour.
882 /*!
883  * \param rect          rectangle délimitant la zone
884  */
885 void DrawingWindow::dirty(const QRect &rect)
886 {
887     if (dirtyFlag) {
888         dirtyRect |= rect;
889     } else {
890         dirtyFlag = true;
891         dirtyRect = rect;
892     }
893 }
894
895 //! Génère un update si besoin.
896 /*!
897  * Génère une demande de mise à jour de la fenêtre (appel à update)
898  * s'il y en a besoin.
899  */
900 void DrawingWindow::mayUpdate()
901 {
902     imageMutex.lock();
903     bool dirty = dirtyFlag;
904     QRect rect = dirtyRect;
905     dirtyFlag = false;
906     imageMutex.unlock();
907     if (dirty)
908         update(rect);
909 }
910
911 //! Fonction bas-niveau pour sync.
912 /*!
913  * Fonction de synchronisation dans le thread principal.
914  *
915  * \see sync, customEvent
916  */
917 void DrawingWindow::realSync()
918 {
919     mayUpdate();
920     qApp->sendPostedEvents(this, QEvent::UpdateLater);
921     qApp->sendPostedEvents(this, QEvent::UpdateRequest);
922     qApp->sendPostedEvents(this, QEvent::Paint);
923     qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
924                         QEventLoop::ExcludeSocketNotifiers |
925                         QEventLoop::DeferredDeletion |
926                         QEventLoop::X11ExcludeTimers);
927     qApp->flush();
928     qApp->syncX();
929     syncMutex.lock();
930     syncCondition.wakeAll();
931     syncMutex.unlock();
932 }
933
934 //! Fonction bas-niveau pour drawText.
935 /*!
936  * Le rendu de texte doit être fait dans le thread principal.  D'où
937  * les manipulations tordues et la synchronisation qui s'en suit.
938  *
939  * \param x, y, text, flags     cf. drawText
940  *
941  * \see drawText, customEvent
942  */
943 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
944 {
945     QRect r(image->rect());
946     switch (flags & Qt::AlignHorizontal_Mask) {
947     case Qt::AlignRight:
948         r.setRight(x);
949         break;
950     case Qt::AlignHCenter:
951         if (x < width / 2)
952             r.setLeft(2 * x - width + 1);
953         else
954             r.setRight(2 * x);
955         break;
956     default:
957         r.setLeft(x);
958     }
959     switch (flags & Qt::AlignVertical_Mask) {
960     case Qt::AlignBottom:
961         r.setBottom(y);
962         break;
963     case Qt::AlignVCenter:
964         if (y < height / 2)
965             r.setTop(2 * y - height + 1);
966         else
967             r.setBottom(2 * y);
968         break;
969     default:
970         r.setTop(y);
971     }
972     syncMutex.lock();
973     painter->drawText(r, flags, text, &r);
974     dirty(r);
975     syncCondition.wakeAll();
976     syncMutex.unlock();
977 }
978
979 //--- DrawingThread ----------------------------------------------------
980
981 //! Constructeur.
982 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
983     : drawingWindow(w)
984     , threadFunction(f)
985     , started_once(false)
986 {
987 }
988
989 //! Démarre le thread si ce n'a pas encore été fait.
990 void DrawingThread::start_once(Priority priority)
991 {
992     if (!started_once) {
993         started_once = true;
994         start(priority);
995     }
996 }
997
998 //! La vraie fonction pour le thread.
999 void DrawingThread::run()
1000 {
1001     threadFunction(drawingWindow);
1002 }