/* * @(#)GraphicsProgram.java 2.0 08/09/20 */ // ************************************************************************ // * Copyright (c) 2008 by the Association for Computing Machinery * // * * // * The Java Task Force seeks to impose few restrictions on the use of * // * these packages so that users have as much freedom as possible to * // * use this software in constructive ways and can make the benefits of * // * that work available to others. In view of the legal complexities * // * of software development, however, it is essential for the ACM to * // * maintain its copyright to guard against attempts by others to * // * claim ownership rights. The full text of the JTF Software License * // * is available at the following URL: * // * * // * http://www.acm.org/jtf/jtf-software-license.pdf * // * * // ************************************************************************ // REVISION HISTORY // // -- V2.0 -- // Code cleanup 30-Jan-07 (ESR) // 1. Renamed instance variable "listener" to "eventListener" to avoid // warning messages from some compilers. // 2. Removed unnecessary startHook code. package acm.program; import acm.graphics.*; import acm.util.*; import java.awt.*; import java.awt.event.*; import java.lang.reflect.*; import java.util.*; /** * This class is a standard subclass of <code><a href="Program.html">Program</a></code> * whose principal window is used for drawing graphics. */ public abstract class GraphicsProgram extends Program { /* Constructor: GraphicsProgram() */ /** * Creates a new graphics program. * * @usage GraphicsProgram program = new GraphicsProgram(); */ protected GraphicsProgram() { eventListener = new GProgramListener(this); gc = createGCanvas(); gc.addMouseListener(eventListener); if (eventListener.needsMouseMotionListeners()) { gc.addMouseMotionListener(eventListener); } add(gc, CENTER); validate(); } /* Method: run() */ /** * Specifies the code to be executed as the program runs. * The <code>run</code> method is required for applications that have * a thread of control that runs even in the absence of user actions, * such as a program that uses console interation or that involves * animation. GUI-based programs that operate by setting up an initial * configuration and then wait for user events usually do not specify a * <code>run</code> method and supply a new definition for <code>init</code> * instead. */ public void run() { /* Empty */ } /* Method: init() */ /** * Specifies the code to be executed as startup time before the * <code>run</code> method is called. Subclasses can override this * method to perform any initialization code that would ordinarily * be included in an applet <code>init</code> method. In general, * subclasses will override <code>init</code> in GUI-based programs * where the program simply sets up an initial state and then waits * for events from the user. The <code>run</code> method is required * for applications in which there needs to be some control thread * while the program runs, as in a typical animation. * * @usage program.init(); */ public void init() { /* Empty */ } /* Method: getGCanvas() */ /** * Returns the <code>GCanvas</code> object used by this program. * * @usage GCanvas gc = getGCanvas(); * @return The <code>GCanvas</code> object used by the program */ public GCanvas getGCanvas() { return gc; } /* Method: add(gobj) */ /** * Adds a new graphical object to this container. * * @usage add(gobj); * @param gobj The graphical object to add */ public void add(GObject gobj) { gc.add(gobj); } /* Method: add(gobj, x, y) */ /** * Adds the graphical object to the canvas and sets its location * to the point (<code>x</code>, <code>y</code>). * * @usage add(gobj, x, y); * @param gobj The graphical object to add * @param x The new x-coordinate for the object * @param y The new y-coordinate for the object */ public final void add(GObject gobj, double x, double y) { gobj.setLocation(x, y); add(gobj); } /* Method: add(gobj, pt) */ /** * Adds the graphical object to the canvas and sets its location to the specified point. * * @usage add(gobj, pt); * @param gobj The graphical object to add * @param pt The new coordinates of the point */ public final void add(GObject gobj, GPoint pt) { gobj.setLocation(pt); add(gobj); } /* Method: add(comp, x, y) */ /** * Adds the component to the canvas and sets its location * to the point (<code>x</code>, <code>y</code>). * * @usage add(comp, x, y); * @param comp The component to add * @param x The new x-coordinate for the object * @param y The new y-coordinate for the object */ public final void add(Component comp, double x, double y) { comp.setLocation(GMath.round(x), GMath.round(y)); gc.add(comp); } /* Method: add(comp, pt) */ /** * Adds the component to the canvas and sets its location to the specified point. * * @usage add(comp, pt); * @param comp The component to add * @param pt A <code>GPoint</code> object giving the coordinates of the point */ public final void add(Component comp, GPoint pt) { add(comp, pt.getX(), pt.getY()); } /* Method: remove(gobj) */ /** * Removes a graphical object from this container. * * @usage remove(gobj); * @param gobj The graphical object to remove */ public void remove(GObject gobj) { gc.remove(gobj); } /* Method: removeAll() */ /** * Removes all graphical objects from this container. Note that this * definition overrides the <code>Container</code> version of * <code>removeAll</code>, which is replaced by * <a href="#removeAllComponents()"><code>removeAllComponents</code></a>. * * @usage removeAll(); */ public void removeAll() { gc.removeAll(); } /* Method: getElementCount() */ /** * Returns the number of graphical objects stored in this <code>GCanvas</code>. * * @usage int n = getElementCount(); * @return The number of graphical objects in this <code>GCanvas</code> */ public int getElementCount() { return gc.getElementCount(); } /* Method: getElement(index) */ /** * Returns the graphical object at the specified index, numbering from back * to front in the the <i>z</i> dimension. * * @usage GObject gobj = getElement(index); * @param index The index of the component to return * @return The graphical object at the specified index */ public GObject getElement(int index) { return gc.getElement(index); } /* Method: getElementAt(x, y) */ /** * Returns the topmost graphical object that contains the point * (<code>x</code>, <code>y</code>), or <code>null</code> if no such * object exists. * * @usage GObject gobj = program.getElementAt(x, y); * @param x The x-coordinate of the point being tested * @param y The y-coordinate of the point being tested * @return The graphical object at the specified location, or <code>null</code> * if no such object exists. */ public GObject getElementAt(double x, double y) { return gc.getElementAt(x, y); } /* Method: getElementAt(pt) */ /** * Returns the topmost graphical object that contains the specified point, * or <code>null</code> if no such object exists. * * @usage GObject gobj = program.getElementAt(pt); * @param pt The coordinates being tested * @return The graphical object at the specified location, or <code>null</code> * if no such object exists */ public final GObject getElementAt(GPoint pt) { return getElementAt(pt.getX(), pt.getY()); } /* Method: iterator() */ /** * Returns an <code>Iterator</code> that cycles through the elements within * this container in the default direction, which is from back to front. * You can also run the iterator in the opposite direction by using the * <a href="#iterator(int)"><code>iterator</code></a><code>(</code><font * size=-1><i>direction</i></font><code>)</code> form of this method. * * <p>Applets that want to run in browsers, however, should avoid using * this method, because <code>Iterator</code> is not supported on 1.1 browsers. * For maximum portability, you should rely instead on the * <a href="GContainer.html#getElementCount()"><code>getElementCount</code></a> * and <a href="GContainer.html#getElement(int)"><code>getElement</code></a> methods, * which provide the same functionality in a browser-compatible way. * * @usage Iterator<GObject> i = iterator(); * @return An <code>Iterator</code> ranging over the elements of the * container from back to front */ public Iterator<GObject> iterator() { return gc.iterator(); } /* Method: iterator(direction) */ /** * Returns an <code>Iterator</code> that cycles through the elements * within this container in the specified direction, which must be one * of the constants <a href="../graphics/GContainer.html#FRONT_TO_BACK"><code>FRONT_TO_BACK</code></a> * or <a href="GContainer.html#BACK_TO_FRONT"><code>BACK_TO_FRONT</code></a> * from the <a href="../graphics/GContainer.html"><code>GContainer</code></a> interface.<p> * * <code> for (Iterator<GObject> i = iterator(direction); i.hasNext(); )</code> * * <p>Applets that want to run in browsers, however, should avoid using * this method, because <code>Iterator</code> is not supported on 1.1 browsers. * For maximum portability, you should rely instead on the * <a href="GContainer.html#getElementCount()"><code>getElementCount</code></a> * and <a href="GContainer.html#getElement(int)"><code>getElement</code></a> methods, * which provide the same functionality in a browser-compatible way. * * @usage Iterator<GObject> i = iterator(direction); * @return An <code>Iterator</code> ranging over the elements of the * container in the specified direction */ public Iterator<GObject> iterator(int direction) { return gc.iterator(direction); } /* Method: addMouseListeners() */ /** * Adds the program as both a <code>MouseListener</code> and <code>MouseMotionListener</code> * to the canvas. * * @usage addMouseListeners(); */ public void addMouseListeners() { gc.addMouseListener(this); gc.addMouseMotionListener(this); } /* Method: addMouseListeners(listener) */ /** * Adds the specified listener as a <code>MouseListener</code> and/or * <code>MouseMotionListener</code>, as appropriate, to the canvas. * * @usage addMouseListeners(listener); * @param listener A listener object that is either a <code>MouseListener</code>, a * <code>MouseMotionListener</code>, or both */ public void addMouseListeners(EventListener listener) { boolean ok = false; if (listener instanceof MouseListener) { gc.addMouseListener((MouseListener) listener); ok = true; } if (listener instanceof MouseMotionListener) { gc.addMouseMotionListener((MouseMotionListener) listener); ok = true; } if (!ok) throw new ErrorException("addMouseListeners: Illegal listener"); } /* Method: addKeyListeners() */ /** * Adds the program as a <code>KeyListener</code> to the canvas. * * @usage addKeyListeners(); */ public void addKeyListeners() { gc.addKeyListener(this); } /* Method: addKeyListeners(listener) */ /** * Adds the specified listener as a <code>KeyListener</code> to the canvas. * * @usage addKeyListeners(listener); * @param listener A <code>KeyListener</code> object */ public void addKeyListeners(KeyListener listener) { gc.addKeyListener(listener); } /* Method: waitForClick() */ /** * Waits for a mouse click in the window before proceeding. * * @usage waitForClick(); */ public void waitForClick() { eventListener.waitForClick(); } /* Method: repaint() */ /** * Signals a need to repaint this window. * @noshow */ public void repaint() { gc.repaint(); super.repaint(); } /* Method: removeAllComponents() */ /** * Removes all components from this container. * * @usage removeAllComponents(); * @noshow */ public void removeAllComponents() { super.removeAll(); } /* Override method: setBackground(bg) */ /** * Sets the background color of the <code>GCanvas</code>. * * @usage setBackground(bg); * @param bg The new background color * @noshow */ public void setBackground(Color bg) { super.setBackground(bg); if (gc != null) gc.setBackground(bg); } /* Static method: startGraphicsProgram(gobj, args) */ /** * Creates a <code>GraphicsProgram</code> containing the specified <code>GObject</code> * and then starts it. This code is called only by the <code>start</code> method in * <code>GObject</code>. * * @usage startGraphicsProgram(gobj, args); * @param gobj The object to be inserted into the <code>GraphicsProgram</code> * @param args The array of arguments * @noshow */ public static void startGraphicsProgram(GObject gobj, String[] args) { GraphicsProgram program = new GObjectProgram(); program.setStartupObject(gobj); program.start(args); } /* Inherited method: print(value) */ /** * @inherited Program#void print(String value) * Displays the argument value on the console, leaving the cursor at the end of * the output. */ /* Inherited method: println() */ /** * @inherited Program#void println() * Advances the console cursor to the beginning of the next line. */ /* Inherited method: println(value) */ /** * @inherited Program#void println(String value) * Displays the argument value on the console and then advances the cursor * to the next line. */ /* Inherited method: readLine() */ /** * @inherited Program#String readLine() * Reads and returns a line of input from the console. */ /* Inherited method: readLine(prompt) */ /** * @inherited Program#String readLine(String prompt) * Prompts the user for a line of input. */ /* Inherited method: readInt() */ /** * @inherited Program#int readInt() * Reads and returns an integer value from the user. */ /* Inherited method: readInt(prompt) */ /** * @inherited Program#int readInt(String prompt) * Prompts the user to enter an integer. */ /* Inherited method: readDouble() */ /** * @inherited Program#double readDouble() * Reads and returns a double-precision value from the user. */ /* Inherited method: readDouble(prompt) */ /** * @inherited Program#double readDouble(String prompt) * Prompts the user to enter a double-precision number, which is * returned as the value of this method. */ /* Inherited method: readBoolean() */ /** * @inherited Program#boolean readBoolean() * Reads and returns a boolean value (<code>true</code> or <code>false</code>). */ /* Inherited method: readBoolean(prompt) */ /** * @inherited Program#boolean readBoolean(String prompt) * Prompts the user to enter a boolean value. */ /* Inherited method: readBoolean(prompt, trueLabel, falseLabel) */ /** * @inherited Program#boolean readBoolean(String prompt, String trueLabel, String falseLabel) * Prompts the user to enter a boolean value, which is matched against the * labels provided. */ /* Inherited method: getConsole() */ /** * @inherited Program#IOConsole getConsole() * Returns the console associated with this program. */ /* Inherited method: getDialog() */ /** * @inherited Program#IODialog getDialog() * Returns the dialog used for user interaction. */ /* Inherited method: getReader() */ /** * @inherited Program#BufferedReader getReader() * Returns a <code>BufferedReader</code> whose input comes from the console. */ /* Inherited method: getWriter() */ /** * @inherited Program#PrintWriter getWriter() * Returns a <code>PrintWriter</code> whose output is directed to the console. */ /* Inherited method: setTitle(title) */ /** * @inherited Program#void setTitle(String title) * Sets the title of this program. */ /* Inherited method: getTitle() */ /** * @inherited Program#String getTitle() * Gets the title of this program. */ /* Inherited method: pause(milliseconds) */ /** * @inherited Program#void pause(double milliseconds) * Delays the calling thread for the specified time, which is expressed in * milliseconds. */ /* Factory method: createGCanvas() */ /** * Creates the <code>GCanvas</code> used by the <code>GraphicsProgram</code>. Subclasses can * override this method to create their own <code>GCanvas</code> types. * * @usage GCanvas gc = program.createGCanvas(); * @return The <code>GCanvas</code> to be inserted into the program * @noshow */ protected GCanvas createGCanvas() { return new GCanvas(); } /* Protected method: endHook() */ /** * Ensures that the window is repainted at the end of the program. */ protected void endHook() { gc.repaint(); } /* Protected method: isStarted() */ /** * Checks to see whether this program has started, usually by checking to see * whether some pane exists. Subclasses can override this method to ensure * that their structures are visible before proceeding. * @noshow */ protected boolean isStarted() { if (gc == null || !super.isStarted()) return false; Dimension size = gc.getSize(); return (size != null) && (size.width != 0) && (size.height != 0); } /* Private instance variables */ private GCanvas gc; GProgramListener eventListener; } /* Package class: GProgramListener */ /** * The <code>GProgramListener</code> class implements the waitForClick * method and the objectdraw-style listener model. */ class GProgramListener implements MouseListener, MouseMotionListener { /* Constructor: GProgramListener() */ /** * Creates the <code>GProgramListener</code>. */ public GProgramListener(GraphicsProgram program) { myProgram = program; try { Class<?> programClass = program.getClass(); Class[] types = { Class.forName("acm.graphics.GPoint") }; try { mousePressedHook = programClass.getMethod("mousePressed", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseReleasedHook = programClass.getMethod("mouseReleased", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseClickedHook = programClass.getMethod("mouseClicked", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseMovedHook = programClass.getMethod("mouseMoved", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseDraggedHook = programClass.getMethod("mouseDragged", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseEnteredHook = programClass.getMethod("mouseEntered", types); } catch (NoSuchMethodException ex) { /* Empty */ } try { mouseExitedHook = programClass.getMethod("mouseExited", types); } catch (NoSuchMethodException ex) { /* Empty */ } } catch (Exception ex) { throw new ErrorException(ex); } } /* Method: needsMouseMotionListeners() */ /** * Returns true if this listener has to respond to mouse motion events as well. */ public boolean needsMouseMotionListeners() { return mouseMovedHook != null || mouseDraggedHook != null; } /* Method: mouseClicked() */ /** * Called by the event-handling system when the mouse is clicked in the canvas. */ public void mouseClicked(MouseEvent e) { if (mouseClickedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseClickedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } signalClickOccurred(); } /* Method: mousePressed() */ /** * Called by the event-handling system when the mouse button is pressed. */ public void mousePressed(MouseEvent e) { if (mousePressedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mousePressedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: mouseReleased() */ /** * Called by the event-handling system when the mouse button is released. */ public void mouseReleased(MouseEvent e) { if (mouseReleasedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseReleasedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: mouseEntered() */ /** * Called by the event-handling system when the mouse enters the component. */ public void mouseEntered(MouseEvent e) { if (mouseEnteredHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseEnteredHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: mouseExited() */ /** * Called by the event-handling system when the mouse leaves the component. */ public void mouseExited(MouseEvent e) { if (mouseExitedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseExitedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: mouseMoved() */ /** * Called by the event-handling system when the mouse moves. */ public void mouseMoved(MouseEvent e) { if (mouseMovedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseMovedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: mouseDragged() */ /** * Called by the event-handling system when the mouse is dragged with the button down. */ public void mouseDragged(MouseEvent e) { if (mouseDraggedHook != null) { Object[] args = { new GPoint(e.getX(), e.getY()) }; try { mouseDraggedHook.invoke(myProgram, args); } catch (Exception ex) { throw new ErrorException(ex); } } } /* Method: waitForClick() */ /** * Waits for a mouse click in the window before proceeding. * * @usage waitForClick(); */ public synchronized void waitForClick() { clickFlag = false; while (!clickFlag) { try { wait(); } catch (InterruptedException ex) { /* Empty */ } } } /* Private method: signalClickOccurred() */ /** * Notifies any waiting objects that a click has occurred. */ private synchronized void signalClickOccurred() { clickFlag = true; notifyAll(); } /* Private instance variables */ private GraphicsProgram myProgram; private Method mousePressedHook; private Method mouseReleasedHook; private Method mouseClickedHook; private Method mouseMovedHook; private Method mouseDraggedHook; private Method mouseEnteredHook; private Method mouseExitedHook; private boolean clickFlag; } /* Package class: GObjectProgram */ /** * This class is used to launch a program containing a single <code>GObject</code> * instance at its center. */ class GObjectProgram extends GraphicsProgram { /* Hook method: runHook() */ /** * Calls the run method in the graphical object. */ protected void runHook() { GObject gobj = (GObject) getStartupObject(); GDimension size = gobj.getSize(); add(gobj, (getWidth() - size.getWidth()) / 2, (getHeight() - size.getHeight()) / 2); try { Class<?> gobjClass = gobj.getClass(); String className = gobjClass.getName(); className = className.substring(className.lastIndexOf(".") + 1); setTitle(className); Method run = gobjClass.getMethod("run", new Class[0]); if (run == null) throw new ErrorException(className + " has no run method"); run.invoke(gobj, new Object[0]); } catch (Exception ex) { throw new ErrorException(ex); } } }