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 }