X-Git-Url: http://info.iut-bm.univ-fcomte.fr/pub/gitweb/graphlib.git/blobdiff_plain/a7346f12e37f808491a76b2465de8ae8cd951d51..0e285c89f65c68d9b304a44891d95cea61a766bf:/DrawingWindow.cpp diff --git a/DrawingWindow.cpp b/DrawingWindow.cpp index c68b7b8..3234441 100644 --- a/DrawingWindow.cpp +++ b/DrawingWindow.cpp @@ -1,10 +1,111 @@ +/* + * Copyright (c) 2007-2013, Arnaud Giersch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + #include "DrawingWindow.h" #include #include -#include #include #include +/*! \class DrawingWindow + * \brief Fenêtre de dessin. + * + * \author Arnaud Giersch + * \date 2007-2013 + * + * Cette classe décrit un widget Qt permettant d'écrire des + * applications graphiques simples. Pour cela, il faut définir une + * fonction de dessin. Cette fonction ne retourne rien et prend comme + * unique paramètre une référence vers un objet de class + * DrawingWindow. + * + * La fonction devra ensuite être passée en paramètre pour les + * constructeurs de la classe, ainsi que les dimension requises pour + * la fenêtre graphique. Le programme est ensuite compilé comme + * n'importe quel programme Qt. + * + * Concrètement, la fonction sera exécutée dans un nouveau thread, + * tandis que le thread principal s'occupera de la gestion des + * évènements et du rendu dans la fenêtre. + * + * NB. Pour toutes les méthodes de dessin, le coin en haut à gauche + * de la fenêtre a les coordonnées (0, 0). Le coin en bas à droite de + * la fenêtre a les coordonnées (largeur - 1, hauteur - 1), si la + * fenêtre est de dimension largeur × hauteur. + * + * Un appui sur la touche <Esc> provoque la fermeture de la fenêtre. + * Comme pour la plupart des applications, il est également possible + * de fermer la fenêtre via le gestionnaire de fenêtres. + * + * Il est possible, dans une application, d'ouvrir plusieurs fenêtres, + * avec des fonctions de dessin éventuellement différentes. + * L'application se terminera normalement lorsque la dernière fenêtre + * sera fermée. + */ + +/*! \example hello.cpp + * + * Voir le code source à la fin de la page. Pour compiler et exécuter + * ce programme, il faut : + * + * 1. Créer le fichier \c hello.pro + * + * Pour simplifier, ce fichier contient la liste des fichiers sources + * composant le programme. + * + * \include hello.pro + * + * 2. Créer le fichier \c Makefile avec la commande : + * + * \verbatim qmake-qt4 hello.pro \endverbatim + * ou tout simplement : + * \verbatim qmake-qt4 \endverbatim + * + * 3. Compiler le programme avec la commande : + * + * \verbatim make hello \endverbatim + * ou tout simplement : + * \verbatim make \endverbatim + * + * 4. Exécuter le programme avec la commande : + * + * \verbatim ./hello \endverbatim + * + * Code source de l'exemple + */ + +/*! \example exemple.cpp + * + * Un exemple un peu plus sophistiqué. + */ + +//! Classe de thread. class DrawingThread: public QThread { public: DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f); @@ -21,34 +122,130 @@ private: 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(SyncRequest)) + { } +}; + +//! Demande de fermeture de fenêtre. +class CloseRequestEvent: public QEvent { +public: + CloseRequestEvent(): QEvent(static_cast(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(DrawTextRequest)) + , x(x_), y(y_), text(text_), flags(flags_) + { } +}; + //--- DrawingWindow ---------------------------------------------------- -DrawingWindow::DrawingWindow(ThreadFunction f, int w, int h) +/*! \file DrawingWindow.h + * \brief Classe DrawingWindow. + */ + +/*! \typedef DrawingWindow::ThreadFunction + * \brief Type de la fonction de dessin, passée en paramètre de construction. + */ +/*! \var DrawingWindow::DEFAULT_WIDTH + * \brief Largeur par défaut de la fenêtre. + */ +/*! \var DrawingWindow::DEFAULT_HEIGHT + * \brief Hauteur par défaut de la fenêtre. + */ +/*! \var DrawingWindow::width + * \brief Largeur de la fenêtre. + */ +/*! \var DrawingWindow::height + * \brief Hauteur de la fenêtre. + */ +/*! \var DrawingWindow::paintInterval + * \brief Intervalle de temps entre deux rendus (ms). + */ + +//! 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) + , width(width_) + , height(height_) { - initialize(f); + 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) + , width(width_) + , height(height_) { - initialize(f); + 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; @@ -56,36 +253,166 @@ DrawingWindow::~DrawingWindow() delete image; } -void DrawingWindow::setColor(float red, float green, float blue) +//! 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) { - fgColor.setRgbF(red, green, blue); - applyColor(); + 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) { - fgColor.setNamedColor(name); - applyColor(); + setColor(QColor(name)); } -void DrawingWindow::setBgColor(float red, float green, float blue) +//! 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) +{ + setColor(QColor::fromRgbF(red, green, blue)); +} + +//! 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) { - bgColor.setRgbF(red, green, blue); + setBgColor(QColor::fromRgb(color)); } +//! 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.setNamedColor(name); + setBgColor(QColor(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) +{ + setBgColor(QColor::fromRgbF(red, green, blue)); +} + +//! Change l'épaisseur du pinceau +/*! + * Le pinceau à une épaisseur de 1 par défaut. + * + * \param width épaisseur du pinceau + */ +void DrawingWindow::setPenWidth(int width) +{ + QPen pen(painter->pen()); + pen.setWidth(width); + painter->setPen(pen); } +//! Retourne la fonte courante utilisée pour dessiner du texte. +/*! + * \see QFont, setFont + */ +const QFont &DrawingWindow::getFont() const +{ + return painter->font(); +} + +//! Applique une nouvelle font pour dessiner du texte. +/*! + * \see QFont, getFont + */ +void DrawingWindow::setFont(const QFont &font) +{ + painter->setFont(font); +} + +//! Active ou non l'antialiasing. +/*! + * Permet de lisser le dessin. + * Fonctionnalité désactivée par défaut. + * + * \param state état de l'antialiasing + * + * \bug expérimental + */ +void DrawingWindow::setAntialiasing(bool state) +{ + painter->setRenderHint(QPainter::Antialiasing, state); +} + +//! 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); @@ -94,33 +421,83 @@ void DrawingWindow::drawPoint(int x, int y) 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); - painter->drawLine(x1, y1, x2, y2); - dirty(x1, y1, x2, y2); - safeUnlock(imageMutex); + if (x1 == x2 && y1 == y2) { + drawPoint(x1, y1); + } else { + safeLock(imageMutex); + painter->drawLine(x1, y1, x2, y2); + dirty(x1, y1, x2, y2); + 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; - r.setCoords(x1, y1, x2 - 1, y2 - 1); - r = r.normalized(); - safeLock(imageMutex); - painter->drawRect(r); - r.adjust(0, 0, 1, 1); - dirty(r); - safeUnlock(imageMutex); + if (x1 == x2 && y1 == y2) { + drawPoint(x1, y1); + } else { + QRect r; + r.setCoords(x1, y1, x2 - 1, y2 - 1); + r = r.normalized(); + safeLock(imageMutex); + painter->drawRect(r); + r.adjust(0, 0, 1, 1); + dirty(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; @@ -132,23 +509,195 @@ void DrawingWindow::drawCircle(int x, int y, int r) 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) +//! Dessine un triangle. +/*! + * Dessine un triangle défini par les coordonnées de ses sommets: + * (x1, y1), (x2, y2) et (x3, y3). Utilise la couleur de dessin + * courante. + * + * \param x1, y1 coordonnées du premier sommet du triangle + * \param x2, y2 coordonnées du deuxième sommet du triangle + * \param x3, y3 coordonnées du troisième sommet du triangle + * + * \see fillTriangle, setColor + */ +void DrawingWindow::drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { - QRect r(image->rect()); - r.moveTo(x, y); + QPolygon poly(3); + poly.putPoints(0, 3, x1, y1, x2, y2, x3, y3); safeLock(imageMutex); - painter->drawText(r, 0, text, &r); - dirty(r); + painter->drawConvexPolygon(poly); + dirty(poly.boundingRect()); safeUnlock(imageMutex); } +//! Dessine un triangle plein. +/*! + * Dessine un triangle plein défini par les coordonnées de ses + * sommets: (x1, y1), (x2, y2) et (x3, y3). Utilise la couleur de + * dessin courante. + * + * \param x1, y1 coordonnées du premier sommet du triangle + * \param x2, y2 coordonnées du deuxième sommet du triangle + * \param x3, y3 coordonnées du troisième sommet du triangle + * + * \see drawTriangle, setColor + */ +void DrawingWindow::fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) +{ + painter->setBrush(getColor()); + drawTriangle(x1, y1, x2, y2, x3, y3); + painter->setBrush(Qt::NoBrush); +} + +//! É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 drawText(int, int, const std::string &, int) + * \see drawTextBg, setColor + * \see QPainter::drawText + */ +void DrawingWindow::drawText(int x, int y, const char *text, int flags) +{ + safeLock(syncMutex); + if (!terminateThread) { + qApp->postEvent(this, new DrawTextEvent(x, y, text, flags)); + syncCondition.wait(&syncMutex); + } + safeUnlock(syncMutex); +} + +//! Écrit du texte. +/*! + * \see drawText(int, int, const char *, int) + */ +void DrawingWindow::drawText(int x, int y, const std::string &text, int flags) +{ + drawText(x, y, text.c_str(), flags); +} + +//! É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 drawTextBg(int, int, const std::string &, int) + * \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); +} + +//! Écrit du texte sur fond coloré. +/*! + * \see drawTextBg(int, int, const char *, int) + */ +void DrawingWindow::drawTextBg(int x, int y, const std::string &text, int flags) +{ + drawTextBg(x, y, text.c_str(), flags); +} + +//! 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) const +{ + return image->pixel(x, y); +} + +//! Attend l'appui sur un des boutons de la souris. +/*! + * Attend l'appui sur un des boutons de la souris. Retourne le bouton + * qui a été pressé et les coordonnées du pointeur de souris à ce + * moment-là. + * + * \param x, y coordonnées du pointeur de souris + * \param button numéro du bouton qui a été pressé + * (1: gauche, 2: droit, 3: milieu, 0 sinon) + * \param time durée maximale de l'attente + * \return true si un bouton a été pressé + * + * \bug expérimental + */ +bool DrawingWindow::waitMousePress(int &x, int &y, int &button, + unsigned long time) +{ + bool pressed; + safeLock(inputMutex); + if (terminateThread) { + pressed = false; + } else { + pressed = inputCondition.wait(&inputMutex, time) && !terminateThread; + if (pressed) { + x = mousePos.x(); + y = mousePos.y(); + if (mouseButton & Qt::LeftButton) + button = 1; + else if (mouseButton & Qt::RightButton) + button = 2; + else if (mouseButton & Qt::MidButton) + button = 3; + else + button = 0; + } + } + safeUnlock(inputMutex); + return pressed; +} + +//! 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; @@ -156,38 +705,58 @@ bool DrawingWindow::sync(unsigned long time) 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 QEvent(QEvent::Type(QEvent::User + 1))); + 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); } +//--- DrawingWindow (protected methods) -------------------------------- +//! \cond show_protected + +/*! + * \see QWidget + */ void DrawingWindow::closeEvent(QCloseEvent *ev) { timer.stop(); - thread->terminate(); + thread->exit(); syncMutex.lock(); + inputMutex.lock(); terminateThread = true; // this flag is needed for the case // where the following wakeAll() call // occurs between the @@ -195,50 +764,64 @@ void DrawingWindow::closeEvent(QCloseEvent *ev) // mutex lock in safeLock() called // from sync() syncCondition.wakeAll(); + inputCondition.wakeAll(); + inputMutex.unlock(); syncMutex.unlock(); QWidget::closeEvent(ev); - thread->wait(); + if (!thread->wait(250)) { + thread->terminate(); + thread->wait(); + } } +/*! + * \see QWidget + */ void DrawingWindow::customEvent(QEvent *ev) { switch ((int )ev->type()) { - case QEvent::User: - 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(); + case SyncRequest: + realSync(); break; - case QEvent::User + 1: + case CloseRequest: close(); break; + case DrawTextRequest: + DrawTextEvent *tev = dynamic_cast(ev); + realDrawText(tev->x, tev->y, tev->text, tev->flags); + break; } } +/*! + * \see QWidget + * + * \bug expérimental + */ +void DrawingWindow::mousePressEvent(QMouseEvent *ev) +{ + inputMutex.lock(); + mousePos = ev->pos(); + mouseButton = ev->button(); + ev->accept(); + inputCondition.wakeAll(); + inputMutex.unlock(); +} + +/*! + * \see QWidget + */ void DrawingWindow::keyPressEvent(QKeyEvent *ev) { - bool accept = true; - switch (ev->key()) { - case Qt::Key_Escape: + if (ev->key() == Qt::Key_Escape) { + ev->accept(); close(); - break; - default: - accept = false; - break; } - if (accept) - ev->accept(); } +/*! + * \see QWidget + */ void DrawingWindow::paintEvent(QPaintEvent *ev) { QPainter widgetPainter(this); @@ -249,13 +832,21 @@ void DrawingWindow::paintEvent(QPaintEvent *ev) 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()) { @@ -266,15 +857,23 @@ void DrawingWindow::timerEvent(QTimerEvent *ev) } } +// \endcond + //--- 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()); @@ -288,14 +887,57 @@ void DrawingWindow::initialize(DrawingWindow::ThreadFunction f) 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) { @@ -304,6 +946,15 @@ 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) { @@ -312,6 +963,7 @@ void DrawingWindow::safeUnlock(QMutex &mutex) thread->setTerminationEnabled(true); } +//! Marque l'image entière comme non à jour. inline void DrawingWindow::dirty() { @@ -319,12 +971,24 @@ 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) { @@ -333,6 +997,10 @@ 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) { @@ -343,6 +1011,11 @@ void DrawingWindow::dirty(const QRect &rect) } } +//! 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(); @@ -354,8 +1027,77 @@ void DrawingWindow::mayUpdate() 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, QString::fromUtf8(text), &r); + dirty(r); + syncCondition.wakeAll(); + syncMutex.unlock(); +} + //--- DrawingThread ---------------------------------------------------- +//! Constructeur. DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f) : drawingWindow(w) , threadFunction(f) @@ -363,6 +1105,7 @@ DrawingThread::DrawingThread(DrawingWindow &w, DrawingWindow::ThreadFunction f) { } +//! Démarre le thread si ce n'a pas encore été fait. void DrawingThread::start_once(Priority priority) { if (!started_once) { @@ -371,6 +1114,7 @@ void DrawingThread::start_once(Priority priority) } } +//! La vraie fonction pour le thread. void DrawingThread::run() { threadFunction(drawingWindow);