1 import java.util.Collections;
2 import java.util.HashMap;
5 import java.awt.Dimension;
6 import java.awt.Graphics2D;
7 import java.awt.Graphics;
9 import java.awt.Polygon;
10 import java.awt.Toolkit;
11 import java.awt.event.KeyAdapter;
12 import java.awt.event.KeyEvent;
13 import java.awt.event.MouseAdapter;
14 import java.awt.event.MouseEvent;
15 import java.awt.event.WindowAdapter;
16 import java.awt.event.WindowEvent;
17 import java.awt.image.BufferedImage;
18 import javax.swing.JFrame;
19 import javax.swing.JPanel;
24 * <p>Cette classe permet d'écrire des applications graphiques simples
25 * en dessinant dans une fenêtre.
27 * <p><b>NB.</b> Pour toutes les méthodes de dessin, le coin en haut à
28 * gauche de la fenêtre a les coordonnées (0, 0). Le coin en bas à
29 * droite de la fenêtre a les coordonnées (largeur - 1, hauteur - 1),
30 * si la fenêtre est de dimension largeur × hauteur.
32 * <p>Un appui sur la touche <Esc> provoque la fermeture de la
33 * fenêtre. Comme pour la plupart des applications, il est également
34 * possible de fermer la fenêtre via le gestionnaire de fenêtres.
36 * @author Arnaud Giersch <arnaud.giersch@univ-fcomte.fr>
39 public class DrawingWindow {
41 /** Largeur de la fenêtre */
42 public final int width;
44 /** Hauteur de la fenêtre */
45 public final int height;
48 * Construit une nouvelle fenêtre de dessin avec le titre et les dimensions
49 * passés en paramètres.
51 * @param title titre de la fenêtre
52 * @param width largeur de la fenêtre
53 * @param height hauteur de la fenêtre
55 * @see javax.swing.JPanel
57 public DrawingWindow(String title, int width, int height) {
59 this.title = new String(title);
63 mouseLock = new Object();
65 mousePos = new Point(0, 0);
68 image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
69 graphics = image.createGraphics();
72 javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
73 public void run() { createGUI(); }
77 System.err.println("Error: interrupted while creating GUI");
78 System.err.println("Got exception: " + e);
82 setColor(Color.BLACK);
83 setBgColor(Color.WHITE);
89 * Change la couleur de dessin.
91 * @param color couleur
95 * @see #setColor(String)
96 * @see #setColor(float, float, float)
97 * @see #setBgColor(Color)
99 public void setColor(Color color) {
100 graphics.setColor(color);
104 * Change la couleur de dessin.
106 * La couleur est un entier, tel que retourné par {@link #getPointColor}.
107 * Normalement de la forme #00RRGGBB.
111 * @see #setColor(String)
112 * @see #setColor(float, float, float)
113 * @see #setBgColor(int)
114 * @see #getPointColor
116 public void setColor(int rgb) {
117 setColor(new Color(rgb));
121 * Change la couleur de dessin.
123 * @param name nom de couleur
125 * @see #setColor(int)
126 * @see #setColor(float, float, float)
127 * @see #setBgColor(String)
128 * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">liste des noms de couleurs</a>
130 public void setColor(String name) {
131 Color color = colorMap.get(name);
135 System.err.println("Warning: color not found: " + name);
139 * Change la couleur de dessin.
141 * Les composantes de rouge, vert et bleu de la couleur doivent être
142 * compris entre 0 et 1. Si le trois composantes sont à 0, on obtient
143 * du noir; si les trois composantes sont à 1, on obtient du blanc.
145 * @param red composante de rouge
146 * @param green composante de vert
147 * @param blue composante de bleu
149 * @see #setColor(int)
150 * @see #setColor(String)
151 * @see #setBgColor(float, float, float)
153 public void setColor(float red, float green, float blue) {
154 setColor(new Color(red, green, blue));
158 * Change la couleur de fond.
160 * @param color couleur
162 * @see #setBgColor(int)
163 * @see #setBgColor(String)
164 * @see #setBgColor(float, float, float)
165 * @see #setColor(Color)
168 public void setBgColor(Color color) {
172 /** Change la couleur de fond.
176 * @see #setBgColor(String)
177 * @see #setBgColor(float, float, float)
178 * @see #setColor(int)
179 * @see #getPointColor
182 public void setBgColor(int rgb) {
183 setBgColor(new Color(rgb));
187 * Change la couleur de fond.
189 * @param name nom de couleur
191 * @see #setBgColor(int)
192 * @see #setBgColor(float, float, float)
193 * @see #setColor(String)
195 * @see <a href="http://www.w3.org/TR/SVG/types.html#ColorKeywords">liste des noms de couleurs</a>
197 public void setBgColor(String name) {
198 Color color = colorMap.get(name);
202 System.err.println("Warning: color not found: " + name);
205 /** Change la couleur de fond.
207 * @param red composante de rouge
208 * @param green composante de vert
209 * @param blue composante de bleu
211 * @see #setBgColor(int)
212 * @see #setBgColor(String)
213 * @see #setColor(float, float, float)
216 public void setBgColor(float red, float green, float blue) {
217 setBgColor(new Color(red, green, blue));
223 * La fenêtre est effacée avec la couleur de fond courante.
227 public void clearGraph() {
228 synchronized (image) {
229 Color c = graphics.getColor();
230 graphics.setColor(bgColor);
231 graphics.fillRect(0, 0, width, height);
232 graphics.setColor(c);
237 /** Dessine un point.
239 * Dessine un point (pixel) aux coordonnées (x, y), avec la couleur de
244 public void drawPoint(int x, int y) {
245 synchronized (image) {
246 image.setRGB(x, y, graphics.getColor().getRGB());
248 panel.repaint(x, y, 1, 1);
252 * Dessine un segment.
254 * Dessine un segement de droite entre les coordonnées (x1, y1) et
255 * (x2, y2), avec la couleur de dessin courante.
259 public void drawLine(int x1, int y1, int x2, int y2) {
260 synchronized (image) {
261 graphics.drawLine(x1, y1, x2, y2);
263 panel.repaint(Math.min(x1, x2), Math.min(y1, y2),
264 Math.abs(x1 - x2) + 1, Math.abs(y1 - y2) + 1);
267 /** Dessine un rectangle.
269 * Dessine le rectangle parallèle aux axes et défini par les
270 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
271 * la couleur de dessin courante.
276 public void drawRect(int x1, int y1, int x2, int y2) {
277 int x = Math.min(x1, x2);
278 int y = Math.min(y1, y2);
279 int w = Math.abs(x1 - x2);
280 int h = Math.abs(y1 - y2);
281 synchronized (image) {
282 graphics.drawRect(x, y, w, h);
284 panel.repaint(x, y, w + 1, h + 1);
287 /** Dessine un rectangle plein.
289 * Dessine le rectangle plein parallèle aux axes et défini par les
290 * coordonnées de deux sommets opposés (x1, y1) et (x2, y2). Utilise
291 * la couleur de dessin courante.
296 public void fillRect(int x1, int y1, int x2, int y2) {
297 int x = Math.min(x1, x2);
298 int y = Math.min(y1, y2);
299 int w = Math.abs(x1 - x2) + 1;
300 int h = Math.abs(y1 - y2) + 1;
301 synchronized (image) {
302 graphics.fillRect(x, y, w, h);
304 panel.repaint(x, y, w, h);
310 * Dessine un cercle de centre (x, y) et de rayon r. Utilise la
311 * couleur de dessin courante.
316 public void drawCircle(int x, int y, int r) {
317 synchronized (image) {
318 graphics.drawOval(x - r, y - r, 2 * r, 2 * r);
320 panel.repaint(x - r, y - r, 2 * r + 1, 2 * r + 1);
326 * Dessine un disque (cercle plein) de centre (x, y) et de rayon r.
327 * Utilise la couleur de dessin courante.
332 public void fillCircle(int x, int y, int r) {
333 synchronized (image) {
334 graphics.drawOval(x - r, y - r, 2 * r, 2 * r);
335 graphics.fillOval(x - r, y - r, 2 * r, 2 * r);
337 panel.repaint(x - r, y - r, 2 * r + 1, 2 * r + 1);
341 * Dessine un triangle.
343 * Dessine un triangle défini par les coordonnées de ses sommets:
344 * (x1, y1), (x2, y2) et (x3, y3). Utilise la couleur de dessin
351 public void drawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
352 Polygon poly = new Polygon();
353 poly.addPoint(x1, y1);
354 poly.addPoint(x2, y2);
355 poly.addPoint(x3, y3);
356 synchronized (image) {
357 graphics.drawPolygon(poly);
359 panel.repaint(poly.getBounds());
363 * Dessine un triangle plein.
365 * Dessine un triangle plein défini par les coordonnées de ses
366 * sommets: (x1, y1), (x2, y2) et (x3, y3). Utilise la couleur de
372 public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3) {
373 Polygon poly = new Polygon();
374 poly.addPoint(x1, y1);
375 poly.addPoint(x2, y2);
376 poly.addPoint(x3, y3);
377 synchronized (image) {
378 graphics.drawPolygon(poly);
379 graphics.fillPolygon(poly);
381 panel.repaint(poly.getBounds());
387 * Écrit le texte text, aux coordonnées (x, y).
389 public void drawText(int x, int y, String text) {
390 synchronized (image) {
391 graphics.drawString(text, x, y);
393 panel.repaint(); // don't know how to calculate tighter bounding box
397 * Retourne la couleur d'un pixel.
399 * Retourne la couleur du pixel de coordonnées (x, y).
401 * @return couleur du pixel
403 * @see #setColor(int)
404 * @see #setBgColor(int)
406 public int getPointColor(int x, int y) {
407 return image.getRGB(x, y);
411 * Attend l'appui sur un des boutons de la souris.
413 * @return vrai (true) si un bouton a été pressé
415 * @see #waitMousePress(long)
418 * @see #getMouseButton()
420 public boolean waitMousePress() {
421 return waitMousePress(-1);
425 * Attend l'appui sur un des boutons de la souris.
427 * @param timeout temps maximal d'attente (millisecondes)
429 * @return vrai (true) si un bouton a été pressé
431 * @see #waitMousePress()
434 * @see #getMouseButton()
436 public boolean waitMousePress(long timeout) {
437 boolean result = false;
438 synchronized (mouseLock) {
443 mouseLock.wait(timeout);
444 else // (timeout < 0)
447 catch (InterruptedException e) {
450 if (mouseEvent != null) {
451 mousePos.setLocation(mouseEvent.getPoint());
452 mouseButton = mouseEvent.getButton();
461 * Retourne la position (x) de la souris la dernière fois qu'un
462 * bouton a été pressé pendant l'appel à {@link #waitMousePress()}.
464 * @return position (x)
466 public int getMouseX() {
471 * Retourne la position (y) de la souris la dernière fois qu'un
472 * bouton a été pressé pendant l'appel à {@link #waitMousePress()}.
474 * @return position (y)
476 public int getMouseY() {
481 * Retourne le numéro du bouton de la souris pressé pendant
482 * le dernier appel à {@link #waitMousePress()}.
484 * @return numéro de bouton (1: gauche, 2: milieu, 3: droit)
486 public int getMouseButton() {
491 * Synchronise le contenu de la fenêtre.
493 * Pour des raisons d'efficacités, le résultat des fonctions de dessin
494 * n'est pas affiché immédiatement. L'appel à sync permet de
495 * synchroniser le contenu de la fenêtre. Autrement dit, cela bloque
496 * l'exécution du programme jusqu'à ce que le contenu de la fenêtre
500 // put an empty action on the event queue, and wait for its completion
502 javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
503 public void run() { }
506 catch (Exception e) {
511 * Ferme la fenêtre graphique.
513 public void closeGraph() {
514 javax.swing.SwingUtilities.invokeLater(new Runnable() {
517 new WindowEvent(frame, WindowEvent.WINDOW_CLOSING);
518 Toolkit.getDefaultToolkit()
519 .getSystemEventQueue().postEvent(ev);
526 * Suspend l'exécution pendant un certain temps.
528 * @param secs temps d'attente en seconde
530 static void sleep(long secs) {
532 Thread.sleep(secs * 1000);
534 catch (Exception e) {
539 * Suspend l'exécution pendant un certain temps.
541 * @param msecs temps d'attente en millisecondes
543 static void msleep(long msecs) {
547 catch (Exception e) {
552 * Suspend l'exécution pendant un certain temps.
554 * @param usecs temps d'attente en microsecondes
556 static void usleep(long usecs) {
558 Thread.sleep(usecs / 1000, (int)(usecs % 1000) * 1000);
560 catch (Exception e) {
564 /* PRIVATE STUFF FOLLOWS */
566 private static final Map<String, Color> colorMap;
569 Map<String, Color> m = new HashMap<String, Color>();
570 // From http://www.w3.org/TR/SVG/types.html#ColorKeywords
571 m.put("aliceblue", new Color(0x00f0f8ff));
572 m.put("antiquewhite", new Color(0x00faebd7));
573 m.put("aqua", new Color(0x0000ffff));
574 m.put("aquamarine", new Color(0x007fffd4));
575 m.put("azure", new Color(0x00f0ffff));
576 m.put("beige", new Color(0x00f5f5dc));
577 m.put("bisque", new Color(0x00ffe4c4));
578 m.put("black", new Color(0000000000));
579 m.put("blanchedalmond", new Color(0x00ffebcd));
580 m.put("blue", new Color(0x000000ff));
581 m.put("blueviolet", new Color(0x008a2be2));
582 m.put("brown", new Color(0x00a52a2a));
583 m.put("burlywood", new Color(0x00deb887));
584 m.put("cadetblue", new Color(0x005f9ea0));
585 m.put("chartreuse", new Color(0x007fff00));
586 m.put("chocolate", new Color(0x00d2691e));
587 m.put("coral", new Color(0x00ff7f50));
588 m.put("cornflowerblue", new Color(0x006495ed));
589 m.put("cornsilk", new Color(0x00fff8dc));
590 m.put("crimson", new Color(0x00dc143c));
591 m.put("cyan", new Color(0x0000ffff));
592 m.put("darkblue", new Color(0x0000008b));
593 m.put("darkcyan", new Color(0x00008b8b));
594 m.put("darkgoldenrod", new Color(0x00b8860b));
595 m.put("darkgray", new Color(0x00a9a9a9));
596 m.put("darkgreen", new Color(0x00006400));
597 m.put("darkgrey", new Color(0x00a9a9a9));
598 m.put("darkkhaki", new Color(0x00bdb76b));
599 m.put("darkmagenta", new Color(0x008b008b));
600 m.put("darkolivegreen", new Color(0x00556b2f));
601 m.put("darkorange", new Color(0x00ff8c00));
602 m.put("darkorchid", new Color(0x009932cc));
603 m.put("darkred", new Color(0x008b0000));
604 m.put("darksalmon", new Color(0x00e9967a));
605 m.put("darkseagreen", new Color(0x008fbc8f));
606 m.put("darkslateblue", new Color(0x00483d8b));
607 m.put("darkslategray", new Color(0x002f4f4f));
608 m.put("darkslategrey", new Color(0x002f4f4f));
609 m.put("darkturquoise", new Color(0x0000ced1));
610 m.put("darkviolet", new Color(0x009400d3));
611 m.put("deeppink", new Color(0x00ff1493));
612 m.put("deepskyblue", new Color(0x0000bfff));
613 m.put("dimgray", new Color(0x00696969));
614 m.put("dimgrey", new Color(0x00696969));
615 m.put("dodgerblue", new Color(0x001e90ff));
616 m.put("firebrick", new Color(0x00b22222));
617 m.put("floralwhite", new Color(0x00fffaf0));
618 m.put("forestgreen", new Color(0x00228b22));
619 m.put("fuchsia", new Color(0x00ff00ff));
620 m.put("gainsboro", new Color(0x00dcdcdc));
621 m.put("ghostwhite", new Color(0x00f8f8ff));
622 m.put("gold", new Color(0x00ffd700));
623 m.put("goldenrod", new Color(0x00daa520));
624 m.put("gray", new Color(0x00808080));
625 m.put("grey", new Color(0x00808080));
626 m.put("green", new Color(0x00008000));
627 m.put("greenyellow", new Color(0x00adff2f));
628 m.put("honeydew", new Color(0x00f0fff0));
629 m.put("hotpink", new Color(0x00ff69b4));
630 m.put("indianred", new Color(0x00cd5c5c));
631 m.put("indigo", new Color(0x004b0082));
632 m.put("ivory", new Color(0x00fffff0));
633 m.put("khaki", new Color(0x00f0e68c));
634 m.put("lavender", new Color(0x00e6e6fa));
635 m.put("lavenderblush", new Color(0x00fff0f5));
636 m.put("lawngreen", new Color(0x007cfc00));
637 m.put("lemonchiffon", new Color(0x00fffacd));
638 m.put("lightblue", new Color(0x00add8e6));
639 m.put("lightcoral", new Color(0x00f08080));
640 m.put("lightcyan", new Color(0x00e0ffff));
641 m.put("lightgoldenrodyellow", new Color(0x00fafad2));
642 m.put("lightgray", new Color(0x00d3d3d3));
643 m.put("lightgreen", new Color(0x0090ee90));
644 m.put("lightgrey", new Color(0x00d3d3d3));
645 m.put("", new Color(0000000000));
646 m.put("lightpink", new Color(0x00ffb6c1));
647 m.put("lightsalmon", new Color(0x00ffa07a));
648 m.put("lightseagreen", new Color(0x0020b2aa));
649 m.put("lightskyblue", new Color(0x0087cefa));
650 m.put("lightslategray", new Color(0x00778899));
651 m.put("lightslategrey", new Color(0x00778899));
652 m.put("lightsteelblue", new Color(0x00b0c4de));
653 m.put("lightyellow", new Color(0x00ffffe0));
654 m.put("lime", new Color(0x0000ff00));
655 m.put("limegreen", new Color(0x0032cd32));
656 m.put("linen", new Color(0x00faf0e6));
657 m.put("magenta", new Color(0x00ff00ff));
658 m.put("maroon", new Color(0x00800000));
659 m.put("mediumaquamarine", new Color(0x0066cdaa));
660 m.put("mediumblue", new Color(0x000000cd));
661 m.put("mediumorchid", new Color(0x00ba55d3));
662 m.put("mediumpurple", new Color(0x009370db));
663 m.put("mediumseagreen", new Color(0x003cb371));
664 m.put("mediumslateblue", new Color(0x007b68ee));
665 m.put("mediumspringgreen", new Color(0x0000fa9a));
666 m.put("mediumturquoise", new Color(0x0048d1cc));
667 m.put("mediumvioletred", new Color(0x00c71585));
668 m.put("midnightblue", new Color(0x00191970));
669 m.put("mintcream", new Color(0x00f5fffa));
670 m.put("mistyrose", new Color(0x00ffe4e1));
671 m.put("moccasin", new Color(0x00ffe4b5));
672 m.put("navajowhite", new Color(0x00ffdead));
673 m.put("navy", new Color(0x00000080));
674 m.put("oldlace", new Color(0x00fdf5e6));
675 m.put("olive", new Color(0x00808000));
676 m.put("olivedrab", new Color(0x006b8e23));
677 m.put("orange", new Color(0x00ffa500));
678 m.put("orangered", new Color(0x00ff4500));
679 m.put("orchid", new Color(0x00da70d6));
680 m.put("palegoldenrod", new Color(0x00eee8aa));
681 m.put("palegreen", new Color(0x0098fb98));
682 m.put("paleturquoise", new Color(0x00afeeee));
683 m.put("palevioletred", new Color(0x00db7093));
684 m.put("papayawhip", new Color(0x00ffefd5));
685 m.put("peachpuff", new Color(0x00ffdab9));
686 m.put("peru", new Color(0x00cd853f));
687 m.put("pink", new Color(0x00ffc0cb));
688 m.put("plum", new Color(0x00dda0dd));
689 m.put("powderblue", new Color(0x00b0e0e6));
690 m.put("purple", new Color(0x00800080));
691 m.put("red", new Color(0x00ff0000));
692 m.put("rosybrown", new Color(0x00bc8f8f));
693 m.put("royalblue", new Color(0x004169e1));
694 m.put("saddlebrown", new Color(0x008b4513));
695 m.put("salmon", new Color(0x00fa8072));
696 m.put("sandybrown", new Color(0x00f4a460));
697 m.put("seagreen", new Color(0x002e8b57));
698 m.put("seashell", new Color(0x00fff5ee));
699 m.put("sienna", new Color(0x00a0522d));
700 m.put("silver", new Color(0x00c0c0c0));
701 m.put("skyblue", new Color(0x0087ceeb));
702 m.put("slateblue", new Color(0x006a5acd));
703 m.put("slategray", new Color(0x00708090));
704 m.put("slategrey", new Color(0x00708090));
705 m.put("snow", new Color(0x00fffafa));
706 m.put("springgreen", new Color(0x0000ff7f));
707 m.put("steelblue", new Color(0x004682b4));
708 m.put("tan", new Color(0x00d2b48c));
709 m.put("teal", new Color(0x00008080));
710 m.put("thistle", new Color(0x00d8bfd8));
711 m.put("tomato", new Color(0x00ff6347));
712 m.put("turquoise", new Color(0x0040e0d0));
713 m.put("violet", new Color(0x00ee82ee));
714 m.put("wheat", new Color(0x00f5deb3));
715 m.put("white", new Color(0x00ffffff));
716 m.put("whitesmoke", new Color(0x00f5f5f5));
717 m.put("yellow", new Color(0x00ffff00));
718 m.put("yellowgreen", new Color(0x009acd32));
719 colorMap = Collections.unmodifiableMap(m);
722 private static int instances = 0;
724 private final String title; // window's title
725 private JFrame frame; // the frame (window)
726 private DWPanel panel; // the panel showing the image
727 private BufferedImage image; // the image we draw into
728 private Graphics2D graphics; // graphics associated with image
729 private Color bgColor; // background color, for clearGraph()
730 private boolean isClosed; // is the window closed ?
731 private Object mouseLock; // lock for mouse events
732 private MouseEvent mouseEvent; // last mouse event
733 private Point mousePos; // last mouse position
734 private int mouseButton; // last mouse click
736 // To be run on the Event Dispatching Thread
738 panel = new DWPanel();
739 frame = new JFrame(title);
742 frame.setResizable(false);
743 frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
744 frame.addWindowListener(new DWWindowHandler());
745 frame.addKeyListener(new DWKeyHandler());
746 frame.addMouseListener(new DWMouseHandler());
747 frame.setLocationByPlatform(true);
748 frame.setVisible(true);
751 private class DWWindowHandler extends WindowAdapter {
752 public void windowOpened(WindowEvent ev) {
753 DrawingWindow w = DrawingWindow.this;
754 DrawingWindow.instances++;
758 public void windowClosed(WindowEvent ev) {
759 DrawingWindow w = DrawingWindow.this;
762 if (DrawingWindow.instances <= 0)
763 throw new AssertionError("Bad instance counter: " +
764 DrawingWindow.instances);
765 DrawingWindow.instances--;
766 if (DrawingWindow.instances == 0)
772 private class DWKeyHandler extends KeyAdapter {
773 public void keyPressed(KeyEvent ev) {
774 DrawingWindow w = DrawingWindow.this;
775 if (ev.getKeyCode() == KeyEvent.VK_ESCAPE) {
781 private class DWMouseHandler extends MouseAdapter {
782 public void mousePressed(MouseEvent ev) {
783 DrawingWindow w = DrawingWindow.this;
784 synchronized (w.mouseLock) {
786 mouseLock.notifyAll();
791 private class DWPanel extends JPanel {
793 DrawingWindow w = DrawingWindow.this;
794 Dimension dimension = new Dimension(w.width, w.height);
795 super.setMinimumSize(dimension);
796 super.setMaximumSize(dimension);
797 super.setPreferredSize(dimension);
800 public void paint(Graphics g) {
801 DrawingWindow w = DrawingWindow.this;
802 synchronized (w.image) {
803 g.drawImage(w.image, 0, 0, null);
807 private static final long serialVersionUID = 0;