Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
.
[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 novembre 2007
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 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.
56  */
57
58 /*! \example hello.cpp
59  *
60  * Voir le code source à la fin de la page.  Pour compiler et exécuter
61  * ce programme, il faut :
62  *
63  * <b>1. Créer le fichier \c hello.pro</b>
64  *
65  * Pour simplifier, ce fichier contient la liste des fichiers sources
66  * composant le programme.
67  *
68  * \include hello.pro
69  *
70  * <b>2. Créer le fichier \c Makefile avec la commande :</b>
71  *
72  * \verbatim qmake-qt4 hello.pro \endverbatim
73  * ou tout simplement :
74  * \verbatim qmake-qt4 \endverbatim
75  *
76  * <b>3. Compiler le programme avec la commande :</b>
77  *
78  * \verbatim make hello \endverbatim
79  * ou tout simplement :
80  * \verbatim make \endverbatim
81  *
82  * <b>4. Exécuter le programme avec la commande :</b>
83  *
84  * \verbatim ./exemple \endverbatim
85  *
86  * <b>Code source de l'exemple</b>
87  */
88
89 /*! \example exemple.cpp
90  *
91  * Un exemple un peu plus sophistiqué.
92  */
93
94 //! Classe de thread.
95 class DrawingThread: public QThread {
96 public:
97     DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
98     void start_once(Priority priority = InheritPriority);
99
100 protected:
101     void run();
102
103 private:
104     DrawingWindow &drawingWindow;
105     DrawingWindow::ThreadFunction threadFunction;
106     bool started_once;
107
108     friend class DrawingWindow;
109 };
110
111 enum UserEvents {
112     SyncRequest = QEvent::User, //!< Demande de synchronisation.
113     CloseRequest,               //!< Demande de fermeture de la fenêtre.
114     DrawTextRequest,            //!< Demande d'écriture de texte.
115 };
116
117 //! Demande de synchronisation.
118 class SyncRequestEvent: public QEvent {
119 public:
120     SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
121     { }
122 };
123
124 //! Demande de fermeture de fenêtre.
125 class CloseRequestEvent: public QEvent {
126 public:
127     CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
128     { }
129 };
130
131 //! Demande de tracé de texte. 
132 class DrawTextEvent: public QEvent {
133 public:
134     const int x;
135     const int y;
136     const char *text;
137     const int flags;
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_)
141     { }
142 };
143
144 //--- DrawingWindow ----------------------------------------------------
145
146 /*! \file DrawingWindow.h
147  *  \brief Classe DrawingWindow.
148  */
149
150 /*! \typedef DrawingWindow::ThreadFunction
151  *  \brief Type de la fonction de dessin, passée en paramètre de construction.
152  */
153 /*! \var DrawingWindow::DEFAULT_WIDTH
154  *  \brief Largeur par défaut de la fenêtre.
155  */
156 /*! \var DrawingWindow::DEFAULT_HEIGHT
157  *  \brief Hauteur par défaut de la fenêtre.
158  */
159 /*! \var DrawingWindow::width
160  *  \brief Largeur de la fenêtre.
161  */
162 /*! \var DrawingWindow::height
163  *  \brief Hauteur de la fenêtre.
164  */
165 /*! \var DrawingWindow::paintInterval
166  *  \brief Intervalle de temps entre deux rendus (ms).
167  */
168
169 //! Constructeur.
170 /*!
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
173  * nouveau thread.
174  *
175  * \param fun           fonction de dessin
176  * \param width_        largeur de la fenêtre
177  * \param height_       hauteur de la fenêtre
178  *
179  * \see QWidget
180  */
181 DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
182     : QWidget()
183     , width(width_)
184     , height(height_)
185 {
186     initialize(fun);
187 }
188
189 //! Constructeur.
190 /*!
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
193  * thread.
194  *
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
199  *
200  * \see QWidget
201  */
202 DrawingWindow::DrawingWindow(QWidget *parent,
203                              ThreadFunction fun, int width_, int height_)
204     : QWidget(parent)
205     , width(width_)
206     , height(height_)
207 {
208     initialize(fun);
209 }
210
211 //! Constructeur.
212 /*!
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
215  * thread.
216  *
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
222  *
223  * \see QWidget
224  */
225 DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
226                              ThreadFunction fun, int width_, int height_)
227     : QWidget(parent, flags)
228     , width(width_)
229     , height(height_)
230 {
231     initialize(fun);
232 }
233
234 //! Destructeur.
235 DrawingWindow::~DrawingWindow()
236 {
237     delete thread;
238     delete painter;
239     delete image;
240 }
241
242 //! Change la couleur de dessin.
243 /*!
244  * La couleur est un entier, tel que retourné par getPointColor.
245  * Normalement de la forme #00RRGGBB.
246  *
247  * \param color         couleur
248  *
249  * \see setColor(const char *), setColor(float, float, float),
250  *      setBgColor(unsigned int),
251  *      getPointColor
252  */
253 void DrawingWindow::setColor(unsigned int color)
254 {
255     setColor(QColor::fromRgb(color));
256 }
257
258 //! Change la couleur de dessin.
259 /*!
260  * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
261  *
262  * \param name          nom de couleur
263  *
264  * \see setColor(unsigned int), setColor(float, float, float),
265  *      setBgColor(const char *)
266  * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
267  */
268 void DrawingWindow::setColor(const char *name)
269 {
270     setColor(QColor(name));
271 }
272
273 //! Change la couleur de dessin.
274 /*!
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.
278  *
279  * \param red           composante de rouge
280  * \param green         composante de vert
281  * \param blue          composante de bleu
282  *
283  * \see setColor(unsigned int), setColor(const char *),
284  *      setBgColor(float, float, float)
285  */
286 void DrawingWindow::setColor(float red, float green, float blue)
287 {
288     setColor(QColor::fromRgbF(red, green, blue));
289 }
290
291 //! Change la couleur de fond.
292 /*!
293  * \param color         couleur
294  *
295  * \see setBgColor(const char *), setBgColor(float, float, float),
296  *      setColor(unsigned int),
297  *      getPointColor,
298  *      clearGraph
299  */
300 void DrawingWindow::setBgColor(unsigned int color)
301 {
302     setBgColor(QColor::fromRgb(color));
303 }
304
305 //! Change la couleur de fond.
306 /*!
307  * \param name          nom de couleur
308  *
309  * \see setBgColor(unsigned int), setBgColor(float, float, float),
310  *      setColor(const char *),
311  *      clearGraph
312  * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
313  */
314 void DrawingWindow::setBgColor(const char *name)
315 {
316     setBgColor(QColor(name));
317 }
318
319 //! Change la couleur de fond.
320 /*!
321  * \param red           composante de rouge
322  * \param green         composante de vert
323  * \param blue          composante de bleu
324  *
325  * \see setBgColor(unsigned int), setBgColor(const char *),
326  *      setColor(float, float, float),
327  *      clearGraph
328  */
329 void DrawingWindow::setBgColor(float red, float green, float blue)
330 {
331     setBgColor(QColor::fromRgbF(red, green, blue));
332 }
333
334 //! Efface la fenêtre.
335 /*!
336  * La fenêtre est effacée avec la couleur de fond courante.
337  *
338  * \see setBgColor
339  */
340 void DrawingWindow::clearGraph()
341 {
342     safeLock(imageMutex);
343     painter->fillRect(image->rect(), getBgColor());
344     dirty();
345     safeUnlock(imageMutex);
346 }
347
348 //! Dessine un point.
349 /*!
350  * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
351  * dessin courante.
352  *
353  * \param x, y          coordonnées du point
354  *
355  * \see setColor
356  */
357 void DrawingWindow::drawPoint(int x, int y)
358 {
359     safeLock(imageMutex);
360     painter->drawPoint(x, y);
361     dirty(x, y);
362     safeUnlock(imageMutex);
363 }
364
365 //! Dessine un segment.
366 /*!
367  * Dessine un segement de droite entre les coordonnées (x1, y1) et
368  * (x2, y2), avec la couleur de dessin courante.
369  *
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
372  *
373  * \see setColor
374  */
375 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
376 {
377     safeLock(imageMutex);
378     painter->drawLine(x1, y1, x2, y2);
379     dirty(x1, y1, x2, y2);
380     safeUnlock(imageMutex);
381 }
382
383 //! Dessine un rectangle.
384 /*!
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.
388  *
389  * \param x1, y1        coordonnées d'un sommet du rectangle
390  * \param x2, y2        coordonnées du sommet opposé du rectangle
391  *
392  * \see fillRect, setColor
393  */
394 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
395 {
396     QRect r;
397     r.setCoords(x1, y1, x2 - 1, y2 - 1);
398     r = r.normalized();
399     safeLock(imageMutex);
400     painter->drawRect(r);
401     r.adjust(0, 0, 1, 1);
402     dirty(r);
403     safeUnlock(imageMutex);
404 }
405
406 //! Dessine un rectangle plein.
407 /*!
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.
411  *
412  * \param x1, y1        coordonnées d'un sommet du rectangle
413  * \param x2, y2        coordonnées du sommet opposé du rectangle
414  *
415  * \see drawRect, setColor
416  */
417 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
418 {
419     painter->setBrush(getColor());
420     drawRect(x1, y1, x2, y2);
421     painter->setBrush(Qt::NoBrush);
422 }
423
424 //! Dessine un cercle.
425 /*!
426  * Dessine un cercle de centre (x, y) et de rayon r.  Utilise la
427  * couleur de dessin courante.
428  *
429  * \param x, y          coordonnées du centre du cercle
430  * \param r             rayon du cercle
431  *
432  * \see fillCircle, setColor
433  */
434 void DrawingWindow::drawCircle(int x, int y, int r)
435 {
436     QRect rect;
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);
441     dirty(rect);
442     safeUnlock(imageMutex);
443 }
444
445 //! Dessine un disque.
446 /*!
447  * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
448  * Utilise la couleur de dessin courante.
449  *
450  * \param x, y          coordonnées du centre du disque
451  * \param r             rayon du disque
452  *
453  * \see drawCircle, setColor
454  */
455 void DrawingWindow::fillCircle(int x, int y, int r)
456 {
457     painter->setBrush(getColor());
458     drawCircle(x, y, r);
459     painter->setBrush(Qt::NoBrush);
460 }
461
462 //! Écrit du texte.
463 /*!
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.
470  *
471  * \param x, y          coordonnées du texte
472  * \param text          texte à écrire
473  * \param flags         paramètres d'alignement
474  *
475  * \see drawTextBg, setColor
476  * \see QPainter::drawText
477  */
478 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
479 {
480     safeLock(syncMutex);
481     if (!terminateThread) {
482         qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
483         syncCondition.wait(&syncMutex);
484     }
485     safeUnlock(syncMutex);
486 }
487
488 //! Écrit du texte sur fond coloré.
489 /*!
490  * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
491  * la couleur de fond courante.
492  *
493  * \param x, y          coordonnées du texte
494  * \param text          texte à écrire
495  * \param flags         paramètres d'alignement
496  *
497  * \see drawText, setColor, setColorBg
498  */
499 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
500 {
501     painter->setBackgroundMode(Qt::OpaqueMode);
502     drawText(x, y, text, flags);
503     painter->setBackgroundMode(Qt::TransparentMode);
504 }
505
506
507 //! Retourne la couleur d'un pixel.
508 /*!
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).
512  *
513  * \param x, y          coordonnées du pixel
514  * \return              couleur du pixel
515  *
516  * \see setColor(unsigned int), setBgColor(unsigned int)
517  */
518 unsigned int DrawingWindow::getPointColor(int x, int y)
519 {
520     return image->pixel(x, y);
521 }
522
523 //! Synchronise le contenu de la fenêtre.
524 /*!
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
529  * soit à jour.
530  *
531  * \param time          durée maximale de l'attente
532  * \return              true si la fenêtre a pu être synchronisée
533  */
534 bool DrawingWindow::sync(unsigned long time)
535 {
536     bool synced;
537     safeLock(syncMutex);
538     if (terminateThread) {
539         synced = false;
540     } else {
541         qApp->postEvent(this, new SyncRequestEvent());
542         synced = syncCondition.wait(&syncMutex, time);
543     }
544     safeUnlock(syncMutex);
545     return synced;
546 }
547
548 //! Ferme la fenêtre graphique.
549 void DrawingWindow::closeGraph()
550 {
551     qApp->postEvent(this, new CloseRequestEvent());
552 }
553
554 //! Suspend l'exécution pendant un certain temps.
555 /*!
556  * \param secs          temps d'attente en seconde
557  */
558 void DrawingWindow::sleep(unsigned long secs)
559 {
560     DrawingThread::sleep(secs);
561 }
562
563 //! Suspend l'exécution pendant un certain temps.
564 /*!
565  * \param msecs          temps d'attente en millisecondes
566  */
567 void DrawingWindow::msleep(unsigned long msecs)
568 {
569     DrawingThread::msleep(msecs);
570 }
571
572 //! Suspend l'exécution pendant un certain temps.
573 /*!
574  * \param usecs          temps d'attente en microsecondes
575  */
576 void DrawingWindow::usleep(unsigned long usecs)
577 {
578     DrawingThread::usleep(usecs);
579 }
580
581 /*!
582  * \see QWidget
583  */
584 void DrawingWindow::closeEvent(QCloseEvent *ev)
585 {
586     timer.stop();
587     thread->terminate();
588     syncMutex.lock();
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
594                                 // from sync()
595     syncCondition.wakeAll();
596     syncMutex.unlock();
597     QWidget::closeEvent(ev);
598     thread->wait();
599 }
600
601 /*!
602  * \see QWidget
603  */
604 void DrawingWindow::customEvent(QEvent *ev)
605 {
606     switch ((int )ev->type()) {
607     case SyncRequest:
608         realSync();
609         break;
610     case CloseRequest:
611         close();
612         break;
613     case DrawTextRequest:
614         DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
615         realDrawText(tev->x, tev->y, tev->text, tev->flags);
616         break;
617     }
618 }
619
620 /*!
621  * \see QWidget
622  */
623 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
624 {
625     bool accept = true;
626     switch (ev->key()) {
627     case Qt::Key_Escape:
628         close();
629         break;
630     default:
631         accept = false;
632         break;
633     }
634     if (accept)
635         ev->accept();
636 }
637
638 /*!
639  * \see QWidget
640  */
641 void DrawingWindow::paintEvent(QPaintEvent *ev)
642 {
643     QPainter widgetPainter(this);
644     imageMutex.lock();
645     QImage imageCopy(*image);
646     imageMutex.unlock();
647     QRect rect = ev->rect();
648     widgetPainter.drawImage(rect, imageCopy, rect);
649 }
650
651 /*!
652  * \see QWidget
653  */
654 void DrawingWindow::showEvent(QShowEvent *ev)
655 {
656     QWidget::showEvent(ev);
657     qApp->flush();
658     qApp->syncX();
659     timer.start(paintInterval, this);
660     thread->start_once(QThread::IdlePriority);
661 }
662
663 /*!
664  * \see QWidget
665  */
666 void DrawingWindow::timerEvent(QTimerEvent *ev)
667 {
668     if (ev->timerId() == timer.timerId()) {
669         mayUpdate();
670         timer.start(paintInterval, this);
671     } else {
672         QWidget::timerEvent(ev);
673     }
674 }
675
676 //--- DrawingWindow (private methods) ----------------------------------
677
678 //! Fonction d'initialisation.
679 /*!
680  * Fonction appelée par les différents constructeurs.
681  *
682  * \param fun           fonction de dessin
683  */
684 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
685 {
686     terminateThread = false;
687     lockCount = 0;
688     image = new QImage(width, height, QImage::Format_RGB32);
689     painter = new QPainter(image);
690     thread = new DrawingThread(*this, fun);
691
692     setFocusPolicy(Qt::StrongFocus);
693     setFixedSize(image->size());
694     setAttribute(Qt::WA_OpaquePaintEvent);
695     setFocus();
696
697     setColor("black");
698     setBgColor("white");
699     clearGraph();
700
701     dirtyFlag = false;
702 }
703
704 //! Change la couleur de dessin.
705 /*!
706  * \param color                 couleur
707  */
708 inline
709 void DrawingWindow::setColor(const QColor& color)
710 {
711     QPen pen(painter->pen());
712     pen.setColor(color);
713     painter->setPen(pen);
714 }
715
716 //! Change la couleur de fond.
717 /*!
718  * \param color                 couleur
719  */
720 inline
721 void DrawingWindow::setBgColor(const QColor& color)
722 {
723     painter->setBackground(color);
724 }
725
726 //! Retourne la couleur de dessin courante.
727 /*!
728  * \return              couleur de dessin courante
729  */
730 inline
731 QColor DrawingWindow::getColor()
732 {
733     return painter->pen().color();
734 }
735
736 //! Retourne la couleur de fond courante.
737 /*!
738  * \return              couleur de fond courante
739  */
740 inline
741 QColor DrawingWindow::getBgColor()
742 {
743     return painter->background().color();
744 }
745
746 //! Verrouille un mutex.
747 /*!
748  * S'assure que le thread courant ne peut pas être terminé s'il
749  * détient un mutex.  Pendant de safeUnlock.
750  *
751  * \param mutex         le mutex à verrouiller
752  *
753  * \see safeUnlock
754  */
755 inline
756 void DrawingWindow::safeLock(QMutex &mutex)
757 {
758     if (lockCount++ == 0)
759         thread->setTerminationEnabled(false);
760     mutex.lock();
761 }
762
763 //! Déverrouille un mutex.
764 /*!
765  * S'assure que le thread courant ne peut pas être terminé s'il
766  * détient un mutex.  Pendant de safeLock.
767  *
768  * \param mutex         le mutex à déverrouiller
769  *
770  * \see safeLock
771  */
772 inline
773 void DrawingWindow::safeUnlock(QMutex &mutex)
774 {
775     mutex.unlock();
776     if (--lockCount == 0)
777         thread->setTerminationEnabled(true);
778 }
779
780 //! Marque l'image entière comme non à jour.
781 inline
782 void DrawingWindow::dirty()
783 {
784     dirtyFlag = true;
785     dirtyRect = image->rect();
786 }
787
788 //! Marque un point de l'image comme non à jour.
789 /*!
790  * \param x, y          coordonnées du point
791  */
792 inline
793 void DrawingWindow::dirty(int x, int y)
794 {
795     dirty(QRect(x, y, 1, 1));
796 }
797
798 //! Marque une zone de l'image comme non à jour.
799 /*!
800  * La zone est définie par un rectangle dont les coordonnées de deux
801  * sommets oppposés sont données.
802  *
803  * \param x1, y1        coordonnées d'un sommet du rectangle
804  * \param x2, y2        coordonnées du sommet opposé du rectangle
805  */
806 inline
807 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
808 {
809     QRect r;
810     r.setCoords(x1, y1, x2, y2);
811     dirty(r.normalized());
812 }
813
814 //! Marque une zone de l'image comme non à jour.
815 /*!
816  * \param rect          rectangle délimitant la zone
817  */
818 void DrawingWindow::dirty(const QRect &rect)
819 {
820     if (dirtyFlag) {
821         dirtyRect |= rect;
822     } else {
823         dirtyFlag = true;
824         dirtyRect = rect;
825     }
826 }
827
828 //! Génère un update si besoin.
829 /*!
830  * Génère une demande de mise à jour de la fenêtre (appel à update)
831  * s'il y en a besoin.
832  */
833 void DrawingWindow::mayUpdate()
834 {
835     imageMutex.lock();
836     bool dirty = dirtyFlag;
837     QRect rect = dirtyRect;
838     dirtyFlag = false;
839     imageMutex.unlock();
840     if (dirty)
841         update(rect);
842 }
843
844 //! Fonction bas-niveau pour sync.
845 /*!
846  * Fonction de synchronisation dans le thread principal.
847  *
848  * \see sync, customEvent
849  */
850 void DrawingWindow::realSync()
851 {
852     mayUpdate();
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);
860     qApp->flush();
861     qApp->syncX();
862     syncMutex.lock();
863     syncCondition.wakeAll();
864     syncMutex.unlock();
865 }
866
867 //! Fonction bas-niveau pour drawText.
868 /*!
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.
871  *
872  * \param x, y, text, flags     cf. drawText
873  *
874  * \see drawText, customEvent
875  */
876 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
877 {
878     QRect r(image->rect());
879     switch (flags & Qt::AlignHorizontal_Mask) {
880     case Qt::AlignRight:
881         r.setRight(x);
882         break;
883     case Qt::AlignHCenter:
884         if (x < width / 2)
885             r.setLeft(2 * x - width + 1);
886         else
887             r.setRight(2 * x);
888         break;
889     default:
890         r.setLeft(x);
891     }
892     switch (flags & Qt::AlignVertical_Mask) {
893     case Qt::AlignBottom:
894         r.setBottom(y);
895         break;
896     case Qt::AlignVCenter:
897         if (y < height / 2)
898             r.setTop(2 * y - height + 1);
899         else
900             r.setBottom(2 * y);
901         break;
902     default:
903         r.setTop(y);
904     }
905     syncMutex.lock();
906     painter->drawText(r, flags, text, &r);
907     dirty(r);
908     syncCondition.wakeAll();
909     syncMutex.unlock();
910 }
911
912 //--- DrawingThread ----------------------------------------------------
913
914 //! Constructeur.
915 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
916     : drawingWindow(w)
917     , threadFunction(f)
918     , started_once(false)
919 {
920 }
921
922 //! Démarre le thread si ce n'a pas encore été fait.
923 void DrawingThread::start_once(Priority priority)
924 {
925     if (!started_once) {
926         started_once = true;
927         start(priority);
928     }
929 }
930
931 //! La vraie fonction pour le thread.
932 void DrawingThread::run()
933 {
934     threadFunction(drawingWindow);
935 }