001// Modified by Hunter Schafer (July 2022)
002
003/**
004 * AUTHOR: NICHOLAS SEWARD
005 * EMAIL: nicholas.seward@gmail.com
006 * LICENSE: MIT (USE THIS HOWEVER YOU SEE FIT.)
007 * DATE: 6/21/2012
008 * VERSION: 2 (THIS SHOULD BE VERSION 0.1 BUT THAT IS A SILLY NUMBERING SYSTEM.)
009 * <p>
010 * <p>
011 * THIS SOFTWARE HAS NO WARRANTY.  IF IT WORKS, SUPER.  IF IT DOESN'T, LET ME
012 * KNOW AND I MIGHT OR MIGHT NOT DO SOMETHING ABOUT IT.
013 * <p>
014 *   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
015 * _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
016 *   U U      U U      U U      U U      U U      U U      U U      U U
017 * <p>
018 * Additional contributors to the UW version (2022):
019 * Hunter Schafer, Leah Perlmutter
020 * </p>
021 */
022
023import java.awt.event.*;
024import javax.swing.*;
025import java.awt.*;
026import java.awt.geom.*;
027import java.awt.image.*;
028import java.io.*;
029import java.lang.reflect.Method;
030import java.util.*;
031import javax.imageio.ImageIO;
032import javax.swing.filechooser.FileNameExtensionFilter;
033
034/**
035 * <h1>About Turtle Graphics</h1>
036 * Turtle is a software library for drawing graphics with simple code, approachable
037 * to beginners.
038 *
039 * This library is one incarnation of Turtle Graphics.
040 * Turtle Graphics has been implemented in many programming languages over many
041 * decades, often as a tool to support people in learning to program!
042 *
043 * <h1>Methods Overview </h1>
044 * <p>
045 * Here is an overview of many of the methods in this package. If you
046 * are new to Turtle, you may want to start with the
047 * methods marked by boldface font and the turtle emoji \ud83d\udc22!
048 * </p>
049 * <h4 style="display:inline">Constructing and Copying Turtles</h4>
050 * <div><ul>
051 * <li> \ud83d\udc22 <b>{@link #Turtle()}</b></li>
052 * <li>{@link #Turtle(double, double)}</li>
053 * <li>{@link #clone()}</li>
054 * </ul></div>
055 * <h4 style="display:inline">Moving and Turning a Turtle</h4>
056 * <div><ul>
057 * <li> \ud83d\udc22 <b>{@link #forward(double)}</b> | <b>{@link #backward(double)}</b></li>
058 * <li> \ud83d\udc22 <b>{@link #left(double)}</b> | <b>{@link #right(double)}</b></li>
059 * <li> \ud83d\udc22 <b>{@link #speed(double)}</b></li>
060 * <li>{@link #home()}</li>
061 * <li>{@link #face(double, double)} | {@link #setDirection(double)} </li>
062 * <li>{@link #setPosition(double, double)} | {@link #setPosition(double, double, double)}</li>
063 * </ul></div>
064 * <h4 style="display:inline">Manipulating the Pen and Drawing</h4>
065 * <div><ul>
066 * <li> \ud83d\udc22 <b>{@link #up()}</b> | <b>{@link #down()}</b></li>
067 * <li> \ud83d\udc22 <b>{@link #width(double)}</b></li>
068 * <li> \ud83d\udc22 <b>{@link #penColor(java.awt.Color)}</b> | <b>{@link #penColor(java.lang.String)}</b></li>
069 * <li>{@link #dot()} | {@link #dot(java.awt.Color)} | {@link #dot(java.awt.Color, double)} | {@link #dot(java.lang.String)} | {@link #dot(java.lang.String, double)}</li>
070 * <li>{@link #stamp()}</li>
071 * </ul></div>
072 * <h4 style="display:inline">Console Output</h4>
073 * <div><ul>
074 * <li> \ud83d\udc22 <b>{@link #accessiblePrinting(boolean)}</b></li>
075 * </ul></div>
076 * <h4 style="display:inline">Changing a Turtle's Appearance</h4>
077 * <div><ul>
078 * <li>{@link #hide()} | {@link #show()}</li>
079 * <li>{@link #fillColor(java.awt.Color)} | {@link #fillColor(java.lang.String)}</li>
080 * <li>{@link #outlineColor(java.awt.Color)} | {@link #outlineColor(java.lang.String)}</li>
081 * <li>{@link #tilt(double)} | {@link #setTilt(double)}</li>
082 * <li>{@link #shape(java.lang.String)} | {@link #shapeSize(int, int)}</li>
083 * </ul></div>
084 * <h4 style="display:inline">Getting a Turtle's State</h4>
085 * <div><ul>
086 * <li>{@link #getX()} | {@link #getY()}</li>
087 * <li>{@link #getDirection()}</li>
088 * <li>{@link #getSpeed()}</li>
089 * <li>{@link #getTilt()}</li>
090 * </ul></div>
091 * <h4 style="display:inline">Manipulating and Saving the Canvas</h4>
092 * <div><ul>
093 * <li>{@link #clear()}</li>
094 * <li>{@link #bgcolor(java.awt.Color)} | {@link #bgcolor(java.lang.String)}</li>
095 * <li>{@link #bgpic(java.lang.String)}</li>
096 * <li>{@link #setCanvasSize(int, int)}</li>
097 * <li>{@link #save(java.lang.String)}</li>
098 * </ul></div>
099 * <h4 style="display:inline">Geometric Computations</h4>
100 * <div><ul>
101 * <li>{@link #canvasX(double)} | {@link #canvasY(double)}</li>
102 * <li>{@link #screenX(double)} | {@link #screenY(double)}</li>
103 * <li>{@link #distance(double, double)}</li>
104 * <li>{@link #towards(double, double)}</li>
105 * </ul></div>
106 *
107 * <h1>Coordinate Systems</h1>
108 * This section is to clarify the meaning of numeric coordinates. 
109 *
110 * <h4>Canvas Coordinates</h4>
111 * Turtle commands such as <code>forward</code>, <code>setPosition</code>,
112 * and <code>getX</code>/<code>getY</code>
113 * are in terms of canvas coordinates.
114 * Canvas coordinates can be thought of as "model coordinates".
115 * <ul>
116 * <li>X axis points rightward.</li>
117 * <li>Y axis points upward.</li>
118 * <li>When the turtle window first loads, the origin is in the center of the
119 * turtle window.</li>
120 * </ul>
121 * 
122 * <h4>Screen Coordinates</h4>
123 * Screen coordinates change relative to the turtle's world as you 
124 * zoom or pan the view of the canvas.
125 * Screen coordinates can be thought of as "view coordinates".
126 * <ul>
127 * <li>X axis points rightward.</li>
128 * <li>Y axis points updward.</li>
129 * <li>The origin is the lower left hand corner of the turtle window, regardless
130 * of how much you have zoomed or panned the view.</li>
131 * </ul>
132 * 
133 *
134 * @author Nicholas Seward
135 */
136@SuppressWarnings({"unchecked", "deprecation", "removal"})
137public class Turtle implements Runnable, ActionListener, MouseListener, MouseMotionListener, KeyListener, ComponentListener, MouseWheelListener {
138
139    private static ArrayList<Turtle> turtles;
140    private static TreeMap<Long, ArrayList> turtleStates;
141    private static TreeMap<Long, ArrayList> redoStates;
142    private static JFrame window;
143    private static JApplet applet;
144    private static JLabel draw;
145    private static int width, height;
146    private static BufferedImage offscreenImage, midscreenImage, onscreenImage;
147    private static Graphics2D offscreen, midscreen, onscreen;
148    private static BufferedImage backgroundImage;
149    private static Color backgroundColor;
150    private static ImageIcon icon;
151    private static Turtle turtle;
152    private static HashMap<String, Polygon> shapes;  //You can only add.  Never remove.
153    private static HashMap<String, Color> colors;
154    private static HashMap<String, ArrayList<ArrayList>> keyBindings;
155    private static HashMap<Turtle, ArrayList<ArrayList>> mouseBindings;
156    private static double centerX, centerY;
157    private static double scale;
158    private static TreeSet<String> keysDown;
159    private static TreeSet<String> processedKeys;
160    private static TreeSet<String> unprocessedKeys;
161    private static long lastUpdate;
162    private static long fps;
163    private static final Object turtleLock = new Object();
164    private static int dragx = 0, dragy = 0, x = 0, y = 0, modifiers = 0;
165    private static final Object keyLock = new Object();
166    private static final int REFRESH_MODE_ANIMATED = 0;
167    private static final int REFRESH_MODE_STATE_CHANGE = 1;
168    private static final int REFRESH_MODE_ON_DEMAND = 2;
169    private static int refreshMode;
170    private static final int BACKGROUND_MODE_STRETCH = 0;
171    private static final int BACKGROUND_MODE_CENTER = 1;
172    private static final int BACKGROUND_MODE_TILE = 2;
173    private static final int BACKGROUND_MODE_CENTER_RELATIVE = 3;
174    private static final int BACKGROUND_MODE_TILE_RELATIVE = 4;
175    private static int backgroundMode;
176    private static Turtle selectedTurtle;
177    private static boolean running;
178
179    // Output if accessibility mode is turned on
180    private static PrintStream out;
181
182    static {
183        init();
184    }
185
186    /**
187     * This is an internal method that should never be called.
188     */
189    public void run() {
190        if (Thread.currentThread().getName().equals("render")) renderLoop();
191        else if (Thread.currentThread().getName().equals("listen")) eventLoop();
192    }
193
194    private void eventLoop() {
195        //System.out.println("EVENT LOOP STARTED");
196        long time = 0;
197        while (running) {
198            time = System.nanoTime();
199            processKeys();
200            waitUntil(time + 1000000000 / fps);
201        }
202    }
203
204    private void renderLoop() {
205        //System.out.println("RENDER LOOP STARTED");
206        long time = 0;
207        while (running) {
208            time = System.nanoTime();
209            //System.out.println(time);
210            if (refreshMode == REFRESH_MODE_ANIMATED) draw();
211            if (!waitUntil(time + 1000000000 / fps)) fps--;
212            else if (fps < 30) fps++;
213        }
214    }
215
216    private static boolean waitUntil(Long time) {
217        long now = System.nanoTime();
218        if (now < time) {
219            try {
220                Thread.sleep((time - now) / 1000000);
221            } catch (Exception e) {
222            }
223            return true;
224        } else return false;
225    }
226
227    private static void init() {
228        //mouseBindings.put(null, new ArrayList<ArrayList>());
229        turtles = new ArrayList<Turtle>();
230        turtleStates = new TreeMap<Long, ArrayList>();
231        redoStates = new TreeMap<Long, ArrayList>();
232        width = 500;
233        height = 500;
234        backgroundColor = Color.WHITE;
235        keyBindings = new HashMap<String, ArrayList<ArrayList>>();
236        mouseBindings = new HashMap<Turtle, ArrayList<ArrayList>>();
237        centerX = 0;
238        centerY = 0;
239        scale = 1;
240        keysDown = new TreeSet<String>();
241        processedKeys = new TreeSet<String>();
242        unprocessedKeys = new TreeSet<String>();
243        lastUpdate = 0;
244        fps = 30;
245        dragx = 0;
246        dragy = 0;
247        x = 0;
248        y = 0;
249        modifiers = 0;
250        refreshMode = REFRESH_MODE_ANIMATED;
251        backgroundMode = BACKGROUND_MODE_TILE_RELATIVE;
252        selectedTurtle = null;
253        running = true;
254
255
256        window = new JFrame("Turtle");
257        icon = new ImageIcon();
258        setupBuffering();
259        draw = new JLabel(icon);
260        window.setContentPane(draw);
261        //window.setDefaultCloseOperation (JFrame.DISPOSE_ON_CLOSE);
262        try {
263            window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
264        } catch (Exception e) {
265        }
266
267        JMenuBar menuBar = new JMenuBar();
268        JMenu menu = new JMenu("File");
269        menuBar.add(menu);
270        JMenuItem menuItem1 = new JMenuItem("Save...");
271
272
273        menuItem1.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S,
274                Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
275        menu.add(menuItem1);
276        window.setJMenuBar(menuBar);
277        window.pack();
278        window.requestFocusInWindow();
279
280        //! TODO: Fix this :)
281        try {
282            Thread.sleep(5000);
283        } catch (Exception e) {
284        }
285
286        drawTurtleIcon();
287        window.setVisible(true);
288
289        makeShapes();
290        turtle = new Turtle(0);
291        draw.setFocusable(true);
292        menuItem1.addActionListener(turtle);
293        window.addComponentListener(turtle);
294        draw.addComponentListener(turtle);
295        draw.addMouseListener(turtle);
296        draw.addMouseMotionListener(turtle);
297        draw.addMouseWheelListener(turtle);
298        window.addKeyListener(turtle);
299        draw.addKeyListener(turtle);
300        draw.requestFocus();
301        initColors();
302
303        // Printing for accessibility default is off
304        accessiblePrinting(false);
305
306//        GraphicsEnvironment ge;
307//        ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
308//        String[] fontNames = ge.getAvailableFontFamilyNames();
309//        System.out.println(Arrays.toString(fontNames));
310
311        (new Thread(turtle, "render")).start();
312        (new Thread(turtle, "listen")).start();
313    }
314
315
316
317    /**
318     * Turn accessible printing on or off.
319     * When accessible printing is turned on, every turtle command that could result
320     * in a visual graphical upate will also be printed to the console as text.
321     *
322     * @param printToConsole <code>true</code> to turn on accessible printing, or
323     * <code>false</code> to turn off accessible printing
324     */
325    public static void accessiblePrinting(boolean printToConsole) {
326        if (printToConsole) {
327            if (out != System.out) {
328                out = System.out;
329                out.println("Printing instructions and position information,"
330                        + " with position information in two dimensional coordinates");
331            }
332        } else {
333            out = new PrintStream(new OutputStream() {
334                public void write(int b) {
335                    //DO NOTHING
336                }
337            });
338        }
339    }
340
341    public static void exit() {
342        running = false;
343        window.setVisible(false);
344        window.dispose();
345    }
346
347    /**
348     * This is an experimental method that should allow you to make turtle
349     * applets in the future.  For now, it doesn't work because the key and
350     * mouse bindings require reflection and applets think that allowing
351     * reflection would open a security hole.  Theoretically in the init method
352     * of the applet you need to place <code>Turtle.startApplet(this);</code>.
353     * <b>This is not currently working.</b>
354     *
355     * @param applet applet
356     */
357    public static void startApplet(JApplet applet) {
358        //turtleStates.clear();
359        //turtles.clear();
360        //init();
361        Turtle.applet = applet;
362        applet.setContentPane(window.getContentPane());
363        window.setVisible(false);
364        try {
365            (new Thread((Runnable) applet, "turtle")).start();
366        } catch (Exception e) {
367            e.printStackTrace();
368        }
369    }
370
371    private static void initColors() {
372        colors = new HashMap<String, Color>();
373        colors.put("aliceblue", new Color(0xf0f8ff));
374        colors.put("antiquewhite", new Color(0xfaebd7));
375        colors.put("aqua", new Color(0x00ffff));
376        colors.put("aquamarine", new Color(0x7fffd4));
377        colors.put("azure", new Color(0xf0ffff));
378        colors.put("beige", new Color(0xf5f5dc));
379        colors.put("bisque", new Color(0xffe4c4));
380        colors.put("black", new Color(0x000000));
381        colors.put("blanchedalmond", new Color(0xffebcd));
382        colors.put("blue", new Color(0x0000ff));
383        colors.put("blueviolet", new Color(0x8a2be2));
384        colors.put("brown", new Color(0xa52a2a));
385        colors.put("burlywood", new Color(0xdeb887));
386        colors.put("cadetblue", new Color(0x5f9ea0));
387        colors.put("chartreuse", new Color(0x7fff00));
388        colors.put("chocolate", new Color(0xd2691e));
389        colors.put("coral", new Color(0xff7f50));
390        colors.put("cornflowerblue", new Color(0x6495ed));
391        colors.put("cornsilk", new Color(0xfff8dc));
392        colors.put("crimson", new Color(0xdc143c));
393        colors.put("cyan", new Color(0x00ffff));
394        colors.put("darkblue", new Color(0x00008b));
395        colors.put("darkcyan", new Color(0x008b8b));
396        colors.put("darkgoldenrod", new Color(0xb8860b));
397        colors.put("darkgray", new Color(0xa9a9a9));
398        colors.put("darkgreen", new Color(0x006400));
399        colors.put("darkkhaki", new Color(0xbdb76b));
400        colors.put("darkmagenta", new Color(0x8b008b));
401        colors.put("darkolivegreen", new Color(0x556b2f));
402        colors.put("darkorange", new Color(0xff8c00));
403        colors.put("darkorchid", new Color(0x9932cc));
404        colors.put("darkred", new Color(0x8b0000));
405        colors.put("darksalmon", new Color(0xe9967a));
406        colors.put("darkseagreen", new Color(0x8fbc8f));
407        colors.put("darkslateblue", new Color(0x483d8b));
408        colors.put("darkslategray", new Color(0x2f4f4f));
409        colors.put("darkturquoise", new Color(0x00ced1));
410        colors.put("darkviolet", new Color(0x9400d3));
411        colors.put("deeppink", new Color(0xff1493));
412        colors.put("deepskyblue", new Color(0x00bfff));
413        colors.put("dimgray", new Color(0x696969));
414        colors.put("dodgerblue", new Color(0x1e90ff));
415        colors.put("firebrick", new Color(0xb22222));
416        colors.put("floralwhite", new Color(0xfffaf0));
417        colors.put("forestgreen", new Color(0x228b22));
418        colors.put("fuchsia", new Color(0xff00ff));
419        colors.put("gainsboro", new Color(0xdcdcdc));
420        colors.put("ghostwhite", new Color(0xf8f8ff));
421        colors.put("gold", new Color(0xffd700));
422        colors.put("goldenrod", new Color(0xdaa520));
423        colors.put("gray", new Color(0x808080));
424        colors.put("green", new Color(0x008000));
425        colors.put("greenyellow", new Color(0xadff2f));
426        colors.put("honeydew", new Color(0xf0fff0));
427        colors.put("hotpink", new Color(0xff69b4));
428        colors.put("indianred", new Color(0xcd5c5c));
429        colors.put("indigo", new Color(0x4b0082));
430        colors.put("ivory", new Color(0xfffff0));
431        colors.put("khaki", new Color(0xf0e68c));
432        colors.put("lavender", new Color(0xe6e6fa));
433        colors.put("lavenderblush", new Color(0xfff0f5));
434        colors.put("lawngreen", new Color(0x7cfc00));
435        colors.put("lemonchiffon", new Color(0xfffacd));
436        colors.put("lightblue", new Color(0xadd8e6));
437        colors.put("lightcoral", new Color(0xf08080));
438        colors.put("lightcyan", new Color(0xe0ffff));
439        colors.put("lightgoldenrodyellow", new Color(0xfafad2));
440        colors.put("lightgreen", new Color(0x90ee90));
441        colors.put("lightgrey", new Color(0xd3d3d3));
442        colors.put("lightpink", new Color(0xffb6c1));
443        colors.put("lightsalmon", new Color(0xffa07a));
444        colors.put("lightseagreen", new Color(0x20b2aa));
445        colors.put("lightskyblue", new Color(0x87cefa));
446        colors.put("lightslategray", new Color(0x778899));
447        colors.put("lightsteelblue", new Color(0xb0c4de));
448        colors.put("lightyellow", new Color(0xffffe0));
449        colors.put("lime", new Color(0x00ff00));
450        colors.put("limegreen", new Color(0x32cd32));
451        colors.put("linen", new Color(0xfaf0e6));
452        colors.put("magenta", new Color(0xff00ff));
453        colors.put("maroon", new Color(0x800000));
454        colors.put("mediumaquamarine", new Color(0x66cdaa));
455        colors.put("mediumblue", new Color(0x0000cd));
456        colors.put("mediumorchid", new Color(0xba55d3));
457        colors.put("mediumpurple", new Color(0x9370db));
458        colors.put("mediumseagreen", new Color(0x3cb371));
459        colors.put("mediumslateblue", new Color(0x7b68ee));
460        colors.put("mediumspringgreen", new Color(0x00fa9a));
461        colors.put("mediumturquoise", new Color(0x48d1cc));
462        colors.put("mediumvioletred", new Color(0xc71585));
463        colors.put("midnightblue", new Color(0x191970));
464        colors.put("mintcream", new Color(0xf5fffa));
465        colors.put("mistyrose", new Color(0xffe4e1));
466        colors.put("moccasin", new Color(0xffe4b5));
467        colors.put("navajowhite", new Color(0xffdead));
468        colors.put("navy", new Color(0x000080));
469        colors.put("oldlace", new Color(0xfdf5e6));
470        colors.put("olive", new Color(0x808000));
471        colors.put("olivedrab", new Color(0x6b8e23));
472        colors.put("orange", new Color(0xffa500));
473        colors.put("orangered", new Color(0xff4500));
474        colors.put("orchid", new Color(0xda70d6));
475        colors.put("palegoldenrod", new Color(0xeee8aa));
476        colors.put("palegreen", new Color(0x98fb98));
477        colors.put("paleturquoise", new Color(0xafeeee));
478        colors.put("palevioletred", new Color(0xdb7093));
479        colors.put("papayawhip", new Color(0xffefd5));
480        colors.put("peachpuff", new Color(0xffdab9));
481        colors.put("peru", new Color(0xcd853f));
482        colors.put("pink", new Color(0xffc0cb));
483        colors.put("plum", new Color(0xdda0dd));
484        colors.put("powderblue", new Color(0xb0e0e6));
485        colors.put("purple", new Color(0x800080));
486        colors.put("red", new Color(0xff0000));
487        colors.put("rosybrown", new Color(0xbc8f8f));
488        colors.put("royalblue", new Color(0x4169e1));
489        colors.put("saddlebrown", new Color(0x8b4513));
490        colors.put("salmon", new Color(0xfa8072));
491        colors.put("sandybrown", new Color(0xf4a460));
492        colors.put("seagreen", new Color(0x2e8b57));
493        colors.put("seashell", new Color(0xfff5ee));
494        colors.put("sienna", new Color(0xa0522d));
495        colors.put("silver", new Color(0xc0c0c0));
496        colors.put("skyblue", new Color(0x87ceeb));
497        colors.put("slateblue", new Color(0x6a5acd));
498        colors.put("slategray", new Color(0x708090));
499        colors.put("snow", new Color(0xfffafa));
500        colors.put("springgreen", new Color(0x00ff7f));
501        colors.put("steelblue", new Color(0x4682b4));
502        colors.put("tan", new Color(0xd2b48c));
503        colors.put("teal", new Color(0x008080));
504        colors.put("thistle", new Color(0xd8bfd8));
505        colors.put("tomato", new Color(0xff6347));
506        colors.put("turquoise", new Color(0x40e0d0));
507        colors.put("violet", new Color(0xee82ee));
508        colors.put("wheat", new Color(0xf5deb3));
509        colors.put("white", new Color(0xffffff));
510        colors.put("whitesmoke", new Color(0xf5f5f5));
511        colors.put("yellow", new Color(0xffff00));
512        colors.put("yellowgreen", new Color(0x9acd32));
513    }
514
515    private static Color getColor(String color) {
516        String origColor = color;
517        color = color.toLowerCase().replaceAll("[^a-z]", "");
518        if (colors.containsKey(color)) {
519            return colors.get(color);
520        } else {
521            return null;
522        }
523    }
524
525    private static void setupBuffering() {
526        synchronized (turtleLock) {
527            lastUpdate = 0;
528            offscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
529            midscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
530            onscreenImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
531            offscreen = offscreenImage.createGraphics();
532            midscreen = midscreenImage.createGraphics();
533            onscreen = onscreenImage.createGraphics();
534            //offscreen.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
535            //offscreen.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
536            offscreen.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
537            midscreen.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
538            onscreen.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
539            drawBackground(offscreen);
540            drawBackground(onscreen);
541            icon.setImage(onscreenImage);
542        }
543    }
544
545    private static void drawTurtleIcon() {
546        byte[] imageData = new byte[]{71, 73, 70, 56, 57, 97, 16, 0, 16, 0, -95, 2, 0, 0, -103,
547                0, 0, -1, 0, -1, -1, -1, -1, -1, -1, 33, -7, 4, 1, 10, 0, 2, 0, 44, 0, 0, 0, 0, 16, 0, 16, 0,
548                0, 2, 44, -108, -113, -87, -53, -19, -33, -128, 4, 104, 74, 35, 67, -72, 34, -21, 11,
549                124, 27, -90, -107, -109, 72, 117, -91, -71, 110, 103, -37, 90, -31, -10, -55, -87,
550                122, -34, 74, 72, -15, 17, -56, -127, 8, 33, 5, 0, 59};
551        try {
552            BufferedImage tmpicon = ImageIO.read(new ByteArrayInputStream(imageData));
553            window.setIconImage(tmpicon);
554        } catch (Exception e) {
555        }
556    }
557
558    private static void makeShapes() {
559        shapes = new HashMap<String, Polygon>();
560        int[] xs = new int[]{66, 65, 63, 61, 53, 44, 33, 24, 23, 19, 17, 14, 9, 8, 8, 10, 13, 11, 10, 2, 9, 11, 15, 11, 11, 10, 12, 18, 20, 22, 23, 26, 35, 44, 53, 61, 62, 64, 66, 71, 77, 78, 77, 76, 72, 77, 81, 86, 91, 94, 97, 98, 97, 95, 92, 87, 82, 77, 72, 74, 77, 78, 76, 70};
561        int[] ys = new int[]{18, 19, 21, 25, 23, 22, 23, 27, 25, 21, 20, 21, 27, 30, 32, 34, 37, 42, 47, 50, 53, 59, 65, 68, 69, 71, 74, 79, 80, 80, 78, 74, 77, 78, 77, 75, 79, 81, 82, 81, 76, 73, 71, 69, 66, 59, 60, 61, 60, 58, 54, 50, 46, 42, 40, 39, 40, 41, 34, 32, 28, 27, 24, 19};
562        Polygon p = new Polygon(xs, ys, xs.length);
563        shapes.put("turtle", p);
564        xs = new int[]{0, 100, 0, 20};
565        ys = new int[]{0, 50, 100, 50};
566        p = new Polygon(xs, ys, xs.length);
567        shapes.put("arrow", p);
568        xs = new int[]{0, 100, 100, 0};
569        ys = new int[]{0, 0, 100, 100};
570        p = new Polygon(xs, ys, xs.length);
571        shapes.put("rectangle", p);
572        shapes.put("square", p);
573        xs = new int[]{0, 100, 0};
574        ys = new int[]{0, 50, 100};
575        p = new Polygon(xs, ys, xs.length);
576        shapes.put("triangle", p);
577        int divisions = 24;
578        xs = new int[divisions];
579        ys = new int[divisions];
580        for (int i = 0; i < divisions; i++) {
581            double angle = Math.toRadians(i * 360.0 / divisions);
582            xs[i] = (int) Math.round(50 + 50 * Math.cos(angle));
583            ys[i] = (int) Math.round(50 + 50 * Math.sin(angle));
584        }
585        p = new Polygon(xs, ys, xs.length);
586        shapes.put("circle", p);
587    }
588
589    /*    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
590     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
591     *    U U      U U      U U      U U      U U      U U      U U      U U
592     *    .-./*)   .-./*)   .-./*)                     .-./*)   .-./*)   .-./*)
593     *  _/___\/  _/___\/  _/___\/ TURTLE CONSTRUCTION_/___\/  _/___\/  _/___\/
594     *    U U      U U      U U                        U U      U U      U U
595     *    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
596     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
597     *    U U      U U      U U      U U      U U      U U      U U      U U
598     */
599
600    /**
601     * This is a internal constuctor that makes a singleton that does the
602     * listening but is not added to the stack of turtles to be rendered.
603     * You don't need to use this outside of the Turtle.java file.
604     *
605     * @param i Pass this any integer.  It doesn't do anything.
606     */
607    private Turtle(int i) {
608    }
609
610    private Point2D.Double location = new Point2D.Double(0, 0);
611    private double direction = 0; //degrees
612    private String shape = "turtle"; //Stores a key to the shapes hashmap
613    private BufferedImage image = null;
614    private double shapeWidth = 33;
615    private double shapeHeight = 33;
616    private double tilt = 0;
617    private double penWidth = 2;
618    private Color penColor = Color.BLACK;
619    private double outlineWidth = 2;
620    private Color outlineColor = Color.BLACK;
621    private Color fillColor = new Color(0, 255, 0, 128);
622    private double speed = 50; //milliseconds to execute a move
623    private boolean isPenDown = true;
624    private boolean isFilling = false;
625    private boolean isVisible = true;
626    private ArrayList<Point2D.Double> polygon = new ArrayList<Point2D.Double>();
627    //temporary storage
628    private Long _time;
629    private Point2D.Double _location;
630    private Double _direction;
631    private String _shape;
632    private BufferedImage _image;
633    private Double _shapeWidth;
634    private Double _shapeHeight;
635    private Double _tilt;
636    private Double _penWidth;
637    private Color _penColor;
638    private Double _outlineWidth;
639    private Color _outlineColor;
640    private Color _fillColor;
641    private Double _speed;
642    private Boolean _isPenDown;
643    private Boolean _isFilling;
644    private Boolean _isVisible;
645    private ArrayList<Point2D.Double> _polygon;
646    private Boolean _isFill;
647    private Boolean _isStamp;
648    private Double _dotSize;
649    private Color _dotColor;
650    private Font _font;
651    private String _text;
652    private Integer _justification;
653    private Point2D.Double _textOffset;
654
655    //secondary temporary storage
656    private Long __time;
657    private Point2D.Double __location;
658    private Double __direction;
659    private String __shape;
660    private BufferedImage __image;
661    private Double __shapeWidth;
662    private Double __shapeHeight;
663    private Double __tilt;
664    private Double __penWidth;
665    private Color __penColor;
666    private Double __outlineWidth;
667    private Color __outlineColor;
668    private Color __fillColor;
669    private Double __speed;
670    private Boolean __isPenDown;
671    private Boolean __isFilling;
672    private Boolean __isVisible;
673    private ArrayList<Point2D.Double> __polygon;
674    private Boolean __isFill;
675    private Boolean __isStamp;
676    private Double __dotSize;
677    private Color __dotColor;
678    private Font __font;
679    private String __text;
680    private Integer __justification;
681    private Point2D.Double __textOffset;
682
683    /**
684     * Makes a turtle at the center of the canvas at location (0, 0).
685     * <pre>
686     * Turtle t = new Turtle();
687     * </pre>
688     */
689    public Turtle() {
690        if (window == null) init();
691        synchronized (turtleLock) {
692            turtles.add(this);
693        }
694        long time = storeCurrentState();
695        updateAll();
696    }
697
698    /**
699     * Makes a turtle at the specified position.
700     *
701     * @param x x coordinate
702     * @param y y coordinate
703     */
704    public Turtle(double x, double y) {
705        if (window == null) init();
706        location = new Point2D.Double(x, y);
707        synchronized (turtleLock) {
708            turtles.add(this);
709        }
710        long time = storeCurrentState();
711        updateAll();
712    }
713
714    /**
715     * This creates a cloned copy of a turtle.
716     *
717     * @return a cloned copy of a turtle
718     */
719    public Turtle clone() {
720        Turtle t = new Turtle(0);
721        t.location = (Point2D.Double) this.location.clone();
722        t.direction = this.direction;
723        t.shape = t.shape;
724        t.image = this.image;
725        t.shapeWidth = this.shapeWidth;
726        t.shapeHeight = this.shapeHeight;
727        t.tilt = this.tilt;
728        t.penWidth = this.penWidth;
729        t.penColor = this.penColor;
730        t.outlineWidth = this.outlineWidth;
731        t.outlineColor = this.outlineColor;
732        t.fillColor = this.fillColor;
733        t.speed = this.speed;
734        t.isPenDown = this.isPenDown;
735        t.isFilling = this.isFilling;
736        t.isVisible = this.isVisible;
737        if (window == null) init();
738        synchronized (turtleLock) {
739            turtles.add(t);
740        }
741        long time = t.storeCurrentState();
742        return t;
743    }
744
745    /*    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
746     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
747     *    U U      U U      U U      U U      U U      U U      U U      U U
748     *    .-./*)   .-./*)   .-./*)                     .-./*)   .-./*)   .-./*)
749     *  _/___\/  _/___\/  _/___\/  STATE MANAGEMENT  _/___\/  _/___\/  _/___\/
750     *    U U      U U      U U                        U U      U U      U U
751     *    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
752     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
753     *    U U      U U      U U      U U      U U      U U      U U      U U
754     */
755
756    private long storeCurrentState() {
757        return storeCurrentState(false, false, 0, null, null, null, 0, null);
758    }
759
760    private long storeAnimatedState() {
761        return storeCurrentState(true, false, 0, null, null, null, 0, null);
762    }
763
764    private long storeCurrentState(boolean animate, boolean isStamp, double dotSize, Color dotColor, Font font, String text, int justification, Point2D.Double textOffset) {
765        ArrayList state = new ArrayList();
766        long time = System.nanoTime();
767        synchronized (turtleLock) {
768            state.add(0);            //0
769            state.add(this);            //1
770            state.add(location.clone());//2
771            state.add(direction);       //3
772            state.add(shape);           //4
773            state.add(image);           //5
774            state.add(shapeWidth);      //6
775            state.add(shapeHeight);     //7
776            state.add(tilt);            //8
777            state.add(penWidth);        //9
778            state.add(penColor);        //10
779            state.add(outlineWidth);    //11
780            state.add(outlineColor);    //12
781            state.add(fillColor);       //13
782            state.add(speed);           //14
783            state.add(isPenDown);       //15
784            state.add(isFilling);       //16
785            state.add(isVisible);       //17
786            state.add(isStamp);         //18
787            state.add(dotSize);         //19
788            state.add(dotColor);        //20
789            state.add(font);            //21
790            state.add(text);            //22
791            state.add(justification);   //23
792            state.add(textOffset);      //24
793            if (!turtleStates.isEmpty() && turtleStates.lastKey() > time) time = turtleStates.lastKey() + 1;
794            if (animate) time += (long) (speed * 1000000);
795            state.set(0, time);
796            turtleStates.put(time, state);
797            redoStates.clear();
798        }
799        if (refreshMode == REFRESH_MODE_STATE_CHANGE) draw();
800        if (refreshMode == REFRESH_MODE_ANIMATED) waitUntil(time);
801        return time;
802    }
803
804    private static void clearStorage() {
805        synchronized (turtleLock) {
806            for (Turtle t : turtles) {
807                t.__time = null;
808                t.__location = null;
809                t.__direction = null;
810                t.__shape = null;
811                t.__image = null;
812                t.__shapeWidth = null;
813                t.__shapeHeight = null;
814                t.__tilt = null;
815                t.__penWidth = null;
816                t.__penColor = null;
817                t.__outlineWidth = null;
818                t.__outlineColor = null;
819                t.__fillColor = null;
820                t.__speed = null;
821                t.__isPenDown = null;
822                t.__isFilling = null;
823                t.__isVisible = null;
824                t.__isStamp = null;
825                t.__dotSize = null;
826                t.__dotColor = null;
827                t.__font = null;
828                t.__text = null;
829                t.__justification = null;
830                t.__textOffset = null;
831                t._time = null;
832                t._location = null;
833                t._direction = null;
834                t._shape = null;
835                t._shapeWidth = null;
836                t._shapeHeight = null;
837                t._image = null;
838                t._tilt = null;
839                t._penWidth = null;
840                t._penColor = null;
841                t._outlineWidth = null;
842                t._outlineColor = null;
843                t._fillColor = null;
844                t._speed = null;
845                t._isPenDown = null;
846                t._isFilling = null;
847                t._isVisible = null;
848                t._isStamp = null;
849                t._dotSize = null;
850                t._dotColor = null;
851                t._font = null;
852                t._text = null;
853                t._justification = null;
854                t._textOffset = null;
855            }
856        }
857    }
858
859    private static void retrieveState(long time) {
860        synchronized (turtleLock) {
861            Turtle t = getStateTurtle(turtleStates.get(time));
862            t.__time = t._time;
863            t.__location = t._location;
864            t.__direction = t._direction;
865            t.__shape = t._shape;
866            t.__image = t._image;
867            t.__shapeWidth = t._shapeWidth;
868            t.__shapeHeight = t._shapeHeight;
869            t.__tilt = t._tilt;
870            t.__penWidth = t._penWidth;
871            t.__penColor = t._penColor;
872            t.__outlineWidth = t._outlineWidth;
873            t.__outlineColor = t._outlineColor;
874            t.__fillColor = t._fillColor;
875            t.__speed = t._speed;
876            t.__isPenDown = t._isPenDown;
877            t.__isFilling = t._isFilling;
878            t.__isVisible = t._isVisible;
879            t.__isStamp = t._isStamp;
880            t.__dotSize = t._dotSize;
881            t.__dotColor = t._dotColor;
882            t.__font = t._font;
883            t.__text = t._text;
884            t.__justification = t._justification;
885            t.__textOffset = t._textOffset;
886            ArrayList state = turtleStates.get(time);
887            t._time = getStateTime(state);
888            t._location = getStateLocation(state);
889            t._direction = getStateDirection(state);
890            t._shape = getStateShape(state);
891            t._shapeWidth = getStateShapeWidth(state);
892            t._shapeHeight = getStateShapeHeight(state);
893            t._image = getStateImage(state);
894            t._tilt = getStateTilt(state);
895            t._penWidth = getStatePenWidth(state);
896            t._penColor = getStatePenColor(state);
897            t._outlineWidth = getStateOutlineWidth(state);
898            t._outlineColor = getStateOutlineColor(state);
899            t._fillColor = getStateFillColor(state);
900            t._speed = getStateSpeed(state);
901            t._isPenDown = getStateIsPenDown(state);
902            t._isFilling = getStateIsFilling(state);
903            t._isVisible = getStateIsVisible(state);
904            t._isStamp = getStateIsStamp(state);
905            t._dotSize = getStateDotSize(state);
906            t._dotColor = getStateDotColor(state);
907            t._font = getStateFont(state);
908            t._text = getStateText(state);
909            t._justification = getStateJustification(state);
910            t._textOffset = getStateTextOffset(state);
911        }
912    }
913
914    private static long getStateTime(ArrayList state) {
915        return (Long) state.get(0);
916    }
917
918    private static Turtle getStateTurtle(ArrayList state) {
919        return (Turtle) state.get(1);
920    }
921
922    private static Point2D.Double getStateLocation(ArrayList state) {
923        return (Point2D.Double) ((Point2D.Double) state.get(2)).clone();
924    }
925
926    private static double getStateDirection(ArrayList state) {
927        return (Double) state.get(3);
928    }
929
930    private static String getStateShape(ArrayList state) {
931        return (String) state.get(4);
932    }
933
934    private static BufferedImage getStateImage(ArrayList state) {
935        return (BufferedImage) state.get(5);
936    }
937
938    private static double getStateShapeWidth(ArrayList state) {
939        return (Double) state.get(6);
940    }
941
942    private static double getStateShapeHeight(ArrayList state) {
943        return (Double) state.get(7);
944    }
945
946    private static double getStateTilt(ArrayList state) {
947        return (Double) state.get(8);
948    }
949
950    private static double getStatePenWidth(ArrayList state) {
951        return (Double) state.get(9);
952    }
953
954    private static Color getStatePenColor(ArrayList state) {
955        return (Color) state.get(10);
956    }
957
958    private static double getStateOutlineWidth(ArrayList state) {
959        return (Double) state.get(11);
960    }
961
962    private static Color getStateOutlineColor(ArrayList state) {
963        return (Color) state.get(12);
964    }
965
966    private static Color getStateFillColor(ArrayList state) {
967        return (Color) state.get(13);
968    }
969
970    private static double getStateSpeed(ArrayList state) {
971        return (Double) state.get(14);
972    }
973
974    private static boolean getStateIsPenDown(ArrayList state) {
975        return (Boolean) state.get(15);
976    }
977
978    private static boolean getStateIsFilling(ArrayList state) {
979        return (Boolean) state.get(16);
980    }
981
982    private static boolean getStateIsVisible(ArrayList state) {
983        return (Boolean) state.get(17);
984    }
985
986    private static boolean getStateIsStamp(ArrayList state) {
987        return (Boolean) state.get(18);
988    }
989
990    private static double getStateDotSize(ArrayList state) {
991        return (Double) state.get(19);
992    }
993
994    private static Color getStateDotColor(ArrayList state) {
995        return (Color) state.get(20);
996    }
997
998    private static Font getStateFont(ArrayList state) {
999        return (Font) state.get(21);
1000    }
1001
1002    private static String getStateText(ArrayList state) {
1003        return (String) state.get(22);
1004    }
1005
1006    private static int getStateJustification(ArrayList state) {
1007        return (Integer) state.get(23);
1008    }
1009
1010    private static Point2D.Double getStateTextOffset(ArrayList state) {
1011        return (Point2D.Double) state.get(24);
1012    }
1013
1014    private static void restoreState(long time) {
1015        ArrayList state = turtleStates.get(time);
1016        Turtle t = getStateTurtle(turtleStates.get(time));
1017        t.location = getStateLocation(state);
1018        t.direction = getStateDirection(state);
1019        t.shape = getStateShape(state);
1020        t.shapeWidth = getStateShapeWidth(state);
1021        t.shapeHeight = getStateShapeHeight(state);
1022        t.image = getStateImage(state);
1023        t.tilt = getStateTilt(state);
1024        t.penWidth = getStatePenWidth(state);
1025        t.penColor = getStatePenColor(state);
1026        t.outlineWidth = getStateOutlineWidth(state);
1027        t.outlineColor = getStateOutlineColor(state);
1028        t.fillColor = getStateFillColor(state);
1029        t.speed = getStateSpeed(state);
1030        t.isPenDown = getStateIsPenDown(state);
1031        t.isFilling = getStateIsFilling(state);
1032        t.isVisible = getStateIsVisible(state);
1033        if (refreshMode == REFRESH_MODE_STATE_CHANGE) draw();
1034    }
1035
1036    private void select() {
1037        selectedTurtle = this;
1038    }
1039
1040    private void unselect() {
1041        if (selectedTurtle == this) selectedTurtle = null;
1042    }
1043
1044    private void output(String message) {
1045        out.println("Instruction: " + message);
1046        out.printf("Current Pos: (%.3f, %.3f)\n", location.getX(), location.getY());
1047    }
1048
1049    /**
1050     * Determines if a turtle is covering a screen position
1051     *
1052     * @param x x screen coordinate
1053     * @param y y screen coordinate
1054     * @return true if this turtle is at the indicated screen position.
1055     */
1056    public boolean contains(int x, int y) {
1057        Point2D.Double point = new Point2D.Double(x, y);
1058        if (_location == null) return false;
1059        AffineTransform m = offscreen.getTransform();
1060        double x1, y1, dir1;
1061        x1 = _location.x;
1062        y1 = _location.y;
1063        dir1 = _direction;
1064        m.translate(((x1 - centerX) * scale + width / 2), ((y1 - centerY) * (-scale) + height / 2));
1065        m.scale(scale, scale);
1066        if (image == null) {
1067            m.rotate(-Math.toRadians(dir1));
1068            m.scale(shapeWidth / 100.0, shapeHeight / 100.0);
1069            m.translate(-50, -50);
1070            Polygon p = shapes.get(shape);
1071            GeneralPath gp = new GeneralPath();
1072            gp.append(p.getPathIterator(m), false);
1073            return gp.contains(x, y);
1074        } else {
1075            int w = image.getWidth();
1076            int h = image.getHeight();
1077            m.rotate(-Math.toRadians(dir1));
1078            m.scale(shapeWidth / 1.0 / w, shapeHeight / 1.0 / h);
1079            m.translate(-w / 2, -h / 2);
1080            try {
1081                m.inverseTransform(point, point);
1082            } catch (Exception e) {
1083                return false;
1084            }
1085            x = (int) point.x;
1086            y = (int) point.y;
1087            try {
1088                //System.out.println((new Color(image.getRGB(x, y),true)).getAlpha());
1089                return (new Color(image.getRGB(x, y), true)).getAlpha() > 50;
1090            } catch (Exception e) {
1091                return false;
1092            }
1093        }
1094    }
1095
1096    /*    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
1097     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
1098     *    U U      U U      U U      U U      U U      U U      U U      U U
1099     *    .-./*)   .-./*)   .-./*)                     .-./*)   .-./*)   .-./*)
1100     *  _/___\/  _/___\/  _/___\/   TURTLE METHODS   _/___\/  _/___\/  _/___\/
1101     *    U U      U U      U U                        U U      U U      U U
1102     *    .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)   .-./*)
1103     *  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/  _/___\/
1104     *    U U      U U      U U      U U      U U      U U      U U      U U
1105     */
1106
1107    /**
1108     * Gets the speed of the animation.
1109     * @return milliseconds it takes to do one action
1110     */
1111    public double getSpeed() {
1112        return speed;
1113    }
1114
1115    /**
1116     * Sets the speed of the animation.
1117     *
1118     * <pre>
1119     * Turtle t = new Turtle();
1120     * t.speed(1000);
1121     * t.forward(100); // takes 1000 ms (1 second) to move forward 100 units
1122     * t.forward(5); // takes 1000 ms (1 second) to move forward 5 units
1123     * t.speed(100);
1124     * t.left(90); // takes 100 ms (0.1 second) to turn left 90 degrees
1125     * </pre>
1126     *
1127     * @param delay milliseconds it takes to do one action
1128     * @return state change timestamp
1129     */
1130    public long speed(double delay) {
1131        this.speed = delay;
1132        long timeStamp = storeCurrentState();
1133        return timeStamp;
1134    }
1135
1136    /**
1137     * Moves the turtle forward by the given distance. Each unit of distance
1138     * is one pixel at the default zoom level.
1139     * <p>
1140     * The example below constructs a turtle and moves it forward.
1141     * If the canvas is zoomed to the default zoom level, then the
1142     * distance moved is 50 pixels. 
1143     * </p>
1144     * <pre>
1145     * Turtle t = new Turtle();
1146     * t.forward(50);
1147     * </pre>
1148     *
1149     * @param distance number of units to move forward
1150     * @return state change timestamp
1151     */
1152    public long forward(double distance) {
1153        double angle = Math.toRadians(direction);
1154        Point2D.Double pastLocation = (Point2D.Double) location.clone();
1155        location.x += distance * Math.cos(angle);
1156        location.y += distance * Math.sin(angle);
1157        long timeStamp = storeAnimatedState();
1158
1159        output("MOVE FORWARD " + distance);
1160        return timeStamp;
1161    }
1162
1163    /**
1164     * Moves the turtle backward by the given distance. Each unit of distance is one
1165     * pixel at the default zoom level.
1166     *
1167     * @param distance number of units to move forward
1168     * @return state change timestamp
1169     */
1170    public long backward(double distance) {
1171        double angle = Math.toRadians(direction);
1172        Point2D.Double pastLocation = (Point2D.Double) location.clone();
1173        location.x -= distance * Math.cos(angle);
1174        location.y -= distance * Math.sin(angle);
1175        long timeStamp = storeAnimatedState();
1176
1177        output("MOVE BACKWARD " + distance);
1178        return timeStamp;
1179    }
1180
1181    /**
1182     * Turns the turtle left by the number of indicated degrees.
1183     *
1184     * @param angle angle in degrees
1185     * @return state change timestamp
1186     */
1187    public long left(double angle) {
1188        direction += angle;
1189        long timeStamp = storeAnimatedState();
1190
1191        output("TURN LEFT " + angle);
1192        return timeStamp;
1193    }
1194
1195    /**
1196     * Turns the turtle right by the number of indicated degrees.
1197     *
1198     * @param angle angle in degrees
1199     * @return state change timestamp
1200     */
1201    public long right(double angle) {
1202        direction -= angle;
1203        long timeStamp = storeAnimatedState();
1204
1205        output("TURN RIGHT " + angle);
1206        return timeStamp;
1207    }
1208
1209    /**
1210     * Gets the direction the turtle is facing neglecting tilt.
1211     *
1212     * @return state change timestamp
1213     */
1214    public double getDirection() {
1215        double a = direction;
1216        while (a >= 360) a -= 360;
1217        while (a < 0) a += 360;
1218        return a;
1219    }
1220
1221    /**
1222     * Sets the direction the turtle is facing neglecting tilt.
1223     *
1224     * @param direction angle counter-clockwise from east
1225     * @return state change timestamp
1226     */
1227    public long setDirection(double direction) {
1228        double a = direction;
1229        while (this.direction - a > 180) a += 360;
1230        while (this.direction - a < -180) a -= 360;
1231        this.direction = a;
1232        //this.direction=direction;
1233        long timeStamp = storeAnimatedState();
1234
1235        output("SET DIRECTION " + a);
1236        return timeStamp;
1237    }
1238
1239    /**
1240     * Moves the turtle to (0,0) and facing east.
1241     *
1242     * @return state change timestamp
1243     */
1244    public long home() {
1245        output("MOVE HOME");
1246        return setPosition(0, 0, 0);
1247    }
1248
1249    /**
1250     * Hides the turtle but it can still draw.
1251     *
1252     * @return state change timestamp
1253     */
1254    public long hide() {
1255        isVisible = false;
1256        output("HIDING TURTLE");
1257        long timeStamp = storeCurrentState();
1258        return timeStamp;
1259    }
1260
1261    /**
1262     * Makes the turtle visible.
1263     *
1264     * @return state change timestamp
1265     */
1266    public long show() {
1267        isVisible = true;
1268        output("SHOWING TURTLE");
1269        long timeStamp = storeCurrentState();
1270        return timeStamp;
1271    }
1272
1273    /**
1274     * Sets the direction in such a way that it faces (x,y)
1275     *
1276     * @param x x coordinate of target location
1277     * @param y y coordinate of target location
1278     * @return state change timestamp
1279     */
1280    public long face(double x, double y) {
1281        return setDirection(towards(x, y));
1282    }
1283
1284    /**
1285     * Gets direction towards (x,y)
1286     *
1287     * @param x x coordinate of target location
1288     * @param y y coordinate of target location
1289     * @return angle in degrees where 0 &lt;= angle &lt;
1290     */
1291    public double towards(double x, double y) {
1292        return Math.toDegrees(Math.atan2(y - location.y, x - location.x));
1293    }
1294
1295    /**
1296     * Gets the distance to another position.
1297     *
1298     * @param x x coordinate of target location
1299     * @param y y coordinate of target location
1300     * @return distance between turtle's current location and another position
1301     */
1302    public double distance(double x, double y) {
1303        return Math.sqrt((y - location.y) * (y - location.y) + (x - location.x) * (x - location.x));
1304    }
1305
1306    /**
1307     * Gets the x coordinate of the turtle.
1308     *
1309     * @return x coordinate
1310     */
1311    public double getX() {
1312        return location.x;
1313    }
1314
1315    /**
1316     * Gets the y coordinate of the turtle.
1317     *
1318     * @return y coordinate
1319     */
1320    public double getY() {
1321        return location.y;
1322    }
1323
1324    /**
1325     * Sets the position and direction of a turtle.
1326     *
1327     * @param x x coordinate
1328     * @param y y coordinate
1329     * @param direction angle counter-clockwise from east in degrees
1330     * @return state change timestamp
1331     */
1332    public long setPosition(double x, double y, double direction) {
1333        location.x = x;
1334        location.y = y;
1335        double a = direction;
1336        while (this.direction - a > 180) a += 360;
1337        while (this.direction - a < -180) a -= 360;
1338        this.direction = a;
1339        this.direction = direction;
1340
1341        output(String.format("SET POSITION (%f, %f) DIRECTION %f", x, y, direction));
1342        long timeStamp = storeAnimatedState();
1343        return timeStamp;
1344    }
1345
1346    /**
1347     * Sets the position of a turtle.
1348     *
1349     * @param x x coordinate
1350     * @param y y coordinate
1351     * @return state change timestamp
1352     */
1353    public long setPosition(double x, double y) {
1354        return setPosition(x, y, direction);
1355    }
1356
1357    /**
1358     * Adds an additional angle to rotation of the turtle's shape when rendering.
1359     * This is useful when you need to face a different direction than the
1360     * direction you are moving in.
1361     *
1362     * @param angle angle in degrees
1363     * @return state change timestamp
1364     */
1365    public long tilt(double angle) {
1366        tilt += angle;
1367
1368        output("ADD TILT " + angle);
1369
1370        long timeStamp = storeAnimatedState();
1371        return timeStamp;
1372    }
1373
1374    /**
1375     * Sets the angle to rotate the turtle's shape when rendering.
1376     * This is useful when you need to face a different direction than the
1377     * direction you are moving in.
1378     *
1379     * @param angle angle in degrees
1380     * @return state change timestamp
1381     */
1382    public long setTilt(double angle) {
1383        //double a=angle;
1384        //while(tilt-a>180)a+=360;
1385        //while(tilt-a<-180)a-=360;
1386        //tilt=a;
1387        tilt = angle;
1388
1389        output("SET TILT " + angle);
1390
1391        long timeStamp = storeAnimatedState();
1392        return timeStamp;
1393    }
1394
1395    /**
1396     * Gets the rotation of the turtle's shape away from the turtle's direction.
1397     *
1398     * @return tilt in degrees (positive in counter-clockwise)
1399     */
1400    public double getTilt() {
1401        return tilt;
1402    }
1403
1404    /**
1405     * Sets the width of the turtle's pen. Each unit of width corresponds to
1406     * 1 pixel at the default zoom level.
1407     *
1408     *
1409     * @param penWidth number of units wide
1410     * @return state change timestamp
1411     */
1412    public long width(double penWidth) {
1413        this.penWidth = penWidth;
1414
1415        output("SET PEN WIDTH " + penWidth);
1416        long timeStamp = storeCurrentState();
1417        return timeStamp;
1418    }
1419
1420    /**
1421     * Sets the width of the turtle's outline.
1422     *
1423     * @param width width of the turtle's outline
1424     * @return state change timestamp
1425     */
1426    public long outlineWidth(double width) {
1427        this.outlineWidth = width;
1428
1429        output("SET OUTLINE WIDTH " + width);
1430        long timeStamp = storeCurrentState();
1431        return timeStamp;
1432    }
1433
1434    /**
1435     * Picks the turtle's pen up so it won't draw on the screen as it moves.
1436     *
1437     * @return state change timestamp
1438     */
1439    public long up() {
1440        this.isPenDown = false;
1441        output("LIFT PEN");
1442        long timeStamp = storeCurrentState();
1443        return timeStamp;
1444    }
1445
1446    /**
1447     * Puts the turtle's pen down so it will draw on the screen as it moves.
1448     *
1449     * @return state change timestamp
1450     */
1451    public long down() {
1452        this.isPenDown = true;
1453        output("LOWER PEN");
1454        long timeStamp = storeCurrentState();
1455        return timeStamp;
1456    }
1457
1458    public long stab() {
1459        Color c = Turtle.getColor("red");
1460        if (c != null) this.penColor = c;
1461        this.isPenDown = true;
1462
1463        output("STAB");
1464        long timeStamp = storeCurrentState();
1465        return timeStamp;
1466    }
1467
1468    /**
1469     * Sets the turtle's path color.
1470     *
1471     * @param penColor Color of the turtle's path.
1472     * @return state change timestamp
1473     */
1474    public long penColor(String penColor) {
1475        Color c = Turtle.getColor(penColor);
1476        if (c != null) this.penColor = c;
1477        output("SET PEN COLOR " + c);
1478        long timeStamp = storeCurrentState();
1479        return timeStamp;
1480    }
1481
1482    /**
1483     * Sets the turtle's path color.
1484     *
1485     * @param penColor Color of the turtle's path.
1486     * @return state change timestamp
1487     */
1488    public long penColor(Color penColor) {
1489        this.penColor = penColor;
1490
1491        output("SET PEN COLOR " + penColor);
1492        long timeStamp = storeCurrentState();
1493        return timeStamp;
1494    }
1495
1496    /**
1497     * Sets the turtle's outlineColor color.
1498     *
1499     * @param outlineColor Color of the turtle's outlineColor.
1500     * @return state change timestamp
1501     */
1502    public long outlineColor(String outlineColor) {
1503        Color c = Turtle.getColor(outlineColor);
1504        if (c != null) this.outlineColor = c;
1505
1506        output("SET OUTLINE COLOR " + c);
1507        long timeStamp = storeCurrentState();
1508        return timeStamp;
1509    }
1510
1511    /**
1512     * Sets the turtle's outlineColor color.
1513     *
1514     * @param outlineColor Color of the turtle's outlineColor.
1515     * @return state change timestamp
1516     */
1517    public long outlineColor(Color outlineColor) {
1518        this.outlineColor = outlineColor;
1519        output("SET OUTLINE COLOR " + outlineColor);
1520        long timeStamp = storeCurrentState();
1521        return timeStamp;
1522    }
1523
1524    /**
1525     * Sets the turtle's fill color.
1526     *
1527     * @param fillColor Color of the turtle's fill.
1528     * @return state change timestamp
1529     */
1530    public long fillColor(String fillColor) {
1531        Color c = Turtle.getColor(fillColor);
1532        if (c != null) this.fillColor = c;
1533
1534        output("SET FILL COLOR " + c);
1535        long timeStamp = storeCurrentState();
1536        return timeStamp;
1537    }
1538
1539    /**
1540     * Sets the turtle's fill color.
1541     *
1542     * @param fillColor Color of the turtle's fill.
1543     * @return state change timestamp
1544     */
1545    public long fillColor(Color fillColor) {
1546        this.fillColor = fillColor;
1547
1548        output("SET FILL COLOR " + fillColor);
1549        long timeStamp = storeCurrentState();
1550        return timeStamp;
1551    }
1552
1553    /**
1554     * Sets the shape of the turtle using the built in shapes (turtle,square,
1555     * rectangle,triangle,arrow,circle) or to a image.
1556     *
1557     * @param shape shapename or filename of image
1558     * @return state change timestamp
1559     */
1560    public long shape(String shape) {
1561        try {
1562            image = ImageIO.read(new File(shape));
1563            output("SET SHAPE " + shape);
1564            this.shapeHeight = image.getHeight();
1565            this.shapeWidth = image.getWidth();
1566        } catch (Exception e) {
1567            if (shapes.containsKey(shape)) {
1568                output("SET SHAPE " + shape);
1569                this.shape = shape;
1570                this.shapeHeight = 33;
1571                this.shapeWidth = 33;
1572                image = null;
1573            } else {
1574                System.out.println("Unrecognized filename or shape name.");
1575            }
1576        }
1577        //if(refreshMode!=REFRESH_MODE_ON_DEMAND)updateAll();
1578        long timeStamp = storeCurrentState();
1579        return timeStamp;
1580    }
1581
1582    public long shapeSize(int width, int height) {
1583        this.shapeHeight = height;
1584        this.shapeWidth = width;
1585        output(String.format("SET SHAPE SIZE (width=%d, height=%d)", width, height));
1586        long timeStamp = storeCurrentState();
1587        return timeStamp;
1588    }
1589
1590    /**
1591     * Put a copy of the current turtle shape on the canvas.
1592     *
1593     * @return state change timestamp
1594     */
1595    public long stamp() {
1596        long timeStamp = storeCurrentState(true, true, 0, null, null, null, 0, null);
1597        output("LEAVING STAMP");
1598        return timeStamp;
1599    }
1600
1601    /**
1602     * Put a dot 3 times the size of the penWidth on the canvas.
1603     *
1604     * @return state change timestamp
1605     */
1606    public long dot() {
1607        long timeStamp = storeCurrentState(true, false, penWidth * 3, penColor, null, null, 0, null);
1608        output("LEAVING DOT");
1609        return timeStamp;
1610    }
1611
1612    /**
1613     * Put a dot 3 times the size of the penWidth on the canvas.
1614     *
1615     * @param color color of dot
1616     * @return state change timestamp
1617     */
1618    public long dot(String color) {
1619        Color c = Turtle.getColor(color);
1620        if (c == null) c = penColor;
1621        output("LEAVING DOT WITH COLOR " + c);
1622        long timeStamp = storeCurrentState(true, false, penWidth * 3, c, null, null, 0, null);
1623        return timeStamp;
1624    }
1625
1626    /**
1627     * Put a dot 3 times the size of the penWidth on the canvas.
1628     *
1629     * @param color color of dot
1630     * @return state change timestamp
1631     */
1632    public long dot(Color color) {
1633        output("LEAVING DOT WITH COLOR " + color);
1634        long timeStamp = storeCurrentState(true, false, penWidth * 3, color, null, null, 0, null);
1635        return timeStamp;
1636    }
1637
1638    /**
1639     * Put a dot on the canvas.
1640     *
1641     * @param color color of dot
1642     * @param dotSize diameter of the dot
1643     * @return state change timestamp
1644     */
1645    public long dot(String color, double dotSize) {
1646        Color c = Turtle.getColor(color);
1647        if (c == null) c = penColor;
1648
1649        output("LEAVING DOT WITH COLOR " + c + " DOT SIZE " + dotSize);
1650        long timeStamp = storeCurrentState(true, false, dotSize, c, null, null, 0, null);
1651        return timeStamp;
1652    }
1653
1654    /**
1655     * Put a dot on the canvas.
1656     *
1657     * @param color color of dot
1658     * @param dotSize diameter of the dot
1659     * @return state change timestamp
1660     */
1661    public long dot(Color color, double dotSize) {
1662        output("LEAVING DOT WITH COLOR " + color + " DOT SIZE " + dotSize);
1663        long timeStamp = storeCurrentState(true, false, dotSize, color, null, null, 0, null);
1664        return timeStamp;
1665    }
1666
1667    public long write(String text, String fontName, int fontSize, int justification, double xOffset, double yOffset) {
1668        return 0;
1669    }
1670
1671    /**
1672     * Undo turtle state changes.
1673     *
1674     * @param steps the number of state changes to remove
1675     */
1676    public void undo(int steps) {
1677        for (int i = 0; i < steps; i++) rollback();
1678        lastUpdate = 0;
1679
1680        output("UNDOING LAST " + steps + " STEPS");
1681        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1682    }
1683
1684    /**
1685     * Undo the last turtle state change.
1686     */
1687    public void undo() {
1688        output("UNDO LAST STEP");
1689        rollback();
1690        lastUpdate = 0;
1691        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1692    }
1693
1694    /**
1695     * Redo turtle state changes.
1696     *
1697     * @param steps the number of state changes to restore
1698     */
1699    public void redo(int steps) {
1700        output("REDO " + steps + " STEPS");
1701        for (int i = 0; i < steps; i++) rollforward();
1702        lastUpdate = 0;
1703        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1704    }
1705
1706    /**
1707     * Redo turtle state changes.
1708     */
1709    public void redo() {
1710        output("REDO STEP");
1711        rollforward();
1712        lastUpdate = 0;
1713        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1714    }
1715
1716    /**
1717     * Clears all the drawing that a turtle has done but all the turtle
1718     * settings remain the same. (color, location, direction, shape)
1719     */
1720    public void clear() {
1721        output("CLEAR DRAWING");
1722        synchronized (turtleLock) {
1723            long removeKey = 0;
1724            TreeMap<Long, ArrayList> copy_turtleStates = (TreeMap<Long, ArrayList>) turtleStates.clone();
1725            for (Map.Entry<Long, ArrayList> entry : copy_turtleStates.entrySet()) {
1726                ArrayList state = entry.getValue();
1727                long time = entry.getKey();
1728                if (getStateTurtle(state) == this) {
1729                    if (removeKey != 0) {
1730                        turtleStates.remove(removeKey);
1731                    }
1732                    removeKey = time;
1733
1734                }
1735            }
1736            redoStates.clear();
1737            restoreState(removeKey);
1738        }
1739        lastUpdate = 0;
1740        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1741    }
1742
1743    private void rollback() {
1744        int steps = 0;
1745
1746        synchronized (turtleLock) {
1747            long removeKey = 0;
1748            long restoreTime = 0;
1749            for (Map.Entry<Long, ArrayList> entry : turtleStates.descendingMap().entrySet()) {
1750                ArrayList state = entry.getValue();
1751                long time = entry.getKey();
1752                if (getStateTurtle(state) == this) {
1753                    if (steps == 0) {
1754                        removeKey = time;
1755                        steps += 1;
1756                    } else {
1757                        restoreTime = time;
1758                        break;
1759                    }
1760                }
1761            }
1762            if (removeKey != 0 && restoreTime != 0) {
1763                restoreState(restoreTime);
1764                redoStates.put(removeKey, turtleStates.remove(removeKey));
1765            }
1766        }
1767    }
1768
1769    private void rollforward() {
1770        synchronized (turtleLock) {
1771            for (Map.Entry<Long, ArrayList> entry : redoStates.entrySet()) {
1772                ArrayList state = entry.getValue();
1773                long time = entry.getKey();
1774                if (getStateTurtle(state) == this) {
1775                    turtleStates.put(entry.getKey(), redoStates.remove(entry.getKey()));
1776                    restoreState(time);
1777                    return;
1778                }
1779            }
1780        }
1781    }
1782
1783    /**
1784     * This specifies when the screen gets refreshed.
1785     * 0(default)=Animated (The turtle will slide from one state to another without being jerky.)
1786     * 1=State Change (The turtle will refresh immediately to the last state. Jerky motion.)
1787     * 2=On Demand (The turtle will refresh only when you call update())
1788     *
1789     * @param mode refresh mode
1790     */
1791    public static void refreshMode(int mode) {
1792        refreshMode = mode;
1793        updateAll();
1794    }
1795
1796    /**
1797     * This specifies how the background is drawn.
1798     * 0=The image if present is stretched to fill the screen.
1799     * 1=The image is centered on the middle of the screen and will not scale/pan
1800     * 2=The image is tiled and will not scale/pan
1801     * 3=The image is centered on (0,0) and will scale/pan
1802     * 4(default)=The image is tiled and will scale/pan
1803     *
1804     * @param mode background mode
1805     */
1806    public static void backgroundMode(int mode) {
1807        backgroundMode = mode;
1808        updateAll();
1809    }
1810
1811    /**
1812     * Sets the background color.
1813     *
1814     * @param color Color of the background.
1815     */
1816    public static void bgcolor(String color) {
1817        Color c = Turtle.getColor(color);
1818        if (c != null) backgroundColor = c;
1819        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1820    }
1821
1822    /**
1823     * Sets the background color.
1824     *
1825     * @param color Color of the background.
1826     */
1827    public static void bgcolor(Color color) {
1828        backgroundColor = color;
1829        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1830    }
1831
1832    /**
1833     * Set the background image.
1834     *
1835     * @param filename filename for a background image
1836     */
1837    public static void bgpic(String filename) {
1838        try {
1839            backgroundImage = ImageIO.read(new File(filename));
1840        } catch (Exception e) {
1841            e.printStackTrace();
1842        }
1843        if (refreshMode != REFRESH_MODE_ON_DEMAND) updateAll();
1844    }
1845
1846    private static boolean addMouseBinding(String methodName, Turtle t, boolean append, boolean click, boolean repeat) {
1847        String className = "";
1848        try {
1849            throw new Exception("Who called me?");
1850        } catch (Exception e) {
1851            className = e.getStackTrace()[2].getClassName();
1852        }
1853        try {
1854            boolean works = false;
1855            for (Method m : Class.forName(className).getDeclaredMethods()) {
1856                if (m.getName().equals(methodName)) {
1857                    //System.out.println(m);
1858                    works = true;
1859                    for (Class paramType : m.getParameterTypes()) {
1860                        //System.out.println(paramType.getName());
1861                        if (!paramType.getName().equals("double") && !paramType.getName().equals("java.lang.Double") && !paramType.getName().equals("Turtle")) {
1862                            works = false;
1863                            break;
1864                        }
1865                    }
1866                    if (works) break;
1867                }
1868            }
1869            if (works) {
1870                //System.out.println("Method found!");
1871            } else {
1872                System.out.println("ERROR");
1873                return false;
1874            }
1875        } catch (Exception e) {
1876            System.out.println("Calling Class not found.");
1877            return false;
1878        }
1879        if (!append || !mouseBindings.containsKey(t)) mouseBindings.put(t, new ArrayList<ArrayList>());
1880        ArrayList binding = new ArrayList();
1881        binding.add(t);
1882        binding.add(className);
1883        binding.add(methodName);
1884        binding.add(click);
1885        binding.add(repeat);
1886        mouseBindings.get(t).add(binding);
1887        return true;
1888    }
1889
1890    private boolean addKeyBinding(String methodName, String keyText, boolean append, boolean repeat) {
1891        keyText = keyText.toLowerCase();
1892        String className = "";
1893        try {
1894            throw new Exception("Who called me?");
1895        } catch (Exception e) {
1896            className = e.getStackTrace()[2].getClassName();
1897        }
1898        try {
1899            boolean works = false;
1900            for (Method m : Class.forName(className).getDeclaredMethods()) {
1901                if (m.getName().equals(methodName)) {
1902                    //System.out.println(m);
1903                    works = true;
1904                    for (Class paramType : m.getParameterTypes()) {
1905                        //System.out.println(paramType.getName());
1906                        if (!paramType.getName().equals("java.lang.String") && !paramType.getName().equals("Turtle")) {
1907                            works = false;
1908                            break;
1909                        }
1910                    }
1911                    if (works) break;
1912                }
1913            }
1914            if (works) {
1915                //System.out.println("Method found!");
1916            } else {
1917                System.out.println("ERROR");
1918                return false;
1919            }
1920        } catch (Exception e) {
1921            System.out.println("Calling Class not found.");
1922            return false;
1923        }
1924        if (!append || !keyBindings.containsKey(keyText)) keyBindings.put(keyText, new ArrayList<ArrayList>());
1925        ArrayList binding = new ArrayList();
1926        binding.add(this);
1927        binding.add(className);
1928        binding.add(methodName);
1929        binding.add(repeat);
1930        keyBindings.get(keyText).add(binding);
1931        return true;
1932    }
1933
1934    /**
1935     * Links a method to a key.
1936     *
1937     * @param methodName method to be executed when the key is pressed
1938     * @param keyText key that triggers the method
1939     * @return boolean
1940     */
1941    public boolean onKey(String methodName, String keyText) {
1942        return addKeyBinding(methodName, keyText, false, false);
1943    }
1944
1945    /**
1946     * Links a method to a key.
1947     *
1948     * @param methodName method to be executed when the key is pressed
1949     * @param keyText key that triggers the method
1950     * @param append true if you want to have multiple methods per key
1951     * @return boolean
1952     */
1953    public boolean onKey(String methodName, String keyText, boolean append) {
1954        return addKeyBinding(methodName, keyText, append, false);
1955    }
1956
1957    /**
1958     * Links a method to a key.
1959     *
1960     * @param methodName method to be executed when the key is pressed
1961     * @param keyText key that triggers the method
1962     * @param append true if you want to have multiple methods per key
1963     * @param repeat true if you want call the method every screen refresh
1964     * @return boolean
1965     */
1966    public boolean onKey(String methodName, String keyText, boolean append, boolean repeat) {
1967        return addKeyBinding(methodName, keyText, append, repeat);
1968    }
1969
1970    /**
1971     *
1972     * Fits the indicated box in the center of the screen as large as possible.
1973     *
1974     * @param minx left x coordinate of box
1975     * @param miny bottom y coordinate of box
1976     * @param maxx right x coordinate of box
1977     * @param maxy top y coordinate of box
1978     */
1979    public static void zoom(double minx, double miny, double maxx, double maxy) {
1980        synchronized (turtleLock) {
1981            centerX = (minx + maxx) / 2;
1982            centerY = (miny + maxy) / 2;
1983            if (width / (maxx - minx) > height / (maxy - miny)) scale = height / (maxy - miny);
1984            else scale = width / (maxx - minx);
1985            updateAll();
1986        }
1987    }
1988
1989    /**
1990     * Fits everything on the screen.
1991     */
1992    public static void zoomFit() {
1993        synchronized (turtleLock) {
1994            Point2D.Double loc;
1995            if (turtleStates.isEmpty()) return;
1996            else loc = getStateLocation(turtleStates.firstEntry().getValue());
1997            double minx = loc.x, miny = loc.y;
1998            double maxx = minx, maxy = miny;
1999            double shapeWidth = 0;
2000            double shapeHeight = 0;
2001            long time = System.nanoTime();
2002            if (refreshMode != REFRESH_MODE_ANIMATED) time = turtleStates.lastKey() + 1;
2003            for (Map.Entry<Long, ArrayList> entry : turtleStates.headMap(time).entrySet()) {
2004                ArrayList state = entry.getValue();
2005                if (!getStateIsPenDown(state)) continue;
2006                Point2D.Double location = getStateLocation(state);
2007                if (location.x < minx) minx = location.x;
2008                if (location.x > maxx) maxx = location.x;
2009                if (location.y < miny) miny = location.y;
2010                if (location.y > maxy) maxy = location.y;
2011                shapeWidth = getStateShapeWidth(state);
2012                shapeHeight = getStateShapeHeight(state);
2013            }
2014
2015            if (turtleStates.lastKey() > time && getStateSpeed(turtleStates.lastEntry().getValue()) > 0) {
2016                double percent = 1 - (turtleStates.lastKey() - time) / getStateSpeed(turtleStates.lastEntry().getValue()) / 1000000.0;
2017                //System.out.println("trying");
2018                Turtle t = getStateTurtle(turtleStates.lastEntry().getValue());
2019                double x1 = t._location.x, y1 = t._location.y, x2 = t.__location.x, y2 = t.__location.y;
2020                x1 = (x2 - x1) * percent + x1;
2021                y1 = (y2 - y1) * percent + y1;
2022                if (x1 < minx) minx = x1;
2023                if (x1 > maxx) maxx = x1;
2024                if (y1 < miny) miny = y1;
2025                if (y1 > maxy) maxy = y1;
2026            }
2027            double shapeMax = Math.max(shapeWidth, shapeHeight);
2028            zoom(minx - shapeMax / 2, miny - shapeMax / 2, maxx + shapeMax / 2, maxy + shapeMax / 2);
2029        }
2030    }
2031
2032    private static void updateAll() {
2033        lastUpdate = 0;
2034        draw();
2035    }
2036
2037    /**
2038     * Force redraw when the refreshMode is set to on demand.
2039     */
2040    public static void update() {
2041        if (refreshMode == REFRESH_MODE_ON_DEMAND) draw();
2042    }
2043
2044    // Hunter: Made public for testing
2045    public static void draw() {
2046        synchronized (turtleLock) {
2047
2048            long renderTime = System.nanoTime();
2049            if (turtleStates.isEmpty() || lastUpdate == 0) {
2050                clearStorage();
2051                drawBackground(offscreen);
2052            }
2053            if (turtleStates.isEmpty()) {
2054                onscreen.drawImage(offscreenImage, 0, 0, null);
2055                window.repaint();
2056                if (applet != null) applet.repaint();
2057                return;
2058            }
2059            if (refreshMode != REFRESH_MODE_ANIMATED) renderTime = turtleStates.lastKey() + 1;
2060            if (lastUpdate > turtleStates.lastKey()) {
2061                midscreen.drawImage(offscreenImage, 0, 0, null);
2062                for (Turtle t : turtles) {
2063                    if (t.isVisible) t.drawStamp(1, midscreen);
2064                    //if(t==selectedTurtle)t.drawCrossHairs(1,midscreen);
2065                }
2066                onscreen.drawImage(midscreenImage, 0, 0, null);
2067                window.repaint();
2068                if (applet != null) applet.repaint();
2069                return;
2070            }
2071            for (Map.Entry<Long, ArrayList> entry : turtleStates.tailMap(lastUpdate).headMap(renderTime).entrySet()) {
2072                retrieveState(entry.getKey());
2073                Turtle t = getStateTurtle(entry.getValue());
2074                t.drawLine(1, offscreen);
2075                if (t._isStamp) t.drawStamp(1, offscreen);
2076                t.drawDot(1, offscreen);
2077            }
2078
2079            midscreen.drawImage(offscreenImage, 0, 0, null);
2080            Turtle animatedTurtle = null;
2081            double percent = 1;
2082            Long t2;
2083            t2 = Long.valueOf(0);
2084            if (renderTime < turtleStates.lastKey()) {
2085                animatedTurtle = getStateTurtle(turtleStates.ceilingEntry(renderTime).getValue());
2086                t2 = animatedTurtle._time;
2087                retrieveState(turtleStates.ceilingKey(renderTime));
2088                if (animatedTurtle._speed > 0) {
2089                    percent = 1 - (turtleStates.ceilingKey(renderTime) - renderTime) / animatedTurtle._speed / 1000000.0;
2090                } else percent = 1;
2091                if (percent < 0) percent = 0;
2092            }
2093
2094            for (Turtle t : turtles) {
2095                if (t == animatedTurtle) {
2096                    //System.out.println(percent);
2097                    t.drawLine(percent, midscreen);
2098                    if (t._dotSize > 0) t.drawDot(percent, midscreen);
2099                    if (t.isVisible) t.drawStamp(percent, midscreen, false);
2100                    if (t._isStamp) t.drawStamp(percent, midscreen, true);
2101                    //if(t==selectedTurtle)t.drawCrossHairs(percent,midscreen);
2102                    try {
2103                        retrieveState(t2);
2104                    } catch (Exception e) {
2105                    }
2106                } else {
2107                    if (t.isVisible) t.drawStamp(1, midscreen);
2108                    //if(t==selectedTurtle)t.drawCrossHairs(1,midscreen);
2109                }
2110
2111            }
2112            lastUpdate = renderTime;
2113            //zoomFit();
2114            onscreen.drawImage(midscreenImage, 0, 0, null);
2115            window.repaint();
2116            if (applet != null) applet.repaint();
2117        }
2118
2119
2120    }
2121
2122    private void drawLine(double percent, Graphics2D g) {
2123        if (!_isPenDown) return;
2124        g.setColor(_penColor);
2125        g.setStroke(new BasicStroke((float) (scale * _penWidth), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
2126        if (__location != null && !__location.equals(_location)) {
2127            double x1 = _location.x, y1 = _location.y, x2 = __location.x, y2 = __location.y;
2128            if (percent < 1 && percent >= 0) {
2129                x1 = (x1 - x2) * percent + x2;
2130                y1 = (y1 - y2) * percent + y2;
2131            }
2132            //g.draw(new Line2D.Double((x1-centerX)*scale+width/2, (y1-centerY)*(-scale)+height/2, (x2-centerX)*scale+width/2, (y2-centerY)*(-scale)+height/2));
2133            g.drawLine((int) ((x1 - centerX) * scale + width / 2), (int) ((y1 - centerY) * (-scale) + height / 2), (int) ((x2 - centerX) * scale + width / 2), (int) ((y2 - centerY) * (-scale) + height / 2));
2134        }
2135    }
2136
2137    private void drawStamp(double percent, Graphics2D g) {
2138        drawStamp(percent, g, false);
2139    }
2140
2141    private void drawStamp(double percent, Graphics2D g, boolean isStamp) {
2142        if (_location == null) return;
2143        AffineTransform originalTransform = (AffineTransform) g.getTransform().clone();
2144        AffineTransform m = g.getTransform();
2145        double x1, x2, y1, y2, dir1, dir2, tilt1, tilt2;
2146        x1 = _location.x;
2147        y1 = _location.y;
2148        dir1 = _direction;
2149        tilt1 = _tilt;
2150        if (__location == null) {
2151            x2 = x1;
2152            y2 = y1;
2153            dir2 = dir1;
2154            tilt2 = tilt1;
2155        } else {
2156            x2 = __location.x;
2157            y2 = __location.y;
2158            dir2 = __direction;
2159            tilt2 = __tilt;
2160        }
2161        if (percent < 1 && percent >= 0) {
2162            x1 = (x1 - x2) * percent + x2;
2163            y1 = (y1 - y2) * percent + y2;
2164            dir1 = (dir1 - dir2) * percent + dir2;
2165            tilt1 = (tilt1 - tilt2) * percent + tilt2;
2166        }
2167        m.translate(((x1 - centerX) * scale + width / 2), ((y1 - centerY) * (-scale) + height / 2));
2168        if (isStamp) m.scale(scale * percent, scale * percent);
2169        else m.scale(scale, scale);
2170        if (_image == null) {
2171            //_outlineWidth=0.0;
2172            m.rotate(-Math.toRadians(dir1 + tilt1));
2173            m.scale(_shapeWidth / 100.0, _shapeHeight / 100.0);
2174            m.translate(-50, -50);
2175            g.setTransform(m);
2176            Polygon p = shapes.get(_shape);
2177            g.setColor(_fillColor);
2178            g.fillPolygon(p);
2179            g.setColor(_outlineColor);
2180            if (_outlineWidth > 0) {
2181                g.setStroke(new BasicStroke((float) (_outlineWidth * scale), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
2182                g.setTransform(originalTransform);
2183                GeneralPath gp = new GeneralPath();
2184                gp.append(p.getPathIterator(m), false);
2185                g.draw(gp);
2186            }
2187        } else {
2188            int w = _image.getWidth();
2189            int h = _image.getHeight();
2190            m.rotate(-Math.toRadians(dir1 + tilt1));
2191            m.scale(_shapeWidth / 1.0 / w, _shapeHeight / 1.0 / h);
2192            m.translate(-w / 2, -h / 2);
2193            g.setTransform(m);
2194            g.drawImage(_image, 0, 0, null);
2195        }
2196        g.setTransform(originalTransform);
2197    }
2198
2199    private void drawDot(double percent, Graphics2D g) {
2200        AffineTransform originalTransform = (AffineTransform) g.getTransform().clone();
2201        AffineTransform m = g.getTransform();
2202        m.translate(((_location.x - centerX) * scale + width / 2), ((_location.y - centerY) * (-scale) + height / 2));
2203        m.scale(scale * percent / 2, scale * percent / 2);
2204        g.setTransform(m);
2205        g.setColor(_dotColor);
2206        int r = (int) (_dotSize * 1.0);
2207        g.fillOval(-r, -r, 2 * r, 2 * r);
2208        g.setTransform(originalTransform);
2209    }
2210
2211    private static void drawBackground(Graphics2D g) {
2212        g.setColor(backgroundColor);
2213        g.fillRect(0, 0, width, height);
2214        if (backgroundImage == null) return;
2215        int w = backgroundImage.getWidth();
2216        int h = backgroundImage.getHeight();
2217        if (backgroundMode == BACKGROUND_MODE_CENTER) {
2218            offscreen.drawImage(backgroundImage, (width - w) / 2, (height - h) / 2, w, h, null);
2219        } else if (backgroundMode == BACKGROUND_MODE_STRETCH) {
2220            offscreen.drawImage(backgroundImage, 0, 0, width, height, null);
2221        } else if (backgroundMode == BACKGROUND_MODE_CENTER_RELATIVE) {
2222            offscreen.drawImage(backgroundImage, (int) ((-w / 2 - centerX) * scale + width / 2), (int) ((h / 2 - centerY) * (-scale) + height / 2), (int) (w * scale), (int) (h * scale), null);
2223        } else if (backgroundMode == BACKGROUND_MODE_TILE) {
2224            for (int i = 0; i < width; i += w)
2225                for (int j = 0; j < height; j += h) offscreen.drawImage(backgroundImage, i, j, w, h, null);
2226        } else if (backgroundMode == BACKGROUND_MODE_TILE_RELATIVE) {
2227            double left = centerX - width / 2 / scale;
2228            double top = centerY + height / 2 / scale;
2229            double right = centerX + width / 2 / scale;
2230            double bottom = centerY - height / 2 / scale;
2231            for (double x = ((int) (left / w) - 1) * w; x <= right; x += w)
2232                for (double y = ((int) (bottom / h)) * h; y <= top + h; y += h)
2233                    offscreen.drawImage(backgroundImage, (int) ((x - centerX) * scale + width / 2), (int) ((y - centerY) * (-scale) + height / 2), (int) Math.ceil(w * scale), (int) Math.ceil(h * scale), null);
2234        }
2235    }
2236
2237    private void drawCrossHairs(double percent, Graphics2D g) {
2238        if (_location == null) return;
2239        double time = (System.nanoTime() / 100000000) / 10.0;
2240
2241        AffineTransform originalTransform = (AffineTransform) g.getTransform().clone();
2242        AffineTransform m = g.getTransform();
2243        double x1, x2, y1, y2, dir1, dir2;
2244        x1 = _location.x;
2245        y1 = _location.y;
2246        if (__location == null) {
2247            x2 = x1;
2248            y2 = y1;
2249        } else {
2250            x2 = __location.x;
2251            y2 = __location.y;
2252        }
2253        if (percent < 1 && percent >= 0) {
2254            x1 = (x1 - x2) * percent + x2;
2255            y1 = (y1 - y2) * percent + y2;
2256        }
2257        m.translate(((x1 - centerX) * scale + width / 2), ((y1 - centerY) * (-scale) + height / 2));
2258        int f = 10;
2259        m.scale(scale / f, scale / f);
2260        g.setTransform(m);
2261
2262        int period = 50;
2263        int r = (int) (Math.sqrt(shapeWidth * shapeWidth + shapeHeight * shapeHeight) * f / 2);
2264        g.setColor(new Color(255, 255, 255));
2265        g.setStroke(new BasicStroke((float) (6 * f), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
2266        g.drawOval(-r, -r, 2 * r, 2 * r);
2267        r += f;
2268        for (int i = 0; i < 4; i++)
2269            g.drawLine((int) (r * Math.cos(Math.PI / 2 * i + 2 * Math.PI * time / period)), (int) (r * Math.sin(Math.PI / 2 * i + 2 * Math.PI * time / period)),
2270                    (int) ((r + r / 5) * Math.cos(Math.PI / 2 * i + 2 * Math.PI * time / period)), (int) ((r + r / 5) * Math.sin(Math.PI / 2 * i + 2 * Math.PI * time / period)));
2271        r -= f;
2272        g.setColor(new Color(0, 0, 0));
2273        g.setStroke(new BasicStroke((float) (3 * f), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
2274        g.drawOval(-r, -r, 2 * r, 2 * r);
2275        r += f;
2276        for (int i = 0; i < 4; i++)
2277            g.drawLine((int) (r * Math.cos(Math.PI / 2 * i + 2 * Math.PI * time / period)), (int) (r * Math.sin(Math.PI / 2 * i + 2 * Math.PI * time / period)),
2278                    (int) ((r + r / 5) * Math.cos(Math.PI / 2 * i + 2 * Math.PI * time / period)), (int) ((r + r / 5) * Math.sin(Math.PI / 2 * i + 2 * Math.PI * time / period)));
2279        g.setTransform(originalTransform);
2280    }
2281
2282    /**
2283     * Changes the size of the canvas effectively changing the size of the window.
2284     *
2285     * @param width width of the canvas
2286     * @param height height of the canvas
2287     */
2288    public static void setCanvasSize(int width, int height) {
2289        Turtle.width = width;
2290        Turtle.height = height;
2291        Turtle.setupBuffering();
2292        window.pack();
2293        updateAll();
2294    }
2295
2296    /**
2297     * Saves the visible canvas to an image.
2298     *
2299     * @param filename image filename
2300     */
2301    public static void save(String filename) {
2302        save(new File(filename));
2303    }
2304
2305    private static void save(File file) {
2306        WritableRaster raster = onscreenImage.getRaster();
2307        WritableRaster newRaster;
2308        newRaster = raster.createWritableChild(0, 0, width, height, 0, 0, new int[]{0, 1, 2});
2309        DirectColorModel cm = (DirectColorModel) onscreenImage.getColorModel();
2310        DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(),
2311                cm.getRedMask(),
2312                cm.getGreenMask(),
2313                cm.getBlueMask());
2314        BufferedImage rgbBuffer = new BufferedImage(newCM, newRaster, false, null);
2315        try {
2316            String suffix = file.getName().substring(file.getName().lastIndexOf('.') + 1);
2317            if (!ImageIO.write(rgbBuffer, suffix, file)) throw new Exception("Didn't save file.");
2318        } catch (Exception e) {
2319            file.delete();
2320            JOptionPane.showMessageDialog(window,
2321                    "Sorry! We can not process your request at this time.",
2322                    "Image Save Failed",
2323                    JOptionPane.ERROR_MESSAGE);
2324        }
2325    }
2326
2327    /**
2328     * Demo program
2329     *
2330     * @param a commandline args
2331     */
2332    public static void main(String[] a) {
2333        //Turtle bob = new Turtle();
2334        /*for(int i=0;i<360;i++)
2335        {
2336            bob.forward(i*1.25);
2337            bob.left(90.25);
2338        }
2339         */
2340        /*If you don't know what a for loop is yet this is equivalent to repeating the middle 4 lines 5 times in a row.*/
2341        Turtle bob = new Turtle();
2342        bgcolor("lightblue");
2343        bob.penColor("red");
2344        bob.width(10);
2345        for (int i = 0; i < 200; i++) {
2346            bob.forward(i / 10.);
2347            bob.left(5);
2348            if (i % 10 == 0) bob.dot("orange");//Draws dots when i is a multiple of 10.
2349        }
2350        bob.saveGCODE("test.gcode");
2351
2352    }
2353
2354    /**
2355     * Internal mehod for handling events.
2356     * @param e event
2357     */
2358    public void actionPerformed(ActionEvent e) {
2359        if (((JMenuItem) e.getSource()).getText().equals("Save...")) {
2360            JFileChooser chooser = new JFileChooser(System.getProperty("user.dir"));
2361            chooser.setFileFilter(new FileNameExtensionFilter("Image (*.jpg, *.jpeg, *.gif, *.bmp, *.png)", "jpg", "png", "jpeg", "bmp", "gif"));
2362            int option = chooser.showSaveDialog(window);
2363            if (option == JFileChooser.APPROVE_OPTION) {
2364                if (chooser.getSelectedFile() != null) {
2365                    File file = chooser.getSelectedFile();
2366                    save(file);
2367                }
2368            }
2369        }
2370    }
2371
2372    /**
2373     * Internal mehod for handling events.
2374     * @param e event
2375     */
2376    public void mouseClicked(MouseEvent e) {
2377        if (e.getModifiers() == 8 && e.getClickCount() == 2) {
2378            centerX = 0;
2379            centerY = 0;
2380            scale = 1;
2381            updateAll();
2382        }
2383    }
2384
2385    /**
2386     * Internal mehod for handling events.
2387     * @param e event
2388     */
2389    public void mouseEntered(MouseEvent e) {
2390    }
2391
2392    /**
2393     * Internal mehod for handling events.
2394     * @param e event
2395     */
2396    public void mouseExited(MouseEvent e) {
2397    }
2398
2399    /**
2400     * Internal mehod for handling events.
2401     * @param e event
2402     */
2403    public void mousePressed(MouseEvent e) {
2404        dragx = e.getX();
2405        dragy = e.getY();
2406        modifiers += e.getModifiers();
2407        synchronized (turtleLock) {
2408            for (Turtle t : turtles) {
2409                if (t.contains(dragx, dragy)) t.select();
2410                else t.unselect();
2411            }
2412        }
2413    }
2414
2415    /**
2416     * Internal mehod for handling events.
2417     * @param e event
2418     */
2419    public void mouseReleased(MouseEvent e) {
2420        modifiers -= e.getModifiers();
2421    }
2422
2423    /**
2424     * Internal mehod for handling events.
2425     * @param e event
2426     */
2427    public void mouseDragged(MouseEvent e) {
2428        modifiers = e.getModifiers();
2429        int dx, dy;
2430        if (e.getModifiers() == 8) {
2431            x = e.getX();
2432            dx = x - dragx;
2433            y = e.getY();
2434            dy = y - dragy;
2435            dragx = x;
2436            dragy = y;
2437            synchronized (turtleLock) {
2438                centerX -= dx * 1.0 / scale;
2439                centerY += dy * 1.0 / scale;
2440            }
2441            updateAll();
2442        }
2443        this.x = e.getX();
2444        this.y = e.getY();
2445    }
2446
2447    /**
2448     * Internal mehod for handling events.
2449     * @param e event
2450     */
2451    public void mouseMoved(MouseEvent e) {
2452        modifiers = e.getModifiers();
2453        x = e.getX();
2454        y = e.getY();
2455
2456    }
2457
2458    /**
2459     * Internal mehod for handling events.
2460     * @param e event
2461     */
2462    public void keyTyped(KeyEvent e) {
2463    }
2464
2465    /**
2466     * Internal mehod for handling events.
2467     * @param e event
2468     */
2469    public void keyPressed(KeyEvent e) {
2470        String keyText = KeyEvent.getKeyText(e.getKeyCode()).toLowerCase();
2471        synchronized (keyLock) {
2472            keysDown.add(keyText);
2473            if (keyBindings.containsKey(keyText)) {
2474                unprocessedKeys.add(keyText);
2475            }
2476        }
2477    }
2478
2479    /**
2480     * Internal mehod for handling events.
2481     * @param e event
2482     */
2483    public void keyReleased(KeyEvent e) {
2484        String keyText = KeyEvent.getKeyText(e.getKeyCode()).toLowerCase();
2485        synchronized (keyLock) {
2486            keysDown.remove(keyText);
2487            processedKeys.remove(keyText);
2488        }
2489    }
2490
2491    private void processKeys() {
2492        //System.out.println(keysDown);
2493        TreeSet<String> keysDownCopy = new TreeSet<String>();
2494        synchronized (keyLock) {
2495            keysDownCopy = (TreeSet<String>) keysDown.clone();
2496        }
2497        keysDownCopy.addAll(unprocessedKeys);
2498        for (String keyText : keysDownCopy) {
2499            if (keyBindings.containsKey(keyText)) {
2500                for (ArrayList binding : keyBindings.get(keyText)) {
2501                    Turtle t = (Turtle) binding.get(0);
2502                    String className = (String) binding.get(1);
2503                    String methodName = (String) binding.get(2);
2504                    Boolean repeat = (Boolean) binding.get(3);
2505                    if (!repeat && processedKeys.contains(keyText)) break;
2506                    unprocessedKeys.remove(keyText);
2507                    processedKeys.add(keyText);
2508                    try {
2509                        Class cls = Class.forName(className);
2510                        Object clsInstance = (Object) cls.newInstance();
2511                        Method m = clsInstance.getClass().getMethod(methodName, t.getClass());
2512                        m.invoke(clsInstance, t);
2513                    } catch (Exception e1) {
2514                        try {
2515                            Class cls = Class.forName(className);
2516                            Object clsInstance = (Object) cls.newInstance();
2517                            Method m = clsInstance.getClass().getMethod(methodName, t.getClass(), keyText.getClass());
2518                            m.invoke(clsInstance, t, keyText);
2519                        } catch (Exception e2) {
2520                            try {
2521                                Class cls = Class.forName(className);
2522                                Object clsInstance = (Object) cls.newInstance();
2523                                Method m = clsInstance.getClass().getMethod(methodName);
2524                                m.invoke(clsInstance);
2525                            } catch (Exception e3) {
2526                                System.out.println("KeyBinding for " + keyText + " has failed.");
2527                                e1.printStackTrace();
2528                                e2.printStackTrace();
2529                                e3.printStackTrace();
2530                            }
2531                        }
2532                    }
2533                }
2534            }
2535        }
2536    }
2537
2538    /**
2539     * Internal mehod for handling events.
2540     * @param e event
2541     */
2542    public void componentHidden(ComponentEvent e) {
2543    }
2544
2545    /**
2546     * Internal mehod for handling events.
2547     * @param e event
2548     */
2549    public void componentMoved(ComponentEvent e) {
2550    }
2551
2552    /**
2553     * Internal mehod for handling events.
2554     * @param e event
2555     */
2556    public void componentResized(ComponentEvent e) {
2557        width = (int) draw.getBounds().getWidth();
2558        height = (int) draw.getBounds().getHeight();
2559        setupBuffering();
2560        updateAll();
2561    }
2562
2563    /**
2564     * Internal mehod for handling events.
2565     * @param e event
2566     */
2567    public void componentShown(ComponentEvent e) {
2568    }
2569
2570    /**
2571     * Internal mehod for handling events.
2572     * @param e event
2573     */
2574    public void mouseWheelMoved(MouseWheelEvent e) {
2575        int notches = e.getWheelRotation();
2576        double ds = Math.pow(1.1, notches);
2577        x = e.getX();
2578        y = e.getY();
2579        double dx = width / 2 - x;
2580        double dy = height / 2 - y;
2581        synchronized (turtleLock) {
2582            centerX -= (dx * ds - dx) / scale / ds;
2583            centerY += (dy * ds - dy) / scale / ds;
2584            scale *= ds;
2585        }
2586        updateAll();
2587    }
2588
2589    /**
2590     * Get the pressed keys.
2591     *
2592     * @return a list of pressed keys
2593     */
2594    public static String[] keysDown() {
2595        return keysDown.toArray(new String[]{});
2596    }
2597
2598    /**
2599     * Test if a key is pressed or not.
2600     *
2601     * @param key key you are testing
2602     * @return true if the key is pressed
2603     */
2604    public static boolean isKeyDown(String key) {
2605        return keysDown.contains(key);
2606    }
2607
2608    /**
2609     * Get the mouse x coordinate using the screens coordinate system.
2610     *
2611     * @return x coordinate
2612     */
2613    public static int mouseX() {
2614        return turtle.x;
2615    }
2616
2617    /**
2618     * Get the mouse y coordinate using the screens coordinate system.
2619     *
2620     * @return y coordinate
2621     */
2622    public static int mouseY() {
2623        return turtle.y;
2624    }
2625
2626    /**
2627     * Check to see if a  mouse button is down.
2628     *
2629     * @return true if a button is down
2630     */
2631    public static boolean mouseButton() {
2632        return mouseButton1() || mouseButton2() || mouseButton3();
2633    }
2634
2635    /**
2636     * Check to see if the first mouse button is down.
2637     *
2638     * @return true if button 1 is down
2639     */
2640    public static boolean mouseButton1() {
2641        return (turtle.modifiers & 16) == 16;
2642    }
2643
2644    /**
2645     * Check to see if the second mouse button is down.
2646     *
2647     * @return true if button 2 is down
2648     */
2649    public static boolean mouseButton2() {
2650        return (turtle.modifiers & 8) == 8;
2651    }
2652
2653    /**
2654     * Check to see if the third mouse button is down.
2655     *
2656     * @return true if button 3 is down
2657     */
2658    public static boolean mouseButton3() {
2659        return (turtle.modifiers & 4) == 4;
2660    }
2661
2662    /**
2663     * Converts screen coordinates to canvas coordinates.
2664     *
2665     * @param screenX screen x coordinate
2666     * @return canvas x coordinate
2667     */
2668    public static double canvasX(double screenX) {
2669        return (screenX - width / 2.0) / scale + centerX;
2670    }
2671
2672    /**
2673     * Converts screen coordinates to canvas coordinates.
2674     *
2675     * @param screenY screen y coordinate
2676     * @return canvas y coordinate
2677     */
2678    public static double canvasY(double screenY) {
2679        return (-screenY + height / 2.0) / scale + centerY;
2680    }
2681
2682    public static double screenX(double canvasX) {
2683        return (canvasX - centerX) * scale + width / 2.0;
2684    }
2685
2686    public static double screenY(double canvasY) {
2687        return (canvasY - centerY) * scale + height / 2.0;
2688    }
2689
2690
2691    private static void saveGCODE(String filename) {
2692        PrintWriter out = new PrintWriter(System.out);
2693        try {
2694            out = new PrintWriter(filename);
2695        } catch (Exception e) {
2696
2697        }
2698        out.println("M104 S200");
2699        out.println("M109 S200");
2700        out.println("G21");
2701        out.println("G90");
2702        out.println("M82");
2703        out.println("M106");
2704        out.println("G28 X0 Y0");
2705        out.println("G28 Z0");
2706        out.println("G29");
2707        out.println("G1 Z15.0 F9000");
2708        out.println("G92 E0");
2709        out.println("G1 F200 E5");
2710        out.println("G92 E0");
2711        out.println("G1 X50 Y50 F1800");
2712
2713        double e = 0;
2714        synchronized (turtleLock) {
2715            int i = 0;
2716            for (Map.Entry<Long, ArrayList> entry : turtleStates.entrySet()) {
2717                retrieveState(entry.getKey());
2718                Turtle t = getStateTurtle(entry.getValue());
2719                i++;
2720                if (i == 1) continue;
2721                if (t.__location != null && !t.__location.equals(t._location)) {
2722                    double x1 = t._location.x, y1 = t._location.y, x2 = t.__location.x, y2 = t.__location.y;
2723                    double d = Math.hypot(x1 - x2, y1 - y2);
2724                    e += d * 0.05;
2725                    //System.out.printf("%f %f %f %f",x1,y1,x2,y2);
2726                    if (t._isPenDown) {
2727                        out.printf("G1 X%.4f Y%.4f E%.4f\n", screenX(x1) * 1.0 / width * 100, screenY(y1) * 1.0 / height * 100, e);
2728                    } else {
2729
2730                    }
2731
2732                }
2733            }
2734            out.println("G1 Z15");
2735            out.println("M104 S0");
2736            out.println("M140 S0");
2737            out.close();
2738        }
2739    }
2740}