001    package gizmoball;
002    
003    import java.awt.BorderLayout;
004    import java.awt.Color;
005    import java.awt.Dimension;
006    import java.awt.Graphics;
007    import java.awt.Rectangle;
008    import java.awt.event.ActionEvent;
009    import java.awt.event.ActionListener;
010    import java.awt.event.KeyEvent;
011    import java.awt.event.KeyListener;
012    import java.awt.event.MouseAdapter;
013    import java.awt.event.MouseEvent;
014    import java.awt.event.MouseMotionListener;
015    import java.awt.event.WindowAdapter;
016    import java.awt.event.WindowEvent;
017    
018    import javax.swing.JButton;
019    import javax.swing.JComponent;
020    import javax.swing.JFrame;
021    import javax.swing.JPanel;
022    import javax.swing.JScrollPane;
023    import javax.swing.JToolBar;
024    import javax.swing.Timer;
025    
026    /**
027     * This is a sample Swing GUI application.
028     */
029    public class Example {
030    
031        public static void main(String[] args) {
032            ApplicationWindow frame = new ApplicationWindow();
033    
034            // the following code realizes the top level application window
035            frame.pack();
036            frame.setVisible(true);
037        }
038    
039        /**
040         * Overview: A BouncingBall is a mutable data type. It simulates a rubber
041         * ball bouncing inside a two dimensional box. It also provides methods
042         * that are useful for creating animations of the ball as it moves.
043         */
044        private static class BouncingBall {
045    
046            private final static double VELOCITY_STEP = 2.0;
047    
048            private int x = (int) ((Math.random() * 100.0) + 100.0);
049    
050            private int y = (int) ((Math.random() * 100.0) + 100.0);
051    
052            private int vx = (int) ((Math.random() * VELOCITY_STEP) + VELOCITY_STEP);
053    
054            private int vy = (int) ((Math.random() * VELOCITY_STEP) + VELOCITY_STEP);
055    
056            private int radius = 6;
057    
058            private Color color = new Color(255, 0, 0);
059    
060            // Keep track of the animation window that will be drawing this ball.
061            private AnimationWindow win;
062    
063            /**
064             * Constructor.
065             * @param win Animation window that will be drawing this ball.
066             */
067            public BouncingBall(AnimationWindow win) {
068                this.win = win;
069            }
070    
071            /**
072             * @modifies this
073             * @effects Moves the ball according to its velocity.  Reflections off
074             * walls cause the ball to change direction.
075             */
076            public void move() {
077    
078                x += vx;
079                if (x <= radius) {
080                    x = radius;
081                    vx = -vx;
082                }
083                if (x >= win.getWidth() - radius) {
084                    x = win.getWidth() - radius;
085                    vx = -vx;
086                }
087    
088                y += vy;
089                if (y <= radius) {
090                    y = radius;
091                    vy = -vy;
092                }
093                if (y >= win.getHeight() - radius) {
094                    y = win.getHeight() - radius;
095                    vy = -vy;
096                }
097            }
098    
099            /**
100             * @modifies this
101             * @effects Changes the velocity of the ball by a random amount
102             */
103            public void randomBump() {
104                vx += (int) ((Math.random() * VELOCITY_STEP) - (VELOCITY_STEP/2));
105                vx = -vx;
106                vy += (int) ((Math.random() * VELOCITY_STEP) - (VELOCITY_STEP/2));
107                vy = -vy;
108            }
109    
110            /**
111             * @modifies the Graphics object <g>.
112             * @effects paints a circle on <g> reflecting the current position
113             * of the ball.
114             * @param g Graphics context to be used for drawing.
115             */
116            public void paint(Graphics g) {
117    
118                // the "clip rectangle" is the area of the screen that needs to be
119                // modified
120                Rectangle clipRect = g.getClipBounds();
121    
122                // For this tiny program, testing whether we need to redraw is
123                // kind of silly.  But when there are lots of objects all over the
124                // screen this is a very important performance optimization
125                if (clipRect.intersects(this.boundingBox())) {
126                    g.setColor(color);
127                    g.fillOval(x - radius, y - radius, radius + radius, radius
128                                    + radius);
129                }
130            }
131    
132            /**
133             * @return the smallest rectangle that completely covers the current
134             * position of the ball.
135             */
136            public Rectangle boundingBox() {
137    
138                // a Rectangle is the x,y for the upper left corner and then the
139                // width and height
140                return new Rectangle(x - radius - 1, y - radius - 1, radius + radius + 2,
141                                radius + radius + 2);
142            }
143        }
144    
145        // Note the very indirect way control flow works during an animation:
146        //
147        // (1) We set up an eventListener with a reference to the animationWindow.
148        // (2) We set up a timer with a reference to the eventListener.
149        // (3) We call timer.start().
150        // (4) Every 20 milliseconds the timer calls eventListener.actionPerformed()
151        // (5) eventListener.actionPerformed() modifies the logical
152        //     datastructure (e.g. changes the coordinates of the ball).
153        // (6) eventListener.actionPerformed() calls myWindow.repaint.
154        // (7) Swing schedules, at some point in the future, a call to
155        //      myWindow.paint()
156        // (8) myWindow.paint() tells various objects to paint
157        //     themselves on the provided Graphics context.
158        //
159        // This may seem very complicated, but it makes the coordination of
160        // all the various different kinds of user input much easier.  For
161        // example here is how control flow works when the user presses the
162        // mouse button:
163        //
164        // (1) We set up an eventListener (actually we just use the same
165        //     eventListener that is being used by the timer.)
166        // (2) We register the eventListener with the window using the
167        //     addMouseListener() method.
168        // (3) Every time the mouse button is pressed inside the window the
169        //     window calls eventListener.mouseClicked().
170        // (4) eventListener.mouseClicked() modifies the logical
171        //     datastructures.  (In this example it calls ball.randomBump(), but
172        //     in other programs it might do something else, including request a
173        //     repaint operation).
174        //
175    
176        /**
177         * Overview: an AnimationWindow is an area on the screen in which a
178         * bouncing ball animation occurs.  AnimationWindows have two modes:
179         * on and off.  During the on mode the ball moves, during the off
180         *  mode the ball doesn't move.
181         */
182    
183        private static class AnimationWindow extends JComponent {
184    
185            private static final long serialVersionUID = 3257281448464364082L;
186    
187            // Controls how often we redraw
188            private static int FRAMES_PER_SECOND = 100;
189    
190            private AnimationEventListener eventListener;
191    
192            private BouncingBall ball;
193    
194            private Timer timer;
195    
196            private boolean mode;
197    
198            /**
199             * @effects initializes this to be in the off mode.
200             */
201            public AnimationWindow() {
202    
203                super(); // do the standard JPanel setup stuff
204    
205                ball = new BouncingBall(this);
206    
207                // this only initializes the timer, we actually start and stop the
208                // timer in the setMode() method
209                eventListener = new AnimationEventListener();
210    
211                // The first parameter is how often (in milliseconds) the timer
212                // should call us back.
213                timer = new Timer(1000 / FRAMES_PER_SECOND, eventListener);
214    
215                mode = false;
216            }
217    
218    
219            /**
220             * @modifies g
221             * @effects Repaints the Graphics area g.  Swing will then send the newly painted g to the screen.
222             * @param g Graphics context received by either system or app calling repaint()
223             */
224            @Override public void paintComponent(Graphics g) {
225                // first repaint the proper background color (controlled by
226                // the windowing system)
227                //super.paintComponent(g);
228                ball.paint(g);
229            }
230    
231            /**
232             * This method is called when the Timer goes off and we
233             * need to move and repaint the ball.
234             * @modifies both the ball and the window that this listener owns
235             * @effects causes the ball to move and the window to be updated
236             * to show the new position of the ball.
237             */
238            private void update() {
239                Rectangle oldPos = ball.boundingBox();
240    
241                ball.move(); // make changes to the logical animation state
242    
243                Rectangle repaintArea = oldPos.union(ball.boundingBox());
244    
245                // Have Swing tell the AnimationWindow to run its paint()
246                // method.  One could also call repaint(), but this would
247                // repaint the entire window as opposed to only the portion that
248                // has changed.
249                repaint(repaintArea.x, repaintArea.y, repaintArea.width,
250                                repaintArea.height);
251    
252            }
253    
254            /**
255             * @modifies this
256             * @effects Turns the animation on/off.
257             * @param m Boolean indicating if animation is on/off
258             */
259            public void setMode(boolean m) {
260    
261                if (mode == m) {
262                    // Nothing to do.
263                    return;
264                }
265    
266                if (mode == true) {
267                    // we're about to change mode: turn off all the old listeners
268                    removeMouseListener(eventListener);
269                    removeMouseMotionListener(eventListener);
270                    removeKeyListener(eventListener);
271                }
272    
273                mode = m;
274    
275                if (mode == true) {
276                    // the mode is true: turn on the listeners
277                    addMouseListener(eventListener);
278                    addMouseMotionListener(eventListener);
279                    addKeyListener(eventListener);
280                    requestFocus(); // make sure keyboard is directed to us
281                    timer.start();
282                }
283                else {
284                    timer.stop();
285                }
286            }
287    
288            /**
289             * Overview: AnimationEventListener is an inner class that
290             * responds to all sorts of external events, and provides the
291             * required semantic operations for our particular program.  It
292             * owns, and sends semantic actions to the ball and window of the
293             * outer class
294             */
295            class AnimationEventListener extends MouseAdapter implements
296                            MouseMotionListener, KeyListener, ActionListener {
297    
298                // MouseAdapter gives us empty methods for the MouseListener
299                // interface: mouseClicked, mouseEntered, mouseExited, mousePressed,
300                // and mouseReleased.
301    
302                /**
303                 * For this example we only need to override mouseClicked
304                 * @modifes the ball that this listener owns
305                 * @effects causes the ball to be bumped in a random direction
306                 * @param e Detected MouseEvent
307                 */
308                @Override public void mouseClicked(MouseEvent e) {
309                    ball.randomBump();
310                }
311    
312                /**
313                 * MouseMotionListener interface
314                 * Override this method to act on mouse drag events.
315                 * @param e Detected MouseEvent
316                 */
317                public void mouseDragged(MouseEvent e) {
318                }
319    
320                /**
321                 * MouseMotionListener interface
322                 * Override this method to act on mouse move events.
323                 * @param e Detected MouseEvent
324                 */
325                public void mouseMoved(MouseEvent e) {
326                }
327    
328                /**
329                 * We implement the KeyListener interface so that we can bump the ball in a
330                 * random direction if keys A-J is presse.
331                 * @modifies the ball that this listener owns
332                 * @effects causes the ball to be bumped in a random direction but
333                 * only if one of the keys A-J is pressed.
334                 * @param e Detected Key Press Event
335                 */
336                public void keyPressed(KeyEvent e) {
337                    //
338                    int keynum = e.getKeyCode();
339    
340                    if ((keynum >= 65) && (keynum <= 74)) {
341                        System.out.println("keypress " + e.getKeyCode());
342                        ball.randomBump();
343                    }
344                }
345    
346                /**
347                 * Do nothing.
348                 * @param e Detected Key Released Event
349                 */
350                public void keyReleased(KeyEvent e) {
351                }
352    
353                /**
354                 * Do nothing.
355                 * @param e Detected Key Typed Event
356                 */
357                public void keyTyped(KeyEvent e) {
358                }
359    
360                /**
361                 * This is the callback for the timer
362                 * @param e ActionEvent generated by timer
363                 */
364                public void actionPerformed(ActionEvent e) {
365                    update();
366                }
367            }
368        }
369    
370        /**
371         * Overview: An ApplicationWindow is a top level program window that
372         * contains a toolbar and an animation window.
373         */
374        private static class ApplicationWindow extends JFrame {
375    
376            private static final long serialVersionUID = 3257563992905298229L;
377    
378            protected AnimationWindow animationWindow;
379    
380            /**
381             * @effects Initializes the application window so that it contains
382             * a toolbar and an animation window.
383             */
384            public ApplicationWindow() {
385    
386                // Title bar
387                super("Swing Demonstration Program");
388    
389                // respond to the window system asking us to quit
390                addWindowListener(new WindowAdapter() {
391                    public void windowClosing(WindowEvent e) {
392                        System.exit(0);
393                    }
394                });
395    
396                //Create the toolbar.
397                JToolBar toolBar = new JToolBar();
398                addButtons(toolBar);
399    
400                //Create the animation area used for output.
401                animationWindow = new AnimationWindow();
402                // Put it in a scrollPane, (this makes a border)
403                JScrollPane scrollPane = new JScrollPane(animationWindow);
404    
405                //Lay out the content pane.
406                JPanel contentPane = new JPanel();
407                contentPane.setLayout(new BorderLayout());
408                contentPane.setPreferredSize(new Dimension(510, 530));
409                contentPane.add(toolBar, BorderLayout.NORTH);
410                contentPane.add(scrollPane, BorderLayout.CENTER);
411                setContentPane(contentPane);
412            }
413    
414            /**
415             * @modifies toolBar
416             * @effects adds Run, Stop and Quit buttons to toolBar
417             * @param toolBar toolbar to add buttons to.
418             */
419            protected void addButtons(JToolBar toolBar) {
420    
421                JButton button = null;
422    
423                button = new JButton("Run");
424                button.setToolTipText("Start the animation");
425                // when this button is pushed it calls animationWindow.setMode(true)
426                button.addActionListener(new ActionListener() {
427                    public void actionPerformed(ActionEvent e) {
428                        animationWindow.setMode(true);
429                    }
430                });
431                toolBar.add(button);
432    
433                button = new JButton("Stop");
434                button.setToolTipText("Stop the animation");
435                // when this button is pushed it calls animationWindow.setMode(false)
436                button.addActionListener(new ActionListener() {
437                    public void actionPerformed(ActionEvent e) {
438                        animationWindow.setMode(false);
439                    }
440                });
441                toolBar.add(button);
442    
443                button = new JButton("Quit");
444                button.setToolTipText("Quit the program");
445                button.addActionListener(new ActionListener() {
446                    public void actionPerformed(ActionEvent e) {
447                        System.exit(0);
448                    }
449                });
450                toolBar.add(button);
451            }
452        }
453    }