import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Graphics; import java.awt.Point; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; /** * Fenêtre de dessin * *
Cette classe permet d'écrire des applications graphiques simples * en dessinant dans une 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.
*
* @author Arnaud Giersch <arnaud.giersch@univ-fcomte.fr>
* @version 20141104
*/
public class DrawingWindow {
/** Largeur de la fenêtre */
public final int width;
/** Hauteur de la fenêtre */
public final int height;
/**
* Construit une nouvelle fenêtre de dessin avec le titre et les dimensions
* passés en paramètres.
*
* @param title titre de la fenêtre
* @param width largeur de la fenêtre
* @param height hauteur de la fenêtre
*
* @see javax.swing.JPanel
*/
public DrawingWindow(String title, int width, int height) {
this.title = new String(title);
this.width = width;
this.height = height;
mouseLock = new Object();
mouseEvent = null;
mousePos = new Point(0, 0);
mouseButton = 0;
image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
graphics = image.createGraphics();
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() { createGUI(); }
});
}
catch (Exception e) {
System.err.println("Error: interrupted while creating GUI");
System.err.println("Got exception: " + e);
System.exit(1);
}
setColor(Color.BLACK);
setBgColor(Color.WHITE);
clearGraph();
sync();
}
/**
* Change la couleur de dessin.
*
* @param color couleur
*
* @see java.awt.Color
* @see #setColor(int)
* @see #setColor(String)
* @see #setColor(float, float, float)
* @see #setBgColor(Color)
*/
public void setColor(Color color) {
graphics.setColor(color);
}
/**
* Change la couleur de dessin.
*
* La couleur est un entier, tel que retourné par {@link #getPointColor}.
* Normalement de la forme #00RRGGBB.
*
* @param rgb couleur
*
* @see #setColor(String)
* @see #setColor(float, float, float)
* @see #setBgColor(int)
* @see #getPointColor
*/
public void setColor(int rgb) {
setColor(new Color(rgb));
}
/**
* Change la couleur de dessin.
*
* @param name nom de couleur
*
* @see #setColor(int)
* @see #setColor(float, float, float)
* @see #setBgColor(String)
* @see liste des noms de couleurs
*/
public void setColor(String name) {
Color color = colorMap.get(name);
if (color != null)
setColor(color);
else
System.err.println("Warning: color not found: " + 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(int)
* @see #setColor(String)
* @see #setBgColor(float, float, float)
*/
public void setColor(float red, float green, float blue) {
setColor(new Color(red, green, blue));
}
/**
* Change la couleur de fond.
*
* @param color couleur
*
* @see #setBgColor(int)
* @see #setBgColor(String)
* @see #setBgColor(float, float, float)
* @see #setColor(Color)
* @see #clearGraph
*/
public void setBgColor(Color color) {
bgColor = color;
}
/** Change la couleur de fond.
*
* @param rgb couleur
*
* @see #setBgColor(String)
* @see #setBgColor(float, float, float)
* @see #setColor(int)
* @see #getPointColor
* @see #clearGraph
*/
public void setBgColor(int rgb) {
setBgColor(new Color(rgb));
}
/**
* Change la couleur de fond.
*
* @param name nom de couleur
*
* @see #setBgColor(int)
* @see #setBgColor(float, float, float)
* @see #setColor(String)
* @see #clearGraph
* @see liste des noms de couleurs
*/
public void setBgColor(String name) {
Color color = colorMap.get(name);
if (color != null)
setBgColor(color);
else
System.err.println("Warning: color not found: " + name);
}
/** Change la couleur de fond.
*
* @param red composante de rouge
* @param green composante de vert
* @param blue composante de bleu
*
* @see #setBgColor(int)
* @see #setBgColor(String)
* @see #setColor(float, float, float)
* @see #clearGraph
*/
public void setBgColor(float red, float green, float blue) {
setBgColor(new Color(red, green, blue));
}
/**
* Efface la fenêtre.
*
* La fenêtre est effacée avec la couleur de fond courante.
*
* @see #setBgColor
*/
public void clearGraph() {
synchronized (image) {
Color c = graphics.getColor();
graphics.setColor(bgColor);
graphics.fillRect(0, 0, width, height);
graphics.setColor(c);
}
panel.repaint();
}
/** Dessine un point.
*
* Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
* dessin courante.
*
* @see #setColor
*/
public void drawPoint(int x, int y) {
if (x < 0 || y < 0 || x >= width || y >= height)
return;
synchronized (image) {
image.setRGB(x, y, graphics.getColor().getRGB());
}
panel.repaint(x, y, 1, 1);
}
/**
* Dessine un segment.
*
* Dessine un segement de droite entre les coordonnées (x1, y1) et
* (x2, y2), avec la couleur de dessin courante.
*
* @see #setColor
*/
public void drawLine(int x1, int y1, int x2, int y2) {
synchronized (image) {
graphics.drawLine(x1, y1, x2, y2);
}
panel.repaint(Math.min(x1, x2), Math.min(y1, y2),
Math.abs(x1 - x2) + 1, Math.abs(y1 - y2) + 1);
}
/** 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.
*
* @see #fillRect
* @see #setColor
*/
public void drawRect(int x1, int y1, int x2, int y2) {
int x = Math.min(x1, x2);
int y = Math.min(y1, y2);
int w = Math.abs(x1 - x2);
int h = Math.abs(y1 - y2);
synchronized (image) {
graphics.drawRect(x, y, w, h);
}
panel.repaint(x, y, w + 1, h + 1);
}
/** 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.
*
* @see #drawRect
* @see #setColor
*/
public void fillRect(int x1, int y1, int x2, int y2) {
int x = Math.min(x1, x2);
int y = Math.min(y1, y2);
int w = Math.abs(x1 - x2) + 1;
int h = Math.abs(y1 - y2) + 1;
synchronized (image) {
graphics.fillRect(x, y, w, h);
}
panel.repaint(x, y, w, h);
}
/**
* Dessine un cercle.
*
* Dessine un cercle de centre (x, y) et de rayon r. Utilise la
* couleur de dessin courante.
*
* @see #fillCircle
* @see #setColor
*/
public void drawCircle(int x, int y, int r) {
synchronized (image) {
graphics.drawOval(x - r, y - r, 2 * r, 2 * r);
}
panel.repaint(x - r, y - r, 2 * r + 1, 2 * r + 1);
}
/**
* Dessine un disque.
*
* Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
* Utilise la couleur de dessin courante.
*
* @see #drawCircle
* @see #setColor
*/
public void fillCircle(int x, int y, int r) {
synchronized (image) {
graphics.drawOval(x - r, y - r, 2 * r, 2 * r);
graphics.fillOval(x - r, y - r, 2 * r, 2 * r);
}
panel.repaint(x - r, y - r, 2 * r + 1, 2 * r + 1);
}
/**
* 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.
*
* @see #fillTriangle
* @see #setColor
*/
public void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
Polygon poly = new Polygon();
poly.addPoint(x1, y1);
poly.addPoint(x2, y2);
poly.addPoint(x3, y3);
synchronized (image) {
graphics.drawPolygon(poly);
}
Rectangle bounds = poly.getBounds();
bounds.setSize(bounds.width + 1, bounds.height + 1);
panel.repaint(bounds);
}
/**
* 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.
*
* @see #drawTriangle
* @see #setColor
*/
public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
Polygon poly = new Polygon();
poly.addPoint(x1, y1);
poly.addPoint(x2, y2);
poly.addPoint(x3, y3);
synchronized (image) {
graphics.drawPolygon(poly);
graphics.fillPolygon(poly);
}
Rectangle bounds = poly.getBounds();
bounds.setSize(bounds.width + 1, bounds.height + 1);
panel.repaint(poly.getBounds());
}
/**
* Écrit du texte.
*
* Écrit le texte text, aux coordonnées (x, y).
*/
public void drawText(int x, int y, String text) {
synchronized (image) {
graphics.drawString(text, x, y);
}
panel.repaint(); // don't know how to calculate tighter bounding box
}
/**
* Retourne la couleur d'un pixel.
*
* Retourne la couleur du pixel de coordonnées (x, y).
*
* @return couleur du pixel
*
* @see #setColor(int)
* @see #setBgColor(int)
*/
public int getPointColor(int x, int y) {
return (x < 0 || y < 0 || x >= width || y >= height) ?
0 : image.getRGB(x, y) & 0x00ffffff;
}
/**
* Attend l'appui sur un des boutons de la souris.
*
* @return vrai (true) si un bouton a été pressé
*
* @see #waitMousePress(long)
* @see #getMouseX
* @see #getMouseY
* @see #getMouseButton
*/
public boolean waitMousePress() {
return waitMousePress(-1);
}
/**
* Attend l'appui sur un des boutons de la souris.
*
* @param timeout temps maximal d'attente (millisecondes)
*
* @return vrai (true) si un bouton a été pressé
*
* @see #waitMousePress()
* @see #getMouseX
* @see #getMouseY
* @see #getMouseButton
*/
public boolean waitMousePress(long timeout) {
boolean result = false;
synchronized (mouseLock) {
if (timeout != 0) {
mouseEvent = null;
try {
if (timeout > 0)
mouseLock.wait(timeout);
else // (timeout < 0)
mouseLock.wait();
}
catch (InterruptedException e) {
}
}
if (mouseEvent != null) {
mousePos.setLocation(mouseEvent.getPoint());
mouseButton = mouseEvent.getButton();
mouseEvent = null;
result = true;
}
}
return result;
}
/**
* Retourne la position (x) de la souris la dernière fois qu'un
* bouton a été pressé pendant l'appel à {@link #waitMousePress()}.
*
* @return position (x)
*/
public int getMouseX() {
return mousePos.x;
}
/**
* Retourne la position (y) de la souris la dernière fois qu'un
* bouton a été pressé pendant l'appel à {@link #waitMousePress()}.
*
* @return position (y)
*/
public int getMouseY() {
return mousePos.y;
}
/**
* Retourne le numéro du bouton de la souris pressé pendant
* le dernier appel à {@link #waitMousePress()}.
*
* @return numéro de bouton (1: gauche, 2: milieu, 3: droit)
*/
public int getMouseButton() {
return mouseButton;
}
/**
* 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.
*/
public void sync() {
// put an empty action on the event queue, and wait for its completion
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() { }
});
}
catch (Exception e) {
}
}
/**
* Ferme la fenêtre graphique.
*/
public void closeGraph() {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
WindowEvent ev =
new WindowEvent(frame, WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit()
.getSystemEventQueue().postEvent(ev);
}
});
}
/**
* Suspend l'exécution pendant un certain temps.
*
* @param secs temps d'attente en seconde
*
* @see #msleep
* @see #usleep
*/
public static void sleep(long secs) {
try {
Thread.sleep(secs * 1000);
}
catch (Exception e) {
}
}
/**
* Suspend l'exécution pendant un certain temps.
*
* @param msecs temps d'attente en millisecondes
*
* @see #sleep
* @see #usleep
*/
public static void msleep(long msecs) {
try {
Thread.sleep(msecs);
}
catch (Exception e) {
}
}
/**
* Suspend l'exécution pendant un certain temps.
*
* @param usecs temps d'attente en microsecondes
*
* @see #sleep
* @see #msleep
*/
public static void usleep(long usecs) {
try {
Thread.sleep(usecs / 1000, (int)(usecs % 1000) * 1000);
}
catch (Exception e) {
}
}
/* PRIVATE STUFF FOLLOWS */
private static final Map