#include "DrawingWindow.h"
#include <QApplication>
#include <QPaintEvent>
-#include <QPainter>
#include <QThread>
#include <QTimerEvent>
+/*! Classe de thread.
+ */
class DrawingThread: public QThread {
public:
DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f);
friend class DrawingWindow;
};
+enum UserEvents {
+ SyncRequest = QEvent::User, //!< Demande de synchronisation.
+ CloseRequest, //!< Demande de fermeture de la fenêtre.
+ DrawTextRequest, //!< Demande d'écriture de texte.
+};
+
+/*! Demande de synchronisation.
+ */
+class SyncRequestEvent: public QEvent {
+public:
+ SyncRequestEvent(): QEvent(static_cast<QEvent::Type>(SyncRequest))
+ { }
+};
+
+/*! Demande de fermeture de fenêtre.
+ */
+class CloseRequestEvent: public QEvent {
+public:
+ CloseRequestEvent(): QEvent(static_cast<QEvent::Type>(CloseRequest))
+ { }
+};
+
+/*! Demande de tracé de texte.
+ */
+class DrawTextEvent: public QEvent {
+public:
+ const int x;
+ const int y;
+ const char *text;
+ const int flags;
+ DrawTextEvent(int x_, int y_, const char* text_, int flags_)
+ : QEvent(static_cast<QEvent::Type>(DrawTextRequest))
+ , x(x_), y(y_), text(text_), flags(flags_)
+ { }
+};
+
//--- DrawingWindow ----------------------------------------------------
-DrawingWindow::DrawingWindow(ThreadFunction f, int w, int h)
+/*! Constructeur.
+ *
+ * Construit une nouvelle fenêtre de dessin avec les dimensions
+ * passées en paramètres. La fonction fun sera exécutée dans un
+ * nouveau thread.
+ *
+ * \param fun fonction de dessin
+ * \param width_ largeur de la fenêtre
+ * \param height_ hauteur de la fenêtre
+ *
+ * \see QWidget
+ */
+DrawingWindow::DrawingWindow(ThreadFunction fun, int width_, int height_)
: QWidget()
- , width(w)
- , height(h)
-{
- initialize(f);
-}
-
+ , width(width_)
+ , height(height_)
+{
+ initialize(fun);
+}
+
+/*! Constructeur.
+ *
+ * Construit un nouveau widget de dessin avec les dimensions passées
+ * en paramètres. La fonction fun sera exécutée dans un nouveau
+ * thread.
+ *
+ * \param parent widget parent
+ * \param fun fonction de dessin
+ * \param width_ largeur de la fenêtre
+ * \param height_ hauteur de la fenêtre
+ *
+ * \see QWidget
+ */
DrawingWindow::DrawingWindow(QWidget *parent,
- ThreadFunction f, int w, int h)
+ ThreadFunction fun, int width_, int height_)
: QWidget(parent)
- , width(w)
- , height(h)
-{
- initialize(f);
-}
-
+ , width(width_)
+ , height(height_)
+{
+ initialize(fun);
+}
+
+/*! Constructeur.
+ *
+ * Construit un nouveau widget de dessin avec les dimensions passées
+ * en paramètres. La fonction fun sera exécutée dans un nouveau
+ * thread.
+ *
+ * \param parent widget parent
+ * \param flags flags passés au constructeur de QWidget
+ * \param fun fonction de dessin
+ * \param width_ largeur de la fenêtre
+ * \param height_ hauteur de la fenêtre
+ *
+ * \see QWidget
+ */
DrawingWindow::DrawingWindow(QWidget *parent, Qt::WindowFlags flags,
- ThreadFunction f, int w, int h)
+ ThreadFunction fun, int width_, int height_)
: QWidget(parent, flags)
- , width(w)
- , height(h)
+ , width(width_)
+ , height(height_)
{
- initialize(f);
+ initialize(fun);
}
+/*! Destructeur.
+ */
DrawingWindow::~DrawingWindow()
{
delete thread;
delete image;
}
+/*! Change la couleur de dessin.
+ *
+ * La couleur est un entier, tel que retourné par getPointColor.
+ * Normalement de la forme #00RRGGBB.
+ *
+ * \param color couleur
+ *
+ * \see setColor(const char *), setColor(float, float, float),
+ * setBgColor(unsigned int),
+ * getPointColor
+ */
+void DrawingWindow::setColor(unsigned int color)
+{
+ setColor(QColor::fromRgb(color));
+}
+
+/*! Change la couleur de dessin.
+ *
+ * Le nom de couleur est de la forme "black", "white", "red", "blue", ...
+ *
+ * \param name nom de couleur
+ *
+ * \see setColor(unsigned int), setColor(float, float, float),
+ * setBgColor(const char *)
+ * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
+ */
+void DrawingWindow::setColor(const char *name)
+{
+ setColor(QColor(name));
+}
+
+/*! Change la couleur de dessin.
+ *
+ * Les composantes de rouge, vert et bleu de la couleur doivent être
+ * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
+ * du noir; si les trois composantes sont à 1, on obtient du blanc.
+ *
+ * \param red composante de rouge
+ * \param green composante de vert
+ * \param blue composante de bleu
+ *
+ * \see setColor(unsigned int), setColor(const char *),
+ * setBgColor(float, float, float)
+ */
void DrawingWindow::setColor(float red, float green, float blue)
{
- fgColor.setRgbF(red, green, blue);
- applyColor();
+ setColor(QColor::fromRgbF(red, green, blue));
}
-void DrawingWindow::setColor(const char *name)
+/*! Change la couleur de fond.
+ *
+ * \param color couleur
+ *
+ * \see setBgColor(const char *), setBgColor(float, float, float),
+ * setColor(unsigned int),
+ * getPointColor,
+ * clearGraph
+ */
+void DrawingWindow::setBgColor(unsigned int color)
{
- fgColor.setNamedColor(name);
- applyColor();
+ setBgColor(QColor::fromRgb(color));
}
-void DrawingWindow::setBgColor(float red, float green, float blue)
+/*! Change la couleur de fond.
+ *
+ * \param name nom de couleur
+ *
+ * \see setBgColor(unsigned int), setBgColor(float, float, float),
+ * setColor(const char *),
+ * clearGraph
+ * \see http://www.w3.org/TR/SVG/types.html#ColorKeywords
+ */
+void DrawingWindow::setBgColor(const char *name)
{
- bgColor.setRgbF(red, green, blue);
+ setBgColor(QColor(name));
}
-void DrawingWindow::setBgColor(const char *name)
+/*! Change la couleur de fond.
+ *
+ * \param red composante de rouge
+ * \param green composante de vert
+ * \param blue composante de bleu
+ *
+ * \see setBgColor(unsigned int), setBgColor(const char *),
+ * setColor(float, float, float),
+ * clearGraph
+ */
+void DrawingWindow::setBgColor(float red, float green, float blue)
{
- bgColor.setNamedColor(name);
+ setBgColor(QColor::fromRgbF(red, green, blue));
}
+/*! Efface la fenêtre.
+ *
+ * La fenêtre est effacée avec la couleur de fond courante.
+ *
+ * \see setBgColor
+ */
void DrawingWindow::clearGraph()
{
safeLock(imageMutex);
- painter->fillRect(image->rect(), bgColor);
+ painter->fillRect(image->rect(), getBgColor());
dirty();
safeUnlock(imageMutex);
}
+/*! Dessine un point.
+ *
+ * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
+ * dessin courante.
+ *
+ * \param x, y coordonnées du point
+ *
+ * \see setColor
+ */
void DrawingWindow::drawPoint(int x, int y)
{
safeLock(imageMutex);
safeUnlock(imageMutex);
}
+/*! Dessine un segment.
+ *
+ * Dessine un segement de droite entre les coordonnées (x1, y1) et
+ * (x2, y2), avec la couleur de dessin courante.
+ *
+ * \param x1, y1 coordonnées d'une extrémité du segment
+ * \param x2, y2 coordonnées de l'autre extrémité du segment
+ *
+ * \see setColor
+ */
void DrawingWindow::drawLine(int x1, int y1, int x2, int y2)
{
safeLock(imageMutex);
safeUnlock(imageMutex);
}
+/*! Dessine un rectangle.
+ *
+ * Dessine le rectangle parallèle aux axes et défini par les
+ * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
+ * la couleur de dessin courante.
+ *
+ * \param x1, y1 coordonnées d'un sommet du rectangle
+ * \param x2, y2 coordonnées du sommet opposé du rectangle
+ *
+ * \see fillRect, setColor
+ */
void DrawingWindow::drawRect(int x1, int y1, int x2, int y2)
{
QRect r;
safeUnlock(imageMutex);
}
+/*! Dessine un rectangle plein.
+ *
+ * Dessine le rectangle plein parallèle aux axes et défini par les
+ * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
+ * la couleur de dessin courante.
+ *
+ * \param x1, y1 coordonnées d'un sommet du rectangle
+ * \param x2, y2 coordonnées du sommet opposé du rectangle
+ *
+ * \see drawRect, setColor
+ */
void DrawingWindow::fillRect(int x1, int y1, int x2, int y2)
{
- painter->setBrush(fgColor);
+ painter->setBrush(getColor());
drawRect(x1, y1, x2, y2);
painter->setBrush(Qt::NoBrush);
}
+/*! Dessine un cercle.
+ *
+ * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
+ * couleur de dessin courante.
+ *
+ * \param x, y coordonnées du centre du cercle
+ * \param r rayon du cercle
+ *
+ * \see fillCircle, setColor
+ */
void DrawingWindow::drawCircle(int x, int y, int r)
{
QRect rect;
safeUnlock(imageMutex);
}
+/*! Dessine un disque.
+ *
+ * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
+ * Utilise la couleur de dessin courante.
+ *
+ * \param x, y coordonnées du centre du disque
+ * \param r rayon du disque
+ *
+ * \see drawCircle, setColor
+ */
void DrawingWindow::fillCircle(int x, int y, int r)
{
- painter->setBrush(fgColor);
+ painter->setBrush(getColor());
drawCircle(x, y, r);
painter->setBrush(Qt::NoBrush);
}
-void DrawingWindow::drawText(int x, int y, const char *text)
+/*! Écrit du texte.
+ *
+ * Écrit le texte text, aux coordonnées (x, y) et avec les paramètres
+ * d'alignement flags. Le texte est écrit avec la couleur de dessin
+ * courante. Les flags sont une combinaison (ou binaire) de
+ * Qt::AlignLeft, Qt::AligneRight, Qt::AlignHCenter, Qt::AlignTop,
+ * Qt::AlignBottom, Qt::AlignVCenter, Qt::AlignCenter. Par défaut, le
+ * texte est aligné en haut à gauche.
+ *
+ * \param x, y coordonnées du texte
+ * \param text texte à écrire
+ * \param flags paramètres d'alignement
+ *
+ * \see drawTextBg, setColor
+ * \see QPainter::drawText
+ */
+void DrawingWindow::drawText(int x, int y, const char *text, int flags)
{
- QRect r(image->rect());
- r.moveTo(x, y);
- safeLock(imageMutex);
- painter->drawText(r, 0, text, &r);
- dirty(r);
- safeUnlock(imageMutex);
+ safeLock(syncMutex);
+ if (!terminateThread) {
+ qApp->postEvent(this, new DrawTextEvent(x, y, text, flags));
+ syncCondition.wait(&syncMutex);
+ }
+ safeUnlock(syncMutex);
}
+/*! Écrit du texte sur fond coloré.
+ *
+ * Écrit du texte comme drawText, mais l'arrière-plan est coloré avec
+ * la couleur de fond courante.
+ *
+ * \param x, y coordonnées du texte
+ * \param text texte à écrire
+ * \param flags paramètres d'alignement
+ *
+ * \see drawText, setColor, setColorBg
+ */
+void DrawingWindow::drawTextBg(int x, int y, const char *text, int flags)
+{
+ painter->setBackgroundMode(Qt::OpaqueMode);
+ drawText(x, y, text, flags);
+ painter->setBackgroundMode(Qt::TransparentMode);
+}
+
+
+/*! Retourne la couleur d'un pixel.
+ *
+ * Retourne la couleur du pixel de coordonnées (x, y). La valeur
+ * retournée peut servir de paramètres à setColor(unsigned int) ou
+ * setBgColor(unsigned int).
+ *
+ * \param x, y coordonnées du pixel
+ * \return couleur du pixel
+ *
+ * \see setColor(unsigned int), setBgColor(unsigned int)
+ */
+unsigned int DrawingWindow::getPointColor(int x, int y)
+{
+ return image->pixel(x, y);
+}
+
+/*! Synchronise le contenu de la fenêtre.
+ *
+ * Pour des raisons d'efficacités, le résultat des fonctions de dessin
+ * n'est pas affiché immédiatement. L'appel à sync permet de
+ * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
+ * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
+ * soit à jour.
+ *
+ * \param time durée maximale de l'attente
+ * \return true si la fenêtre a pu être synchronisée
+ */
bool DrawingWindow::sync(unsigned long time)
{
bool synced;
if (terminateThread) {
synced = false;
} else {
- qApp->postEvent(this, new QEvent(QEvent::User));
+ qApp->postEvent(this, new SyncRequestEvent());
synced = syncCondition.wait(&syncMutex, time);
}
safeUnlock(syncMutex);
return synced;
}
+/*! Ferme la fenêtre graphique.
+ */
void DrawingWindow::closeGraph()
{
- qApp->postEvent(this, new QCloseEvent());
+ qApp->postEvent(this, new CloseRequestEvent());
}
+/*! Suspend l'exécution pendant un certain temps.
+ *
+ * \param secs temps d'attente en seconde
+ */
void DrawingWindow::sleep(unsigned long secs)
{
DrawingThread::sleep(secs);
}
+/*! Suspend l'exécution pendant un certain temps.
+ *
+ * \param msecs temps d'attente en millisecondes
+ */
void DrawingWindow::msleep(unsigned long msecs)
{
DrawingThread::msleep(msecs);
}
+/*! Suspend l'exécution pendant un certain temps.
+ *
+ * \param usecs temps d'attente en microsecondes
+ */
void DrawingWindow::usleep(unsigned long usecs)
{
DrawingThread::usleep(usecs);
}
+/*! \see QWidget
+ */
void DrawingWindow::closeEvent(QCloseEvent *ev)
{
- qDebug(">>>CLOSING<<<\n");
- char x = 'A';
- qDebug(">>> %c <<<\n", x++);
timer.stop();
- qDebug(">>> %c <<<\n", x++);
thread->terminate();
- qDebug(">>> %c <<<\n", x++);
syncMutex.lock();
- qDebug(">>> %c <<<\n", x++);
terminateThread = true; // this flag is needed for the case
// where the following wakeAll() call
// occurs between the
// setTerminationEnable(false) and the
// mutex lock in safeLock() called
// from sync()
- qDebug(">>> %c <<<\n", x++);
syncCondition.wakeAll();
- qDebug(">>> %c <<<\n", x++);
syncMutex.unlock();
- qDebug(">>> %c <<<\n", x++);
QWidget::closeEvent(ev);
- qDebug(">>> %c <<<\n", x++);
thread->wait();
- qDebug(">>> %c <<<\n", x++);
- qDebug(">>>CLOSED<<<\n");
}
-void DrawingWindow::customEvent(QEvent *)
+/*! \see QWidget
+ */
+void DrawingWindow::customEvent(QEvent *ev)
{
- mayUpdate();
- qApp->sendPostedEvents(this, QEvent::UpdateLater);
- qApp->sendPostedEvents(this, QEvent::UpdateRequest);
- qApp->sendPostedEvents(this, QEvent::Paint);
- qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
- QEventLoop::ExcludeSocketNotifiers |
- QEventLoop::DeferredDeletion |
- QEventLoop::X11ExcludeTimers);
- qApp->flush();
- qApp->syncX();
- syncMutex.lock();
- syncCondition.wakeAll();
- syncMutex.unlock();
+ switch ((int )ev->type()) {
+ case SyncRequest:
+ realSync();
+ break;
+ case CloseRequest:
+ close();
+ break;
+ case DrawTextRequest:
+ DrawTextEvent* tev = dynamic_cast<DrawTextEvent *>(ev);
+ realDrawText(tev->x, tev->y, tev->text, tev->flags);
+ break;
+ }
}
+/*! \see QWidget
+ */
void DrawingWindow::keyPressEvent(QKeyEvent *ev)
{
bool accept = true;
ev->accept();
}
+/*! \see QWidget
+ */
void DrawingWindow::paintEvent(QPaintEvent *ev)
{
QPainter widgetPainter(this);
widgetPainter.drawImage(rect, imageCopy, rect);
}
+/*! \see QWidget
+ */
void DrawingWindow::showEvent(QShowEvent *ev)
{
+ QWidget::showEvent(ev);
+ qApp->flush();
+ qApp->syncX();
timer.start(paintInterval, this);
thread->start_once(QThread::IdlePriority);
- QWidget::showEvent(ev);
}
+/*! \see QWidget
+ */
void DrawingWindow::timerEvent(QTimerEvent *ev)
{
if (ev->timerId() == timer.timerId()) {
//--- DrawingWindow (private methods) ----------------------------------
-void DrawingWindow::initialize(DrawingWindow::ThreadFunction f)
+/*! Fonction d'initialisation.
+ *
+ * Fonction appelée par les différents constructeurs.
+ *
+ * \param fun fonction de dessin
+ */
+void DrawingWindow::initialize(DrawingWindow::ThreadFunction fun)
{
terminateThread = false;
lockCount = 0;
image = new QImage(width, height, QImage::Format_RGB32);
painter = new QPainter(image);
- thread = new DrawingThread(*this, f);
+ thread = new DrawingThread(*this, fun);
setFocusPolicy(Qt::StrongFocus);
setFixedSize(image->size());
dirtyFlag = false;
}
+/*! Change la couleur de dessin.
+ *
+ * \param color couleur
+ */
inline
-void DrawingWindow::applyColor()
+void DrawingWindow::setColor(const QColor& color)
{
QPen pen(painter->pen());
- pen.setColor(fgColor);
+ pen.setColor(color);
painter->setPen(pen);
}
+/*! Change la couleur de fond.
+ *
+ * \param color couleur
+ */
+inline
+void DrawingWindow::setBgColor(const QColor& color)
+{
+ painter->setBackground(color);
+}
+
+/*! Retourne la couleur de dessin courante.
+ *
+ * \return couleur de dessin courante
+ */
+inline
+QColor DrawingWindow::getColor()
+{
+ return painter->pen().color();
+}
+
+/*! Retourne la couleur de fond courante.
+ *
+ * \return couleur de fond courante
+ */
+inline
+QColor DrawingWindow::getBgColor()
+{
+ return painter->background().color();
+}
+
+/*! Verrouille un mutex.
+ *
+ * S'assure que le thread courant ne peut pas être terminé s'il
+ * détient un mutex. Pendant de safeUnlock.
+ *
+ * \param mutex le mutex à verrouiller
+ *
+ * \see safeUnlock
+ */
inline
void DrawingWindow::safeLock(QMutex &mutex)
{
mutex.lock();
}
+/*! Déverrouille un mutex.
+ *
+ * S'assure que le thread courant ne peut pas être terminé s'il
+ * détient un mutex. Pendant de safeLock.
+ *
+ * \param mutex le mutex à déverrouiller
+ *
+ * \see safeLock
+ */
inline
void DrawingWindow::safeUnlock(QMutex &mutex)
{
thread->setTerminationEnabled(true);
}
+/*! Marque l'image entière comme non à jour.
+ */
inline
void DrawingWindow::dirty()
{
dirtyRect = image->rect();
}
+/*! Marque un point de l'image comme non à jour.
+ *
+ * \param x, y coordonnées du point
+ */
inline
void DrawingWindow::dirty(int x, int y)
{
dirty(QRect(x, y, 1, 1));
}
+/*! Marque une zone de l'image comme non à jour.
+ *
+ * La zone est définie par un rectangle dont les coordonnées de deux
+ * sommets oppposés sont données.
+ *
+ * \param x1, y1 coordonnées d'un sommet du rectangle
+ * \param x2, y2 coordonnées du sommet opposé du rectangle
+ */
inline
void DrawingWindow::dirty(int x1, int y1, int x2, int y2)
{
dirty(r.normalized());
}
+/*! Marque une zone de l'image comme non à jour.
+ *
+ * \param rect rectangle délimitant la zone
+ */
void DrawingWindow::dirty(const QRect &rect)
{
if (dirtyFlag) {
}
}
+/*! Génère un update si besoin.
+ *
+ * Génère une demande de mise à jour de la fenêtre (appel à update)
+ * s'il y en a besoin.
+ */
void DrawingWindow::mayUpdate()
{
imageMutex.lock();
update(rect);
}
+/*! Fonction bas-niveau pour sync.
+ *
+ * Fonction de synchronisation dans le thread principal.
+ *
+ * \see sync, customEvent
+ */
+void DrawingWindow::realSync()
+{
+ mayUpdate();
+ qApp->sendPostedEvents(this, QEvent::UpdateLater);
+ qApp->sendPostedEvents(this, QEvent::UpdateRequest);
+ qApp->sendPostedEvents(this, QEvent::Paint);
+ qApp->processEvents(QEventLoop::ExcludeUserInputEvents |
+ QEventLoop::ExcludeSocketNotifiers |
+ QEventLoop::DeferredDeletion |
+ QEventLoop::X11ExcludeTimers);
+ qApp->flush();
+ qApp->syncX();
+ syncMutex.lock();
+ syncCondition.wakeAll();
+ syncMutex.unlock();
+}
+
+/*! Fonction bas-niveau pour drawText.
+ *
+ * Le rendu de texte doit être fait dans le thread principal. D'où
+ * les manipulations tordues et la synchronisation qui s'en suit.
+ *
+ * \param x, y, text, flags cf. drawText
+ *
+ * \see drawText, customEvent
+ */
+void DrawingWindow::realDrawText(int x, int y, const char *text, int flags)
+{
+ QRect r(image->rect());
+ switch (flags & Qt::AlignHorizontal_Mask) {
+ case Qt::AlignRight:
+ r.setRight(x);
+ break;
+ case Qt::AlignHCenter:
+ if (x < width / 2)
+ r.setLeft(2 * x - width + 1);
+ else
+ r.setRight(2 * x);
+ break;
+ default:
+ r.setLeft(x);
+ }
+ switch (flags & Qt::AlignVertical_Mask) {
+ case Qt::AlignBottom:
+ r.setBottom(y);
+ break;
+ case Qt::AlignVCenter:
+ if (y < height / 2)
+ r.setTop(2 * y - height + 1);
+ else
+ r.setBottom(2 * y);
+ break;
+ default:
+ r.setTop(y);
+ }
+ syncMutex.lock();
+ painter->drawText(r, flags, text, &r);
+ dirty(r);
+ syncCondition.wakeAll();
+ syncMutex.unlock();
+}
+
//--- DrawingThread ----------------------------------------------------
+/*! Constructeur.
+ */
DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f)
: drawingWindow(w)
, threadFunction(f)
{
}
+/*! Démarre le thread si ce n'a pas encore été fait.
+ */
void DrawingThread::start_once(Priority priority)
{
if (!started_once) {
}
}
+/*! La vraie fonction pour le thread.
+ */
void DrawingThread::run()
{
threadFunction(drawingWindow);