Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
3234441cefa4dc735f5d611b682041e15d1948d7
[graphlib.git] / DrawingWindow.cpp
1 /*
2  * Copyright (c) 2007-2013, 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-2013
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 //! Change l'épaisseur du pinceau
349 /*!
350  * Le pinceau à une épaisseur de 1 par défaut.
351  *
352  * \param width         épaisseur du pinceau
353  */
354 void DrawingWindow::setPenWidth(int width)
355 {
356     QPen pen(painter->pen());
357     pen.setWidth(width);
358     painter->setPen(pen);
359 }
360
361 //! Retourne la fonte courante utilisée pour dessiner du texte.
362 /*!
363  * \see QFont, setFont
364  */
365 const QFont &DrawingWindow::getFont() const
366 {
367     return painter->font();
368 }
369
370 //! Applique une nouvelle font pour dessiner du texte.
371 /*!
372  * \see QFont, getFont
373  */
374 void DrawingWindow::setFont(const QFont &font)
375 {
376     painter->setFont(font);
377 }
378
379 //! Active ou non l'antialiasing.
380 /*!
381  * Permet de lisser le dessin.
382  * Fonctionnalité désactivée par défaut.
383  *
384  * \param state         état de l'antialiasing
385  *
386  * \bug                 expérimental
387  */
388 void DrawingWindow::setAntialiasing(bool state)
389 {
390     painter->setRenderHint(QPainter::Antialiasing, state);
391 }
392
393 //! Efface la fenêtre.
394 /*!
395  * La fenêtre est effacée avec la couleur de fond courante.
396  *
397  * \see setBgColor
398  */
399 void DrawingWindow::clearGraph()
400 {
401     safeLock(imageMutex);
402     painter->fillRect(image->rect(), getBgColor());
403     dirty();
404     safeUnlock(imageMutex);
405 }
406
407 //! Dessine un point.
408 /*!
409  * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
410  * dessin courante.
411  *
412  * \param x, y          coordonnées du point
413  *
414  * \see setColor
415  */
416 void DrawingWindow::drawPoint(int x, int y)
417 {
418     safeLock(imageMutex);
419     painter->drawPoint(x, y);
420     dirty(x, y);
421     safeUnlock(imageMutex);
422 }
423
424 //! Dessine un segment.
425 /*!
426  * Dessine un segement de droite entre les coordonnées (x1, y1) et
427  * (x2, y2), avec la couleur de dessin courante.
428  *
429  * \param x1, y1        coordonnées d'une extrémité du segment
430  * \param x2, y2        coordonnées de l'autre extrémité du segment
431  *
432  * \see setColor
433  */
434 void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
435 {
436     if (x1 == x2 && y1 == y2) {
437         drawPoint(x1, y1);
438     } else {
439         safeLock(imageMutex);
440         painter->drawLine(x1, y1, x2, y2);
441         dirty(x1, y1, x2, y2);
442         safeUnlock(imageMutex);
443     }
444 }
445
446 //! Dessine un rectangle.
447 /*!
448  * Dessine le rectangle parallèle aux axes et défini par les
449  * coordonnées de deux sommets opposés (x1, y1) et (x2, y2).  Utilise
450  * la couleur de dessin courante.
451  *
452  * \param x1, y1        coordonnées d'un sommet du rectangle
453  * \param x2, y2        coordonnées du sommet opposé du rectangle
454  *
455  * \see fillRect, setColor
456  */
457 void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
458 {
459     if (x1 == x2 && y1 == y2) {
460         drawPoint(x1, y1);
461     } else {
462         QRect r;
463         r.setCoords(x1, y1, x2 - 1, y2 - 1);
464         r = r.normalized();
465         safeLock(imageMutex);
466         painter->drawRect(r);
467         r.adjust(0, 0, 1, 1);
468         dirty(r);
469         safeUnlock(imageMutex);
470     }
471 }
472
473 //! Dessine un rectangle plein.
474 /*!
475  * Dessine le rectangle plein parallèle aux axes et défini par les
476  * coordonnées de deux sommets opposés (x1, y1) et (x2, y2).  Utilise
477  * la couleur de dessin courante.
478  *
479  * \param x1, y1        coordonnées d'un sommet du rectangle
480  * \param x2, y2        coordonnées du sommet opposé du rectangle
481  *
482  * \see drawRect, setColor
483  */
484 void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
485 {
486     painter->setBrush(getColor());
487     drawRect(x1, y1, x2, y2);
488     painter->setBrush(Qt::NoBrush);
489 }
490
491 //! Dessine un cercle.
492 /*!
493  * Dessine un cercle de centre (x, y) et de rayon r.  Utilise la
494  * couleur de dessin courante.
495  *
496  * \param x, y          coordonnées du centre du cercle
497  * \param r             rayon du cercle
498  *
499  * \see fillCircle, setColor
500  */
501 void DrawingWindow::drawCircle(int x, int y, int r)
502 {
503     QRect rect;
504     rect.setCoords(x - r, y - r, x + r - 1, y + r - 1);
505     safeLock(imageMutex);
506     painter->drawEllipse(rect);
507     rect.adjust(0, 0, 1, 1);
508     dirty(rect);
509     safeUnlock(imageMutex);
510 }
511
512 //! Dessine un disque.
513 /*!
514  * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
515  * Utilise la couleur de dessin courante.
516  *
517  * \param x, y          coordonnées du centre du disque
518  * \param r             rayon du disque
519  *
520  * \see drawCircle, setColor
521  */
522 void DrawingWindow::fillCircle(int x, int y, int r)
523 {
524     painter->setBrush(getColor());
525     drawCircle(x, y, r);
526     painter->setBrush(Qt::NoBrush);
527 }
528
529 //! Dessine un triangle.
530 /*!
531  * Dessine un triangle défini par les coordonnées de ses sommets:
532  * (x1, y1), (x2, y2) et (x3, y3).  Utilise la couleur de dessin
533  * courante.
534  *
535  * \param x1, y1        coordonnées du premier sommet du triangle
536  * \param x2, y2        coordonnées du deuxième sommet du triangle
537  * \param x3, y3        coordonnées du troisième sommet du triangle
538  *
539  * \see fillTriangle, setColor
540  */
541 void DrawingWindow::drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
542 {
543     QPolygon poly(3);
544     poly.putPoints(0, 3, x1, y1, x2, y2, x3, y3);
545     safeLock(imageMutex);
546     painter->drawConvexPolygon(poly);
547     dirty(poly.boundingRect());
548     safeUnlock(imageMutex);
549 }
550
551 //! Dessine un triangle plein.
552 /*!
553  * Dessine un triangle plein défini par les coordonnées de ses
554  * sommets: (x1, y1), (x2, y2) et (x3, y3).  Utilise la couleur de
555  * dessin courante.
556  *
557  * \param x1, y1        coordonnées du premier sommet du triangle
558  * \param x2, y2        coordonnées du deuxième sommet du triangle
559  * \param x3, y3        coordonnées du troisième sommet du triangle
560  *
561  * \see drawTriangle, setColor
562  */
563 void DrawingWindow::fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3)
564 {
565     painter->setBrush(getColor());
566     drawTriangle(x1, y1, x2, y2, x3, y3);
567     painter->setBrush(Qt::NoBrush);
568 }
569
570 //! Écrit du texte.
571 /*!
572  * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
573  * d'alignement flags.  Le texte est écrit avec la couleur de dessin
574  * courante.  Les flags sont une combinaison (ou binaire) de
575  * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
576  * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter.  Par défaut, le
577  * texte est aligné en haut à gauche.
578  *
579  * \param x, y          coordonnées du texte
580  * \param text          texte à écrire
581  * \param flags         paramètres d'alignement
582  *
583  * \see drawText(int, int, const std::string &, int)
584  * \see drawTextBg, setColor
585  * \see QPainter::drawText
586  */
587 void DrawingWindow::drawText(int x, int y, const char *text, int flags)
588 {
589     safeLock(syncMutex);
590     if (!terminateThread) {
591         qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
592         syncCondition.wait(&syncMutex);
593     }
594     safeUnlock(syncMutex);
595 }
596
597 //! Écrit du texte.
598 /*!
599  * \see drawText(int, int, const char *, int)
600  */
601 void DrawingWindow::drawText(int x, int y, const std::string &text, int flags)
602 {
603     drawText(x, y, text.c_str(), flags);
604 }
605
606 //! Écrit du texte sur fond coloré.
607 /*!
608  * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
609  * la couleur de fond courante.
610  *
611  * \param x, y          coordonnées du texte
612  * \param text          texte à écrire
613  * \param flags         paramètres d'alignement
614  *
615  * \see drawTextBg(int, int, const std::string &, int)
616  * \see drawText, setColor, setColorBg
617  */
618 void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
619 {
620     painter->setBackgroundMode(Qt::OpaqueMode);
621     drawText(x, y, text, flags);
622     painter->setBackgroundMode(Qt::TransparentMode);
623 }
624
625 //! Écrit du texte sur fond coloré.
626 /*!
627  * \see drawTextBg(int, int, const char *, int)
628  */
629 void DrawingWindow::drawTextBg(int x, int y, const std::string &text, int flags)
630 {
631     drawTextBg(x, y, text.c_str(), flags);
632 }
633
634 //! Retourne la couleur d'un pixel.
635 /*!
636  * Retourne la couleur du pixel de coordonnées (x, y).  La valeur
637  * retournée peut servir de paramètres à setColor(unsigned int) ou
638  * setBgColor(unsigned int).
639  *
640  * \param x, y          coordonnées du pixel
641  * \return              couleur du pixel
642  *
643  * \see setColor(unsigned int), setBgColor(unsigned int)
644  */
645 unsigned int DrawingWindow::getPointColor(int x, int y) const
646 {
647     return image->pixel(x, y);
648 }
649
650 //! Attend l'appui sur un des boutons de la souris.
651 /*!
652  * Attend l'appui sur un des boutons de la souris.  Retourne le bouton
653  * qui a été pressé et les coordonnées du pointeur de souris à ce
654  * moment-là.
655  *
656  * \param x, y          coordonnées du pointeur de souris
657  * \param button        numéro du bouton qui a été pressé
658  *                      (1: gauche, 2: droit, 3: milieu, 0 sinon)
659  * \param time          durée maximale de l'attente
660  * \return              true si un bouton a été pressé
661  *
662  * \bug                 expérimental
663  */
664 bool DrawingWindow::waitMousePress(int &x, int &y, int &button,
665                                    unsigned long time)
666 {
667     bool pressed;
668     safeLock(inputMutex);
669     if (terminateThread) {
670         pressed = false;
671     } else {
672         pressed = inputCondition.wait(&inputMutex, time) && !terminateThread;
673         if (pressed) {
674             x = mousePos.x();
675             y = mousePos.y();
676             if (mouseButton & Qt::LeftButton)
677                 button = 1;
678             else if (mouseButton & Qt::RightButton)
679                 button = 2;
680             else if (mouseButton & Qt::MidButton)
681                 button = 3;
682             else
683                 button = 0;
684         }
685     }
686     safeUnlock(inputMutex);
687     return pressed;
688 }
689
690 //! Synchronise le contenu de la fenêtre.
691 /*!
692  * Pour des raisons d'efficacités, le résultat des fonctions de dessin
693  * n'est pas affiché immédiatement.  L'appel à sync permet de
694  * synchroniser le contenu de la fenêtre.  Autrement dit, cela bloque
695  * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
696  * soit à jour.
697  *
698  * \param time          durée maximale de l'attente
699  * \return              true si la fenêtre a pu être synchronisée
700  */
701 bool DrawingWindow::sync(unsigned long time)
702 {
703     bool synced;
704     safeLock(syncMutex);
705     if (terminateThread) {
706         synced = false;
707     } else {
708         qApp->postEvent(this, new SyncRequestEvent());
709         synced = syncCondition.wait(&syncMutex, time);
710     }
711     safeUnlock(syncMutex);
712     return synced;
713 }
714
715 //! Ferme la fenêtre graphique.
716 void DrawingWindow::closeGraph()
717 {
718     qApp->postEvent(this, new CloseRequestEvent());
719 }
720
721 //! Suspend l'exécution pendant un certain temps.
722 /*!
723  * \param secs          temps d'attente en seconde
724  */
725 void DrawingWindow::sleep(unsigned long secs)
726 {
727     DrawingThread::sleep(secs);
728 }
729
730 //! Suspend l'exécution pendant un certain temps.
731 /*!
732  * \param msecs          temps d'attente en millisecondes
733  */
734 void DrawingWindow::msleep(unsigned long msecs)
735 {
736     DrawingThread::msleep(msecs);
737 }
738
739 //! Suspend l'exécution pendant un certain temps.
740 /*!
741  * \param usecs          temps d'attente en microsecondes
742  */
743 void DrawingWindow::usleep(unsigned long usecs)
744 {
745     DrawingThread::usleep(usecs);
746 }
747
748 //--- DrawingWindow (protected methods) --------------------------------
749 //! \cond show_protected
750
751 /*!
752  * \see QWidget
753  */
754 void DrawingWindow::closeEvent(QCloseEvent *ev)
755 {
756     timer.stop();
757     thread->exit();
758     syncMutex.lock();
759     inputMutex.lock();
760     terminateThread = true;     // this flag is needed for the case
761                                 // where the following wakeAll() call
762                                 // occurs between the
763                                 // setTerminationEnable(false) and the
764                                 // mutex lock in safeLock() called
765                                 // from sync()
766     syncCondition.wakeAll();
767     inputCondition.wakeAll();
768     inputMutex.unlock();
769     syncMutex.unlock();
770     QWidget::closeEvent(ev);
771     if (!thread->wait(250)) {
772         thread->terminate();
773         thread->wait();
774     }
775 }
776
777 /*!
778  * \see QWidget
779  */
780 void DrawingWindow::customEvent(QEvent *ev)
781 {
782     switch ((int )ev->type()) {
783     case SyncRequest:
784         realSync();
785         break;
786     case CloseRequest:
787         close();
788         break;
789     case DrawTextRequest:
790         DrawTextEvent *tev = dynamic_cast<DrawTextEvent *>(ev);
791         realDrawText(tev->x, tev->y, tev->text, tev->flags);
792         break;
793     }
794 }
795
796 /*!
797  * \see QWidget
798  *
799  * \bug                 expérimental
800  */
801 void DrawingWindow::mousePressEvent(QMouseEvent *ev)
802 {
803     inputMutex.lock();
804     mousePos = ev->pos();
805     mouseButton = ev->button();
806     ev->accept();
807     inputCondition.wakeAll();
808     inputMutex.unlock();
809 }
810
811 /*!
812  * \see QWidget
813  */
814 void DrawingWindow::keyPressEvent(QKeyEvent *ev)
815 {
816     if (ev->key() == Qt::Key_Escape) {
817         ev->accept();
818         close();
819     }
820 }
821
822 /*!
823  * \see QWidget
824  */
825 void DrawingWindow::paintEvent(QPaintEvent *ev)
826 {
827     QPainter widgetPainter(this);
828     imageMutex.lock();
829     QImage imageCopy(*image);
830     imageMutex.unlock();
831     QRect rect = ev->rect();
832     widgetPainter.drawImage(rect, imageCopy, rect);
833 }
834
835 /*!
836  * \see QWidget
837  */
838 void DrawingWindow::showEvent(QShowEvent *ev)
839 {
840     QWidget::showEvent(ev);
841     qApp->flush();
842     qApp->syncX();
843     timer.start(paintInterval, this);
844     thread->start_once(QThread::IdlePriority);
845 }
846
847 /*!
848  * \see QWidget
849  */
850 void DrawingWindow::timerEvent(QTimerEvent *ev)
851 {
852     if (ev->timerId() == timer.timerId()) {
853         mayUpdate();
854         timer.start(paintInterval, this);
855     } else {
856         QWidget::timerEvent(ev);
857     }
858 }
859
860 // \endcond
861
862 //--- DrawingWindow (private methods) ----------------------------------
863
864 //! Fonction d'initialisation.
865 /*!
866  * Fonction appelée par les différents constructeurs.
867  *
868  * \param fun           fonction de dessin
869  */
870 void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
871 {
872     terminateThread = false;
873     lockCount = 0;
874     image = new QImage(width, height, QImage::Format_RGB32);
875     painter = new QPainter(image);
876     thread = new DrawingThread(*this, fun);
877
878     setFocusPolicy(Qt::StrongFocus);
879     setFixedSize(image->size());
880     setAttribute(Qt::WA_OpaquePaintEvent);
881     setFocus();
882
883     setColor("black");
884     setBgColor("white");
885     clearGraph();
886
887     dirtyFlag = false;
888 }
889
890 //! Change la couleur de dessin.
891 /*!
892  * \param color                 couleur
893  */
894 inline
895 void DrawingWindow::setColor(const QColor &color)
896 {
897     QPen pen(painter->pen());
898     pen.setColor(color);
899     painter->setPen(pen);
900 }
901
902 //! Change la couleur de fond.
903 /*!
904  * \param color                 couleur
905  */
906 inline
907 void DrawingWindow::setBgColor(const QColor &color)
908 {
909     painter->setBackground(color);
910 }
911
912 //! Retourne la couleur de dessin courante.
913 /*!
914  * \return              couleur de dessin courante
915  */
916 inline
917 QColor DrawingWindow::getColor()
918 {
919     return painter->pen().color();
920 }
921
922 //! Retourne la couleur de fond courante.
923 /*!
924  * \return              couleur de fond courante
925  */
926 inline
927 QColor DrawingWindow::getBgColor()
928 {
929     return painter->background().color();
930 }
931
932 //! Verrouille un mutex.
933 /*!
934  * S'assure que le thread courant ne peut pas être terminé s'il
935  * détient un mutex.  Pendant de safeUnlock.
936  *
937  * \param mutex         le mutex à verrouiller
938  *
939  * \see safeUnlock
940  */
941 inline
942 void DrawingWindow::safeLock(QMutex &mutex)
943 {
944     if (lockCount++ == 0)
945         thread->setTerminationEnabled(false);
946     mutex.lock();
947 }
948
949 //! Déverrouille un mutex.
950 /*!
951  * S'assure que le thread courant ne peut pas être terminé s'il
952  * détient un mutex.  Pendant de safeLock.
953  *
954  * \param mutex         le mutex à déverrouiller
955  *
956  * \see safeLock
957  */
958 inline
959 void DrawingWindow::safeUnlock(QMutex &mutex)
960 {
961     mutex.unlock();
962     if (--lockCount == 0)
963         thread->setTerminationEnabled(true);
964 }
965
966 //! Marque l'image entière comme non à jour.
967 inline
968 void DrawingWindow::dirty()
969 {
970     dirtyFlag = true;
971     dirtyRect = image->rect();
972 }
973
974 //! Marque un point de l'image comme non à jour.
975 /*!
976  * \param x, y          coordonnées du point
977  */
978 inline
979 void DrawingWindow::dirty(int x, int y)
980 {
981     dirty(QRect(x, y, 1, 1));
982 }
983
984 //! Marque une zone de l'image comme non à jour.
985 /*!
986  * La zone est définie par un rectangle dont les coordonnées de deux
987  * sommets oppposés sont données.
988  *
989  * \param x1, y1        coordonnées d'un sommet du rectangle
990  * \param x2, y2        coordonnées du sommet opposé du rectangle
991  */
992 inline
993 void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
994 {
995     QRect r;
996     r.setCoords(x1, y1, x2, y2);
997     dirty(r.normalized());
998 }
999
1000 //! Marque une zone de l'image comme non à jour.
1001 /*!
1002  * \param rect          rectangle délimitant la zone
1003  */
1004 void DrawingWindow::dirty(const QRect &rect)
1005 {
1006     if (dirtyFlag) {
1007         dirtyRect |= rect;
1008     } else {
1009         dirtyFlag = true;
1010         dirtyRect = rect;
1011     }
1012 }
1013
1014 //! Génère un update si besoin.
1015 /*!
1016  * Génère une demande de mise à jour de la fenêtre (appel à update)
1017  * s'il y en a besoin.
1018  */
1019 void DrawingWindow::mayUpdate()
1020 {
1021     imageMutex.lock();
1022     bool dirty = dirtyFlag;
1023     QRect rect = dirtyRect;
1024     dirtyFlag = false;
1025     imageMutex.unlock();
1026     if (dirty)
1027         update(rect);
1028 }
1029
1030 //! Fonction bas-niveau pour sync.
1031 /*!
1032  * Fonction de synchronisation dans le thread principal.
1033  *
1034  * \see sync, customEvent
1035  */
1036 void DrawingWindow::realSync()
1037 {
1038     mayUpdate();
1039     qApp->sendPostedEvents(this, QEvent::UpdateLater);
1040     qApp->sendPostedEvents(this, QEvent::UpdateRequest);
1041     qApp->sendPostedEvents(this, QEvent::Paint);
1042     qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
1043                         QEventLoop::ExcludeSocketNotifiers |
1044                         QEventLoop::DeferredDeletion |
1045                         QEventLoop::X11ExcludeTimers);
1046     qApp->flush();
1047     qApp->syncX();
1048     syncMutex.lock();
1049     syncCondition.wakeAll();
1050     syncMutex.unlock();
1051 }
1052
1053 //! Fonction bas-niveau pour drawText.
1054 /*!
1055  * Le rendu de texte doit être fait dans le thread principal.  D'où
1056  * les manipulations tordues et la synchronisation qui s'en suit.
1057  *
1058  * \param x, y, text, flags     cf. drawText
1059  *
1060  * \see drawText, customEvent
1061  */
1062 void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
1063 {
1064     QRect r(image->rect());
1065     switch (flags & Qt::AlignHorizontal_Mask) {
1066     case Qt::AlignRight:
1067         r.setRight(x);
1068         break;
1069     case Qt::AlignHCenter:
1070         if (x < width / 2)
1071             r.setLeft(2 * x - width + 1);
1072         else
1073             r.setRight(2 * x);
1074         break;
1075     default:
1076         r.setLeft(x);
1077     }
1078     switch (flags & Qt::AlignVertical_Mask) {
1079     case Qt::AlignBottom:
1080         r.setBottom(y);
1081         break;
1082     case Qt::AlignVCenter:
1083         if (y < height / 2)
1084             r.setTop(2 * y - height + 1);
1085         else
1086             r.setBottom(2 * y);
1087         break;
1088     default:
1089         r.setTop(y);
1090     }
1091     syncMutex.lock();
1092     painter->drawText(r, flags, QString::fromUtf8(text), &r);
1093     dirty(r);
1094     syncCondition.wakeAll();
1095     syncMutex.unlock();
1096 }
1097
1098 //--- DrawingThread ----------------------------------------------------
1099
1100 //! Constructeur.
1101 DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
1102     : drawingWindow(w)
1103     , threadFunction(f)
1104     , started_once(false)
1105 {
1106 }
1107
1108 //! Démarre le thread si ce n'a pas encore été fait.
1109 void DrawingThread::start_once(Priority priority)
1110 {
1111     if (!started_once) {
1112         started_once = true;
1113         start(priority);
1114     }
1115 }
1116
1117 //! La vraie fonction pour le thread.
1118 void DrawingThread::run()
1119 {
1120     threadFunction(drawingWindow);
1121 }