001 /*
002 * $URL: file:///mit/6.170/groups/se64/repository/trunk/gizmoball/Display.java $
003 * $Id: Display.java 691 2007-05-14 23:50:08Z billmag $
004 */
005
006 package gizmoball;
007
008 import gizmoball.physics3d.Vect3;
009
010 import javax.media.opengl.GLCanvas;
011 import javax.media.opengl.GLJPanel;
012
013 import java.awt.Container;
014 import java.awt.Dimension;
015 import java.awt.event.ActionEvent;
016 import java.awt.event.ActionListener;
017 import java.awt.event.ItemEvent;
018 import java.awt.event.ItemListener;
019 import java.io.File;
020 import java.net.MalformedURLException;
021 import java.net.URL;
022 import java.util.SortedSet;
023 import java.util.TreeSet;
024 import java.util.Vector;
025
026 import javax.swing.JButton;
027 import javax.swing.JComboBox;
028 import javax.swing.JDialog;
029 import javax.swing.JFileChooser;
030 import javax.swing.JFrame;
031 import javax.swing.JLabel;
032 import javax.swing.JMenu;
033 import javax.swing.JMenuBar;
034 import javax.swing.JMenuItem;
035 import javax.swing.JOptionPane;
036 import javax.swing.JPanel;
037 import javax.swing.JTabbedPane;
038 import javax.swing.Spring;
039 import javax.swing.SpringLayout;
040 import javax.swing.SwingUtilities;
041 import javax.swing.event.ChangeEvent;
042 import javax.swing.event.ChangeListener;
043 import javax.swing.event.MenuEvent;
044 import javax.swing.event.MenuListener;
045
046
047 /**
048 * Main window and controller of the Gizmoball application.
049 * <code>Display</code> manages all of the state of the Gizmoball
050 * application, both the instances of lower-level modules and the Swing
051 * graphical user interface presented to the user.
052 * <p>
053 * Beware that because one of <code>Display</code>'s major tasks is
054 * interacting with the Swing graphical user interface of the Gizmoball
055 * application, care must be taken with respect to threading. In
056 * particular, unless otherwise specified, all methods in this class
057 * should be called only from the Swing event dispatch thread. If a
058 * client not in the event dispatch thread wants to call a method on a
059 * <code>Display</code>, it should use {@link
060 * SwingUtilities#invokeAndWait} or {@link SwingUtilities#invokeLater}.
061 * <p>
062 * For those familiar with the model-view-controller design pattern,
063 * <code>Display</code> represents the controller in the overall
064 * architecture of the Gizmoball application. The <code>Board</code> and
065 * the <code>Gizmo</code> classes represent most of the model, and the
066 * <code>BoardView</code> and <code>GizmoView</code> classes represent
067 * the view.
068 *
069 * @specfield mode : <code>Display.Mode</code> -
070 * the current mode this <code>Display</code> is in
071 * @specfield board : <code>Board</code> -
072 * the currently loaded <code>Board</code>
073 * @specfield boardFile : <code>File</code> -
074 * the file <code>board</code> was loaded from or last saved to
075 * @specfield clockTickSpeed : integer -
076 * the number of milliseconds between ticks of the
077 * <code>Clock</code> in running mode
078 * @specfield isClockRunning : boolean -
079 * whether the <code>Clock</code> is currently running
080 */
081 public final class Display extends JFrame {
082
083 // Abstraction Function:
084 // TODO: Define mode.
085 // abstract.board = concrete.board;
086 // abstract.clockTickSpeed = concrete.clockTickSpeed;
087 // abstract.isClockRunning = concrete.clock.isRunning()
088
089 // Representation Invariant:
090 // clockTickSpeed > 0
091 // boardView != null
092 // if board != null, then clock != null
093
094
095 /*------------------------------------------------------------------
096 * Fields
097 *----------------------------------------------------------------*/
098
099 // The currently loaded Board.
100 private Board board;
101
102 // The file board was loaded from or last saved to
103 private File boardFile;
104
105 // The number of milliseconds between ticks of the Clock in running
106 // mode.
107 private int clockTickSpeed;
108
109 // The currently active Clock.
110 private Clock clock;
111
112 // The BoardView that is in charge of displaying board.
113 private final GLBoardView boardView;
114
115 // The SwingKeyboardInput that is in charge of displaying board.
116 private SwingKeyboardInput keyboardInput;
117
118 // The GLCanvas boardView renders in
119 private final GLCanvas boardCanvas;
120
121 // The tabbed pane containing the right bar controls
122 private final JTabbedPane rightBar;
123
124 // The panel containing the right bar controls for running mode
125 private final RunModePanel runModePanel;
126
127 // The panel containing the right bar controls for building mode
128 private final BuildModePanel buildModePanel;
129
130 // The top-level menu in running mode
131 private final JMenuBar runModeMenu;
132
133 // The top-level menu in buildling mode
134 private final JMenuBar buildModeMenu;
135
136 // A MenuListener that pauses the Clock when the menu is opened
137 private final MenuListener pausingMenuListener =
138 new MenuListener() {
139 public void menuCanceled(MenuEvent e) { }
140 public void menuDeselected(MenuEvent e) { }
141 public void menuSelected(MenuEvent e) { stopTime(); }
142 };
143
144 // A JFileChooser for the load and save as dialogs
145 private final JFileChooser fileChooser = new JFileChooser();
146
147 // The Gizmo being manipulated currently in building mode
148 private Gizmo currentGizmo = null;
149
150 // XXX: To quench a compiler warning; should be set to better value
151 // later.
152 private static final long serialVersionUID = 1L;
153
154 // Debugging output level. Follows the semantics defined by
155 // DebugUtils.printMessage.
156 private static final int debug = 0;
157
158 // Whether to run expensive checkRep() tests.
159 private static final boolean runExpensiveCheckRepTests = false;
160
161 /**
162 * Class revision identifier. <code>rcsid</code> contains an RCS Id
163 * keyword string that may be used to identify which revision of
164 * <code>Display.java</code> was used to generate an instance of
165 * <code>Display.class</code>. To identify the revision of a
166 * <code>Display.class</code> file, run "<code>ident
167 * Display.class</code>" (<code>ident</code> is part of the <a
168 * href="http://www.cs.purdue.edu/homes/trinkle/RCS/">RCS</a>
169 * software package).
170 */
171 public static final String rcsid = "$Id: Display.java 691 2007-05-14 23:50:08Z billmag $";
172
173
174 /*------------------------------------------------------------------
175 * Constructors
176 *----------------------------------------------------------------*/
177
178 /**
179 * Constructs a new <code>Display</code>. The resulting
180 * <code>Display</code> is in <code>Mode.RUN_MODE</code>, with no
181 * currently loaded board, and with time paused. The default time
182 * between <code>Clock</code> ticks is set to 40 milliseconds,
183 * equivalent to 25 ticks per second.
184 * <p>
185 * This method only prepares a <code>Display</code> for use. It
186 * populates the window with controls and hooks up the correct event
187 * handlers, but it does not show the window. To show the window,
188 * call <code>setVisible(true)</code> on this <code>Display</code>.
189 *
190 * @effects <code>mode = Mode.RUN_MODE</code>
191 * @effects <code>board = null</code>
192 * @effects <code>clockTickSpeed</code> = 40 milliseconds
193 * @effects <code>isClockRunning = false</code>
194 */
195 public Display() {
196 super("Gadgix Gizmoball");
197
198 assert SwingUtilities.isEventDispatchThread();
199
200 // Top-level window content pane -------------------------------
201
202 Container contentPane = getContentPane();
203 SpringLayout layout = new SpringLayout();
204 contentPane.setLayout(layout);
205
206 SpringLayout.Constraints contentPaneCons =
207 layout.getConstraints(contentPane);
208
209
210 // Display of the Board ----------------------------------------
211
212 boardCanvas = new GLCanvas();
213 boardCanvas.setPreferredSize(new Dimension(600, 600));
214 boardCanvas.setFocusable(true);
215 boardView = new GLBoardView(boardCanvas);
216
217 contentPane.add(boardCanvas);
218 SpringLayout.Constraints boardCanvasCons =
219 layout.getConstraints(boardCanvas);
220
221 // Enforce left and top margins
222 boardCanvasCons.setX(contentPaneCons.getX());
223 boardCanvasCons.setY(contentPaneCons.getY());
224
225 // Set that we want to calculate the default window height based
226 // on the height of the board display
227 contentPaneCons.setConstraint(SpringLayout.SOUTH,
228 boardCanvasCons.getConstraint(SpringLayout.SOUTH));
229
230
231 // Tabbed pane on the right ------------------------------------
232
233 rightBar = new JTabbedPane();
234
235 contentPane.add(rightBar);
236 SpringLayout.Constraints rightBarCons =
237 layout.getConstraints(rightBar);
238
239 // Place just to the right of the Board display
240 rightBarCons.setX(boardCanvasCons.getConstraint(SpringLayout.EAST));
241
242 // Snap top and bottom to top and bottom of Board display
243 rightBarCons.setY(boardCanvasCons.getY());
244 rightBarCons.setHeight(boardCanvasCons.getHeight());
245
246 // Set that we want to calculate the default window width based
247 // on the width of the TabbedPane
248 contentPaneCons.setConstraint(SpringLayout.EAST,
249 rightBarCons.getConstraint(SpringLayout.EAST));
250
251 // Catch when the user tries to switch tabs
252 rightBar.addChangeListener(new ChangeListener() {
253 public void stateChanged(ChangeEvent e) {
254 if (rightBar.getSelectedIndex() == 0) {
255 setMode(Mode.RUN_MODE);
256 } else {
257 assert rightBar.getSelectedIndex() == 1;
258 setMode(Mode.BUILD_MODE);
259 }
260 }
261 });
262
263 // Tabbed pane contents ----------------------------------------
264
265 runModePanel = new RunModePanel();
266 rightBar.addTab("Running Mode", runModePanel);
267
268 buildModePanel = new BuildModePanel();
269 rightBar.addTab("Building Mode", buildModePanel);
270
271
272 // Menus -------------------------------------------------------
273
274 runModeMenu = new JMenuBar();
275 runModeMenu.add(buildBoardMenu());
276 runModeMenu.add(buildHelpMenu());
277
278 buildModeMenu = new JMenuBar();
279 buildModeMenu.add(buildBoardMenu());
280 buildModeMenu.add(buildAddGizmoMenu());
281 buildModeMenu.add(buildConnectionsMenu());
282 buildModeMenu.add(buildHelpMenu());
283
284 setJMenuBar(runModeMenu);
285
286
287 // Miscellaneous -----------------------------------------------
288
289 // Actually calculate and set the window width and height
290 pack();
291
292 // Center the window
293 setLocationRelativeTo(null);
294
295 // TODO: Should eventually be hooked to something that checks
296 // for unsaved changes in the Board before exiting.
297 setDefaultCloseOperation(EXIT_ON_CLOSE);
298
299 clockTickSpeed = 30;
300
301 assert checkRep();
302 }
303
304 // Builds the Board menu.
305 private JMenu buildBoardMenu() {
306 JMenu menu = new JMenu("Board");
307
308 // XXX: Needed because we're using the heavyweight GLCanvas
309 // instead of the lightweight GLJPanel.
310 menu.getPopupMenu().setLightWeightPopupEnabled(false);
311
312 menu.addMenuListener(pausingMenuListener);
313
314 JMenuItem menuItem;
315
316 menuItem = new JMenuItem("New");
317 menuItem.addActionListener(new ActionListener() {
318 public void actionPerformed(ActionEvent e) {
319 newBoard();
320 }
321 });
322 menu.add(menuItem);
323
324 menuItem = new JMenuItem("Open...");
325 menuItem.addActionListener(new ActionListener() {
326 public void actionPerformed(ActionEvent e) {
327 openFromBoardMenu();
328 }
329 });
330 menu.add(menuItem);
331
332 menu.addSeparator();
333
334 menuItem = new JMenuItem("Save");
335 menuItem.addActionListener(new ActionListener() {
336 public void actionPerformed(ActionEvent e) {
337 saveFromBoardMenu();
338 }
339 });
340 menu.add(menuItem);
341
342 menuItem = new JMenuItem("Save As...");
343 menuItem.addActionListener(new ActionListener() {
344 public void actionPerformed(ActionEvent e) {
345 saveAsFromBoardMenu();
346 }
347 });
348 menu.add(menuItem);
349
350 menu.addSeparator();
351
352 menuItem = new JMenuItem("Quit");
353 menuItem.addActionListener(new ActionListener() {
354 public void actionPerformed(ActionEvent e) {
355 // TODO: Should eventually be hooked to something that
356 // checks for unsaved changes in the Board before
357 // exiting.
358 System.exit(0);
359 }
360 });
361 menu.add(menuItem);
362
363 return menu;
364 }
365
366 // Builds the Add Gizmo menu.
367 private JMenu buildAddGizmoMenu() {
368 JMenu menu = new JMenu("Add Gizmo");
369
370 // XXX: Needed because we're using the heavyweight GLCanvas
371 // instead of the lightweight GLJPanel.
372 menu.getPopupMenu().setLightWeightPopupEnabled(false);
373
374 menu.addMenuListener(pausingMenuListener);
375
376 JMenuItem menuItem;
377
378 menuItem = new JMenuItem("Add Square Bumper...");
379 menuItem.addActionListener(new ActionListener() {
380 public void actionPerformed(ActionEvent e) {
381 addSquareBumperToBoard();
382 }
383 });
384 menu.add(menuItem);
385
386 menuItem = new JMenuItem("Add Circular Bumper...");
387 menuItem.addActionListener(new ActionListener() {
388 public void actionPerformed(ActionEvent e) {
389 addCircularBumperToBoard();
390 }
391 });
392 menu.add(menuItem);
393
394 menuItem = new JMenuItem("Add Triangular Bumper...");
395 menuItem.addActionListener(new ActionListener() {
396 public void actionPerformed(ActionEvent e) {
397 addTriangularBumperToBoard();
398 }
399 });
400 menu.add(menuItem);
401
402 menu.addSeparator();
403
404 menuItem = new JMenuItem("Add Left Flipper...");
405 menuItem.addActionListener(new ActionListener() {
406 public void actionPerformed(ActionEvent e) {
407 addLeftFlipperToBoard();
408 }
409 });
410 menu.add(menuItem);
411
412 menuItem = new JMenuItem("Add Right Flipper...");
413 menuItem.addActionListener(new ActionListener() {
414 public void actionPerformed(ActionEvent e) {
415 addRightFlipperToBoard();
416 }
417 });
418 menu.add(menuItem);
419
420 menu.addSeparator();
421
422 menuItem = new JMenuItem("Add Absorber...");
423 menuItem.addActionListener(new ActionListener() {
424 public void actionPerformed(ActionEvent e) {
425 addAbsorberToBoard();
426 }
427 });
428 menu.add(menuItem);
429
430 menu.addSeparator();
431
432 menuItem = new JMenuItem("Add Ball...");
433 menuItem.addActionListener(new ActionListener() {
434 public void actionPerformed(ActionEvent e) {
435 addBallToBoard();
436 }
437 });
438 menu.add(menuItem);
439
440 return menu;
441 }
442
443 // Builds the Connections menu.
444 private JMenu buildConnectionsMenu() {
445 JMenu menu = new JMenu("Connections");
446
447 // XXX: Needed because we're using the heavyweight GLCanvas
448 // instead of the lightweight GLJPanel.
449 menu.getPopupMenu().setLightWeightPopupEnabled(false);
450
451 menu.addMenuListener(pausingMenuListener);
452
453 JMenuItem menuItem;
454
455 menuItem = new JMenuItem("Edit Gizmo-Gizmo Connections...");
456 menuItem.addActionListener(new ActionListener() {
457 public void actionPerformed(ActionEvent e) {
458 gizmoConnectModify();
459 }
460 });
461 menu.add(menuItem);
462
463 menuItem = new JMenuItem("Edit Keyboard Connections...");
464 menuItem.addActionListener(new ActionListener() {
465 public void actionPerformed(ActionEvent e) {
466 keyConnectModify();
467 }
468 });
469 menu.add(menuItem);
470
471 return menu;
472 }
473
474 // Builds the Help menu.
475 private JMenu buildHelpMenu() {
476 JMenu menu = new JMenu("Help");
477
478 // XXX: Needed because we're using the heavyweight GLCanvas
479 // instead of the lightweight GLJPanel.
480 menu.getPopupMenu().setLightWeightPopupEnabled(false);
481
482 menu.addMenuListener(pausingMenuListener);
483
484 JMenuItem menuItem;
485
486 menuItem = new JMenuItem("User Guide (external web browser)");
487 menuItem.addActionListener(new ActionListener() {
488 public void actionPerformed(ActionEvent e) {
489 userGuideFromHelpMenu();
490 }
491 });
492 menu.add(menuItem);
493
494 menuItem = new JMenuItem("About...");
495 menuItem.addActionListener(new ActionListener() {
496 public void actionPerformed(ActionEvent e) {
497 aboutFromHelpMenu();
498 }
499 });
500 menu.add(menuItem);
501
502 return menu;
503 }
504
505
506 /*------------------------------------------------------------------
507 * checkRep()
508 *----------------------------------------------------------------*/
509
510 // Checks that the representation invariant has not been violated.
511 // Returns true if the representation invariant holds. Throws a
512 // RuntimeException if the representation invariant does not hold.
513 private boolean checkRep() {
514 // TODO: Implement me.
515
516 return true;
517 }
518
519
520 /*------------------------------------------------------------------
521 * Observers
522 *----------------------------------------------------------------*/
523
524 /**
525 * Returns the current mode. Returns whether we're in running mode
526 * or building mode.
527 *
528 * @return <code>this.mode</code>
529 */
530 public Mode getMode() {
531 assert checkRep();
532 assert SwingUtilities.isEventDispatchThread();
533
534 Mode mode;
535
536 if (rightBar.getSelectedIndex() == 0) {
537 mode = Mode.RUN_MODE;
538 } else {
539 assert rightBar.getSelectedIndex() == 1;
540 mode = Mode.BUILD_MODE;
541 }
542
543 assert checkRep();
544 return mode;
545 }
546
547 /*------------------------------------------------------------------
548 * Mutators
549 *----------------------------------------------------------------*/
550
551 /**
552 * Loads a <code>Board</code> from an XML file stored at the given
553 * URL. If an error occurred in loading the XML file, a dialog
554 * message is displayed, and the current <code>Board</code> is not
555 * changed.
556 *
557 * @param url the URL at which to find the XML file containing the
558 * new <code>Board</code>
559 * @requires <code>url != null</code>
560 * @effects <code>this.board<sub>post</sub></code> = a
561 * <code>Board</code> loaded from <code>url</code> if the
562 * loading was successful,
563 * <code>this.board<sub>pre</sub></code> otherwise
564 * @effects <code>this.boardFile</code> is unset if the loading was
565 * successful, because can't pull a useful
566 * <code>File</code> from a <code>URL</code>
567 * @return <code>true</code> if the loading was successful,
568 * <code>false</code> otherwise
569 */
570 public boolean loadBoardFromURL(URL url) {
571 assert checkRep();
572
573 if (url == null) {
574 throw new NullPointerException("null was passed as argument url");
575 }
576
577 boolean loadSucceeded = true;
578
579 // FIXME: Stop clock? or specify that this must be called with a
580 // stopped clock?
581
582 // TODO: Do this in a separate thread...
583
584 // FIXME: Use new XMLLoadException once that's supported...
585 Board newBoard = null;
586 SwingKeyboardInput input = new SwingKeyboardInput();
587 keyboardInput = input;
588 boardCanvas.addKeyListener(input.getKeyListener());
589 try {
590 BoardBuilderXML builder =
591 new BoardBuilderXML(new PreliminaryReleaseErrorHandler());
592 newBoard = builder.buildBoardFromFile(url, input);
593 } catch (XMLLoadException e) {
594 loadSucceeded = false;
595 e.printStackTrace();
596 }
597
598 if (loadSucceeded) {
599 board = newBoard;
600 board.setDisplayMode(this);
601 board.setInputMode(input);
602 boardView.setBoard(board);
603 clock = new JavaUtilTimerClock(board, clockTickSpeed);
604 clock.addBoardView(boardView);
605 buildModePanel.refreshGizmosCombo();
606 } else {
607 JOptionPane.showMessageDialog(this,
608 "An error occurred in loading " + url + ".", "File Error",
609 JOptionPane.ERROR_MESSAGE);
610 }
611
612 assert checkRep();
613 return loadSucceeded;
614 }
615
616 /**
617 * Loads a <code>Board</code> from an XML file stored at the given
618 * <code>File</code>. If an error occurred in loading the XML file,
619 * a dialog message is displayed, and the current <code>Board</code>
620 * is not changed.
621 *
622 * @param file the <code>File</code> in which to find the XML file
623 * containing the new <code>Board</code>
624 * @requires <code>file != null</code>
625 * @effects <code>this.board<sub>post</sub></code> = a
626 * <code>Board</code> loaded from <code>file</code> if the
627 * loading was successful,
628 * <code>this.board<sub>pre</sub></code> otherwise
629 * @effects <code>this.boardFile</code> is set to <code>file</code>
630 * if the loading was successful, left alone if not
631 * @return <code>true</code> if the loading was successful,
632 * <code>false</code> otherwise
633 */
634 public boolean loadBoardFromFile(File file) {
635 assert checkRep();
636 assert SwingUtilities.isEventDispatchThread();
637
638 URL url;
639 try {
640 url = file.toURI().toURL();
641 } catch (MalformedURLException e) {
642 e.printStackTrace();
643 JOptionPane.showMessageDialog(this,
644 "An error occurred resolving " + file + ".", "File Error",
645 JOptionPane.ERROR_MESSAGE);
646 assert checkRep();
647 return false;
648 }
649
650 boolean loadSucceeded = loadBoardFromURL(url);
651
652 if (loadSucceeded) {
653 boardFile = file;
654 }
655
656 assert checkRep();
657 return loadSucceeded;
658 }
659
660 /**
661 * Switches the mode. Changes the UI such that it's ready for a new
662 * mode.
663 *
664 * @param newMode new mode to switch to
665 * @requires <code>newMode != null</code>
666 * @effects <code>mode = newMode</code>
667 * @effects if <code>newMode = Mode.BUILD_MODE</code>,
668 * then <code>isClockRunning = false</code>
669 */
670 public void setMode(Mode newMode) {
671 assert checkRep();
672 assert SwingUtilities.isEventDispatchThread();
673
674 if (newMode == null) {
675 throw new NullPointerException("null passed as argument newMode");
676 }
677
678 if (newMode == Mode.RUN_MODE) {
679 try {
680 if (currentGizmo != null) {
681 board.addGizmo(currentGizmo);
682 }
683 } catch (NonUniqueGizmoNameException e) {
684 // Cry
685 System.err.println("Unexpected error!");
686 e.printStackTrace();
687 }
688 currentGizmo = null;
689 if (board != null) {
690 boardView.setBoard(board);
691 }
692 boardView.setSelectedGizmo(null);
693 rightBar.setSelectedIndex(0);
694 setJMenuBar(runModeMenu);
695 boardView.redraw();
696 } else {
697 assert newMode == Mode.BUILD_MODE;
698
699 stopTime();
700 rightBar.setSelectedIndex(1);
701 setJMenuBar(buildModeMenu);
702 currentGizmo = board.checkoutGizmo(
703 (String)buildModePanel.gizmosCombo.getSelectedItem());
704 boardView.setSelectedGizmo(currentGizmo);
705 boardView.setBoard(board);
706 boardView.redraw();
707 }
708
709 assert checkRep();
710 }
711
712
713 /*------------------------------------------------------------------
714 * Running mode buttons handlers
715 *----------------------------------------------------------------*/
716
717 /**
718 * Starts the <code>Clock</code>. Calling this method switches to
719 * running mode if this <code>Display</code> is not already in
720 * running mode. It then starts the <code>Clock</code> if the
721 * <code>Clock</code> is not already running and allows the
722 * <code>Board</code> simulation to proceed.
723 *
724 * @effects <code>mode = Mode.RUN_MODE</code>
725 * @effects <code>isClockRunning = true</code>
726 */
727 public void startTime() {
728 assert checkRep();
729 assert SwingUtilities.isEventDispatchThread();
730
731 if (getMode() != Mode.RUN_MODE) {
732 setMode(Mode.RUN_MODE);
733 }
734
735 if (!clock.isRunning()) {
736 clock.start();
737 runModePanel.setClockRunning(true);
738
739 // Set focus to boardCanvas so that key triggers will work
740 // by default
741 // FIXME: This works on OS X (at least) but not on Linux...
742 boardCanvas.requestFocusInWindow();
743 }
744
745 assert checkRep();
746 }
747
748 /**
749 * Stops the <code>Clock</code>. This method stops the
750 * <code>Clock</code> if the <code>Clock</code> is currently running
751 * and thus pauses the <code>Board</code> simulation.
752 *
753 * @effects <code>isClockRunning = false</code>
754 */
755 public void stopTime() {
756 assert checkRep();
757 assert SwingUtilities.isEventDispatchThread();
758
759 if (clock.isRunning()) {
760 // Must use an auxiliary thread because clock.stop() might
761 // need to finish a redraw, and thus might need to access
762 // this Swing event dispatch thread
763 Thread auxThread = new Thread(new Runnable() {
764 public void run() {
765 clock.stop();
766 }
767 });
768 auxThread.start();
769
770 // XXX: The clock will not stop until some indeterminate
771 // time in the future...
772
773 runModePanel.setClockRunning(false);
774 }
775
776 assert checkRep();
777 }
778
779 /**
780 * Resets the <code>Board</code>. This method sets the
781 * <code>Board</code> back to the state that it would be in if it
782 * had just been loaded from a file.
783 *
784 * @requires <code>mode = Mode.RUN_MODE</code> and
785 * <code>isClockRunning = false</code>
786 * @effects <code>board</code> is reset
787 */
788 public void resetBoard() {
789 assert checkRep();
790 assert SwingUtilities.isEventDispatchThread();
791
792 // TODO: Reset ball(s) position and velocity.
793
794 // TODO: Clear all pending triggers.
795 if (boardFile == null)
796 newBoard();
797 else
798 loadBoardFromFile(boardFile);
799
800 assert checkRep();
801 }
802
803
804 /*------------------------------------------------------------------
805 * Building mode button handlers
806 *----------------------------------------------------------------*/
807
808 /**
809 * Prompts the user for a new name for the currently selected Gizmo.
810 */
811 public void renameCurrentGizmo() {
812 assert checkRep();
813 assert SwingUtilities.isEventDispatchThread();
814 assert getMode() == Mode.BUILD_MODE;
815
816 String newName = JOptionPane.showInputDialog(this,
817 "What would you like to name this gizmo?", "Rename Gizmo",
818 JOptionPane.QUESTION_MESSAGE);
819 if (board.getNames().contains(newName)) {
820 JOptionPane.showMessageDialog(this,
821 "There already is a gizmo named " + newName + ".",
822 "Rename Error", JOptionPane.ERROR_MESSAGE);
823 } else {
824 System.out.println("We want to rename " + currentGizmo.getName() +
825 " to " + newName);
826 }
827
828 assert checkRep();
829 }
830
831 /**
832 * Moves the currently selected Gizmo up in building mode. This
833 * corresponds with a decrese in the y coordinate.
834 */
835 public void moveCurrentGizmoUp() {
836 assert checkRep();
837 assert SwingUtilities.isEventDispatchThread();
838 assert getMode() == Mode.BUILD_MODE;
839
840 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
841 Vect3 originalPosition = currentGizmo.getPosition();
842 boolean newPositionFound = false;
843
844 yPositionLoop:
845 while (currentGizmo.getPosition().y() >= step) {
846 currentGizmo.move(new Vect3(0, -step, 0));
847
848 for (Gizmo gizmo : board) {
849 if (!(gizmo instanceof OuterWalls) &&
850 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
851 continue yPositionLoop;
852 }
853 }
854
855 // If we got here, then no overlaps found
856 newPositionFound = true;
857 break;
858 }
859
860 if (newPositionFound) {
861 boardView.redraw();
862 } else {
863 currentGizmo.moveTo(originalPosition);
864 }
865
866 assert checkRep();
867 }
868
869 /**
870 * Moves the currently selected Gizmo left in building mode. This
871 * corresponds with a decrese in the x coordinate.
872 */
873 public void moveCurrentGizmoLeft() {
874 assert checkRep();
875 assert SwingUtilities.isEventDispatchThread();
876 assert getMode() == Mode.BUILD_MODE;
877
878 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
879 Vect3 originalPosition = currentGizmo.getPosition();
880 boolean newPositionFound = false;
881
882 xPositionLoop:
883 while (currentGizmo.getPosition().x() >= step) {
884 currentGizmo.move(new Vect3(-step, 0, 0));
885
886 for (Gizmo gizmo : board) {
887 if (!(gizmo instanceof OuterWalls) &&
888 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
889 continue xPositionLoop;
890 }
891 }
892
893 // If we got here, then no overlaps found
894 newPositionFound = true;
895 break;
896 }
897
898 if (newPositionFound) {
899 boardView.redraw();
900 } else {
901 currentGizmo.moveTo(originalPosition);
902 }
903
904 assert checkRep();
905 }
906
907 /**
908 * Moves the currently selected Gizmo right in building mode. This
909 * corresponds with an increase in the x coordinate.
910 */
911 public void moveCurrentGizmoRight() {
912 assert checkRep();
913 assert SwingUtilities.isEventDispatchThread();
914 assert getMode() == Mode.BUILD_MODE;
915
916 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
917 Vect3 originalPosition = currentGizmo.getPosition();
918 boolean newPositionFound = false;
919
920 xPositionLoop:
921 while ((currentGizmo instanceof Ball) ?
922 (currentGizmo.getPosition().x() <= 20.0 - 0.25 - step) :
923 (GizmoBoundCalculator.ceilX(currentGizmo) <= 20.0 - step)) {
924 currentGizmo.move(new Vect3(step, 0, 0));
925
926 for (Gizmo gizmo : board) {
927 if (!(gizmo instanceof OuterWalls) &&
928 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
929 continue xPositionLoop;
930 }
931 }
932
933 // If we got here, then no overlaps found
934 newPositionFound = true;
935 break;
936 }
937
938 if (newPositionFound) {
939 boardView.redraw();
940 } else {
941 currentGizmo.moveTo(originalPosition);
942 }
943
944 assert checkRep();
945 }
946
947 /**
948 * Moves the currently selected Gizmo down in building mode. This
949 * corresponds with an increase in the y coordinate.
950 */
951 public void moveCurrentGizmoDown() {
952 assert checkRep();
953 assert SwingUtilities.isEventDispatchThread();
954 assert getMode() == Mode.BUILD_MODE;
955
956 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
957 Vect3 originalPosition = currentGizmo.getPosition();
958 boolean newPositionFound = false;
959
960 yPositionLoop:
961 while ((currentGizmo instanceof Ball) ?
962 (currentGizmo.getPosition().y() <= 20.0 - 0.25 - step) :
963 (GizmoBoundCalculator.ceilY(currentGizmo) <= 20.0 - step)) {
964 currentGizmo.move(new Vect3(0, step, 0));
965
966 for (Gizmo gizmo : board) {
967 if (!(gizmo instanceof OuterWalls) &&
968 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
969 continue yPositionLoop;
970 }
971 }
972
973 // If we got here, then no overlaps found
974 newPositionFound = true;
975 break;
976 }
977
978 if (newPositionFound) {
979 boardView.redraw();
980 } else {
981 currentGizmo.moveTo(originalPosition);
982 }
983
984 assert checkRep();
985 }
986
987 /**
988 * Moves the currently selected Gizmo backward in building mode.
989 * This corresponds with an increase in the z coordinate.
990 */
991 public void moveCurrentGizmoBackward() {
992 assert checkRep();
993 assert SwingUtilities.isEventDispatchThread();
994 assert getMode() == Mode.BUILD_MODE;
995
996 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
997 Vect3 originalPosition = currentGizmo.getPosition();
998 boolean newPositionFound = false;
999
1000 zPositionLoop:
1001 while ((currentGizmo instanceof Ball) ?
1002 (currentGizmo.getPosition().z() <= 10.0 - 0.25 - step) :
1003 (GizmoBoundCalculator.ceilZ(currentGizmo) <= 10.0 - step)) {
1004 currentGizmo.move(new Vect3(0, 0, step));
1005
1006 for (Gizmo gizmo : board) {
1007 if (!(gizmo instanceof OuterWalls) &&
1008 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
1009 continue zPositionLoop;
1010 }
1011 }
1012
1013 // If we got here, then no overlaps found
1014 newPositionFound = true;
1015 break;
1016 }
1017
1018 if (newPositionFound) {
1019 boardView.redraw();
1020 } else {
1021 currentGizmo.moveTo(originalPosition);
1022 }
1023
1024 assert checkRep();
1025 }
1026
1027 /**
1028 * Moves the currently selected Gizmo forward in building mode. This
1029 * corresponds with a decrese in the z coordinate.
1030 */
1031 public void moveCurrentGizmoForward() {
1032 assert checkRep();
1033 assert SwingUtilities.isEventDispatchThread();
1034 assert getMode() == Mode.BUILD_MODE;
1035
1036 double step = (currentGizmo instanceof Ball) ? 0.05 : 1.0;
1037 Vect3 originalPosition = currentGizmo.getPosition();
1038 boolean newPositionFound = false;
1039
1040 zPositionLoop:
1041 while (currentGizmo.getPosition().z() >= step) {
1042 currentGizmo.move(new Vect3(0, 0, -step));
1043
1044 for (Gizmo gizmo : board) {
1045 if (!(gizmo instanceof OuterWalls) &&
1046 GizmoBoundCalculator.doOverlap(currentGizmo, gizmo)) {
1047 continue zPositionLoop;
1048 }
1049 }
1050
1051 // If we got here, then no overlaps found
1052 newPositionFound = true;
1053 break;
1054 }
1055
1056 if (newPositionFound) {
1057 boardView.redraw();
1058 } else {
1059 currentGizmo.moveTo(originalPosition);
1060 }
1061
1062 assert checkRep();
1063 }
1064
1065 /**
1066 * Rotates the currently selected Gizmo clockwise by 90 degrees.
1067 */
1068 public void rotateCurrentGizmoClockwise() {
1069 assert checkRep();
1070 assert SwingUtilities.isEventDispatchThread();
1071 assert getMode() == Mode.BUILD_MODE;
1072
1073 currentGizmo.setOrientation(
1074 (currentGizmo.getOrientation() - 90 + 360) % 360);
1075 boardView.redraw();
1076
1077 assert checkRep();
1078 }
1079
1080 /**
1081 * Rotates the currently selected Gizmo counterclockwise by 90
1082 * degrees.
1083 */
1084 public void rotateCurrentGizmoCounterclockwise() {
1085 assert checkRep();
1086 assert SwingUtilities.isEventDispatchThread();
1087 assert getMode() == Mode.BUILD_MODE;
1088
1089 currentGizmo.setOrientation(
1090 (currentGizmo.getOrientation() + 90) % 360);
1091 boardView.redraw();
1092
1093 assert checkRep();
1094 }
1095
1096 /**
1097 * Delete currently selected Gizmo.
1098 */
1099 public void deleteCurrentGizmo() {
1100 assert checkRep();
1101 assert SwingUtilities.isEventDispatchThread();
1102 assert getMode() == Mode.BUILD_MODE;
1103
1104 Gizmo gizmoToDelete = currentGizmo;
1105
1106 buildModePanel.gizmosCombo.setSelectedIndex(
1107 (buildModePanel.gizmosCombo.getSelectedIndex() + 1) %
1108 buildModePanel.gizmosCombo.getItemCount());
1109
1110 board.removeGizmo(gizmoToDelete);
1111 // FIXME: Need to de-register all trigger pairs this gizmo is a
1112 // part of...
1113
1114 buildModePanel.refreshGizmosCombo();
1115
1116 assert checkRep();
1117 }
1118
1119
1120 /*------------------------------------------------------------------
1121 * Board menu item handlers
1122 *----------------------------------------------------------------*/
1123
1124 /**
1125 * Loads a new, empty <code>Board</code>.
1126 *
1127 * @requires <code>isClockRunning = false</code>
1128 * @effects <code>board</code> is a new, empty <code>Board</code>
1129 */
1130 public void newBoard() {
1131 assert checkRep();
1132 assert SwingUtilities.isEventDispatchThread();
1133
1134 // TODO: Discard old Board, Clock, and SwingKeyboardInput.
1135 // Create new Board and hook it up to a new Clock and a new
1136 // SwingKeyboardInput.
1137 // make sure the user wants to do this
1138 Object[] options = {"No!",
1139 "Yes"};
1140 int makeNewBoard = JOptionPane.showOptionDialog(this,
1141 "Are you sure you want to create\n"
1142 + "a NEW board? You will lose all.\n"
1143 + "unsaved changes",
1144 "Create a new Board",
1145 JOptionPane.YES_NO_OPTION,
1146 JOptionPane.QUESTION_MESSAGE,
1147 null, //don't use a custom Icon
1148 options, //the titles of buttons
1149 options[0]); //default button title
1150 switch(makeNewBoard) {
1151 case 0: break;
1152 case 1: {
1153 String newBoardName = (String)JOptionPane.showInputDialog(
1154 this,
1155 "What would you like to:\n"
1156 + "call the new Board?",
1157 "Board Name",
1158 JOptionPane.PLAIN_MESSAGE,
1159 null,
1160 null,
1161 "newBoard");
1162
1163 boolean loadSucceeded = true;
1164
1165 // FIXME: Stop clock? or specify that this must be called with a
1166 // stopped clock?
1167
1168 // TODO: Do this in a separate thread...
1169
1170 // FIXME: Use new XMLLoadException once that's supported...
1171 Vector<Integer> xyz = new Vector<Integer>(3);
1172 Vector<Float> frictionVec = new Vector<Float>(2);
1173 Vect3 grav;
1174 float friction1, friction2;
1175 int timeStep;
1176 Display testDisplay;
1177 SwingKeyboardInput testInput;
1178
1179 xyz.add(0, 20);
1180 xyz.add(1, 20);
1181 xyz.add(2, 10);
1182 friction1 = (float)0.025;
1183 friction2 = (float)0.025;
1184 frictionVec.add(0,friction1);
1185 frictionVec.add(1,friction2);
1186 grav = new Vect3(0,25,0);
1187 timeStep = 40; // 40 ms = 25 f.p.s., which is how fast the Diablo
1188 // II engine ran at.
1189 testDisplay = new Display();
1190 testInput = new SwingKeyboardInput();
1191 Board newBoard = new Board(xyz,grav,frictionVec,testDisplay,testInput);
1192
1193 boardCanvas.addKeyListener(testInput.getKeyListener());
1194 try {
1195 BoardBuilderXML builder =
1196 new BoardBuilderXML(new PreliminaryReleaseErrorHandler());
1197 builder.buildFileFromBoard(newBoard, new File(newBoardName));
1198 } catch (XMLLoadException e) {
1199 loadSucceeded = false;
1200 e.printStackTrace();
1201 }
1202
1203 if (loadSucceeded) {
1204 board = newBoard;
1205 board.setDisplayMode(this);
1206 board.setInputMode(testInput);
1207 boardView.setBoard(board);
1208 clock = new JavaUtilTimerClock(board, clockTickSpeed);
1209 clock.addBoardView(boardView);
1210 buildModePanel.refreshGizmosCombo();
1211 boardView.redraw();
1212 } else {
1213 JOptionPane.showMessageDialog(this,
1214 "An error occurred in creating " + newBoardName + ".", "File Error",
1215 JOptionPane.ERROR_MESSAGE);
1216 }
1217
1218 assert checkRep();
1219 }
1220 break;
1221 default: break;
1222 }
1223
1224
1225 if (debug>0)
1226 System.out.println("newBoard() was called.");
1227
1228 assert checkRep();
1229 }
1230
1231 /**
1232 * Queries the user for a file and loads a <code>Board</code> from
1233 * that file.
1234 *
1235 * @requires <code>isClockRunning = false</code>
1236 * @effects <code>board</code> is the new <code>Board</code> loaded
1237 * from the requested file if the entire procedure was
1238 * successful, unchanged otherwise
1239 */
1240 public void openFromBoardMenu() {
1241 assert checkRep();
1242 assert SwingUtilities.isEventDispatchThread();
1243
1244 int returnVal = fileChooser.showOpenDialog(this);
1245
1246 if (returnVal == JFileChooser.APPROVE_OPTION) {
1247 File file = fileChooser.getSelectedFile();
1248 loadBoardFromFile(file);
1249 }
1250
1251 assert checkRep();
1252 }
1253
1254 /**
1255 * Saves the <code>Board</code> to the file it was loaded from. If
1256 * this <code>Board</code> was originally created by using {@link
1257 * #newBoard}, then prompt the user for a file to save it to.
1258 *
1259 * @requires <code>isClockRunning = false</code>
1260 * @effects If <code>boardFile</code> is not set, and if the user
1261 * selects a file, then <code>boardFile</code> is set to
1262 * the selected file.
1263 */
1264 public void saveFromBoardMenu() {
1265 assert checkRep();
1266 assert SwingUtilities.isEventDispatchThread();
1267
1268 if (boardFile == null) {
1269 // First go to Save As to get a filename to save to
1270 saveAsFromBoardMenu();
1271 } else {
1272 if (currentGizmo != null) {
1273 try {
1274 board.addGizmo(currentGizmo);
1275 } catch (NonUniqueGizmoNameException e) {
1276 e.printStackTrace(); // Cry
1277 }
1278 }
1279
1280 try {
1281 BoardBuilderXML builder =
1282 new BoardBuilderXML(new PreliminaryReleaseErrorHandler());
1283 builder.buildFileFromBoard(board, boardFile);
1284 } catch (XMLLoadException e) {
1285 e.printStackTrace();
1286 JOptionPane.showMessageDialog(this,
1287 "An unexpected error occurred in trying to save to " +
1288 boardFile, "Save Error", JOptionPane.ERROR_MESSAGE);
1289 }
1290
1291 if (currentGizmo != null) {
1292 board.removeGizmo(currentGizmo);
1293 }
1294 }
1295
1296 assert checkRep();
1297 }
1298
1299 /**
1300 * Queries the user for a file location and saves the
1301 * <code>Board</code> to that file.
1302 *
1303 * @requires <code>isClockRunning = false</code>
1304 * @effects If the user selects a file, then <code>boardFile</code>
1305 * is set to the selected file.
1306 */
1307 public void saveAsFromBoardMenu() {
1308 assert checkRep();
1309 assert SwingUtilities.isEventDispatchThread();
1310
1311 int returnVal = fileChooser.showSaveDialog(this);
1312
1313 if (returnVal == JFileChooser.APPROVE_OPTION) {
1314 boardFile = fileChooser.getSelectedFile();
1315 saveFromBoardMenu();
1316 }
1317
1318 assert checkRep();
1319 }
1320
1321
1322 /*------------------------------------------------------------------
1323 * Add Gizmo menu item handlers
1324 *----------------------------------------------------------------*/
1325
1326 /**
1327 * Adds a <code>SquareBumper</code> to the currently loaded
1328 * <code>Board</code>.
1329 *
1330 * @requires <code>mode = Mode.BUILD_MODE</code>
1331 * @effects <code>board</code> has a <code>SquareBumper</code> added
1332 * to it.
1333 */
1334 public void addSquareBumperToBoard() {
1335 assert checkRep();
1336 assert SwingUtilities.isEventDispatchThread();
1337 assert getMode() == Mode.BUILD_MODE;
1338
1339 String name = JOptionPane.showInputDialog(this,
1340 "What would you like to name the new square bumper?",
1341 "Add Square Bumper", JOptionPane.QUESTION_MESSAGE);
1342
1343 if (name == null) {
1344 // User hit cancel
1345 assert checkRep();
1346 return;
1347 }
1348
1349 if (board.getNames().contains(name) ||
1350 (currentGizmo != null &&
1351 name.equals(currentGizmo.getName()))) {
1352 JOptionPane.showMessageDialog(this,
1353 "There already is a gizmo named " + name + ".",
1354 "Naming Error", JOptionPane.ERROR_MESSAGE);
1355 } else {
1356 Gizmo newGizmo = new SquareBumper(new Vect3(0, 0, 0));
1357 newGizmo.setName(name);
1358
1359 boolean placeWasFound = false;
1360 zLocationLoop:
1361 for (int z = 9; z >= 0; z--) {
1362 for (int y = 19; y >= 0; y--) {
1363 xLocationLoop:
1364 for (int x = 19; x >= 0; x--) {
1365 newGizmo.moveTo(new Vect3(x, y, z));
1366
1367 for (Gizmo g : board) {
1368 if (!(g instanceof OuterWalls) &&
1369 (GizmoBoundCalculator.doOverlap(
1370 newGizmo, g))) {
1371 continue xLocationLoop;
1372 }
1373 }
1374
1375 if (currentGizmo != null &&
1376 (GizmoBoundCalculator.doOverlap(
1377 newGizmo, currentGizmo))) {
1378 continue xLocationLoop;
1379 }
1380
1381 placeWasFound = true;
1382 break zLocationLoop;
1383 }
1384 }
1385 }
1386
1387 if (placeWasFound) {
1388 try {
1389 board.addGizmo(newGizmo);
1390 } catch (NonUniqueGizmoNameException e) {
1391 e.printStackTrace(); // Cry
1392 }
1393 buildModePanel.refreshGizmosCombo();
1394 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1395 // The setSelection will indirectly trigger a redraw...
1396 } else {
1397 JOptionPane.showMessageDialog(this,
1398 "A space could not be found to add the new gizmo.",
1399 "Space Error", JOptionPane.ERROR_MESSAGE);
1400 }
1401 }
1402
1403 assert checkRep();
1404 }
1405
1406 /**
1407 * Adds a <code>CircularBumper</code> to the currently loaded
1408 * <code>Board</code>.
1409 *
1410 * @requires <code>mode = Mode.BUILD_MODE</code>
1411 * @effects <code>board</code> has a <code>CircularBumper</code>
1412 * added to it.
1413 */
1414 public void addCircularBumperToBoard() {
1415 assert checkRep();
1416 assert SwingUtilities.isEventDispatchThread();
1417 assert getMode() == Mode.BUILD_MODE;
1418
1419 String name = JOptionPane.showInputDialog(this,
1420 "What would you like to name the new circular bumper?",
1421 "Add Circular Bumper", JOptionPane.QUESTION_MESSAGE);
1422
1423 if (name == null) {
1424 // User hit cancel
1425 assert checkRep();
1426 return;
1427 }
1428
1429 if (board.getNames().contains(name) ||
1430 (currentGizmo != null &&
1431 name.equals(currentGizmo.getName()))) {
1432 JOptionPane.showMessageDialog(this,
1433 "There already is a gizmo named " + name + ".",
1434 "Naming Error", JOptionPane.ERROR_MESSAGE);
1435 } else {
1436 Gizmo newGizmo = new CircularBumper(new Vect3(0, 0, 0));
1437 newGizmo.setName(name);
1438
1439 boolean placeWasFound = false;
1440 zLocationLoop:
1441 for (int z = 9; z >= 0; z--) {
1442 for (int y = 19; y >= 0; y--) {
1443 xLocationLoop:
1444 for (int x = 19; x >= 0; x--) {
1445 newGizmo.moveTo(new Vect3(x, y, z));
1446
1447 for (Gizmo g : board) {
1448 if (!(g instanceof OuterWalls) &&
1449 (GizmoBoundCalculator.doOverlap(
1450 newGizmo, g))) {
1451 continue xLocationLoop;
1452 }
1453 }
1454
1455 if (currentGizmo != null &&
1456 (GizmoBoundCalculator.doOverlap(
1457 newGizmo, currentGizmo))) {
1458 continue xLocationLoop;
1459 }
1460
1461 placeWasFound = true;
1462 break zLocationLoop;
1463 }
1464 }
1465 }
1466
1467 if (placeWasFound) {
1468 try {
1469 board.addGizmo(newGizmo);
1470 } catch (NonUniqueGizmoNameException e) {
1471 e.printStackTrace(); // Cry
1472 }
1473 buildModePanel.refreshGizmosCombo();
1474 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1475 // The setSelection will indirectly trigger a redraw...
1476 } else {
1477 JOptionPane.showMessageDialog(this,
1478 "A space could not be found to add the new gizmo.",
1479 "Space Error", JOptionPane.ERROR_MESSAGE);
1480 }
1481 }
1482
1483 assert checkRep();
1484 }
1485
1486 /**
1487 * Adds a <code>TriangularBumper</code> to the currently loaded
1488 * <code>Board</code>.
1489 *
1490 * @requires <code>mode = Mode.BUILD_MODE</code>
1491 * @effects <code>board</code> has a <code>TriangularBumper</code>
1492 * added to it.
1493 */
1494 public void addTriangularBumperToBoard() {
1495 assert checkRep();
1496 assert SwingUtilities.isEventDispatchThread();
1497 assert getMode() == Mode.BUILD_MODE;
1498
1499 String name = JOptionPane.showInputDialog(this,
1500 "What would you like to name the new triangular bumper?",
1501 "Add Triangular Bumper", JOptionPane.QUESTION_MESSAGE);
1502
1503 if (name == null) {
1504 // User hit cancel
1505 assert checkRep();
1506 return;
1507 }
1508
1509 if (board.getNames().contains(name) ||
1510 (currentGizmo != null &&
1511 name.equals(currentGizmo.getName()))) {
1512 JOptionPane.showMessageDialog(this,
1513 "There already is a gizmo named " + name + ".",
1514 "Naming Error", JOptionPane.ERROR_MESSAGE);
1515 } else {
1516 Gizmo newGizmo = new TriangularBumper(new Vect3(0, 0, 0), 0);
1517 newGizmo.setName(name);
1518
1519 boolean placeWasFound = false;
1520 zLocationLoop:
1521 for (int z = 9; z >= 0; z--) {
1522 for (int y = 19; y >= 0; y--) {
1523 xLocationLoop:
1524 for (int x = 19; x >= 0; x--) {
1525 newGizmo.moveTo(new Vect3(x, y, z));
1526
1527 for (Gizmo g : board) {
1528 if (!(g instanceof OuterWalls) &&
1529 (GizmoBoundCalculator.doOverlap(
1530 newGizmo, g))) {
1531 continue xLocationLoop;
1532 }
1533 }
1534
1535 if (currentGizmo != null &&
1536 (GizmoBoundCalculator.doOverlap(
1537 newGizmo, currentGizmo))) {
1538 continue xLocationLoop;
1539 }
1540
1541 placeWasFound = true;
1542 break zLocationLoop;
1543 }
1544 }
1545 }
1546
1547 if (placeWasFound) {
1548 try {
1549 board.addGizmo(newGizmo);
1550 } catch (NonUniqueGizmoNameException e) {
1551 e.printStackTrace(); // Cry
1552 }
1553 buildModePanel.refreshGizmosCombo();
1554 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1555 // The setSelection will indirectly trigger a redraw...
1556 } else {
1557 JOptionPane.showMessageDialog(this,
1558 "A space could not be found to add the new gizmo.",
1559 "Space Error", JOptionPane.ERROR_MESSAGE);
1560 }
1561 }
1562
1563 assert checkRep();
1564 }
1565
1566 /**
1567 * Adds a <code>LeftFlipper</code> to the currently loaded
1568 * <code>Board</code>.
1569 *
1570 * @requires <code>mode = Mode.BUILD_MODE</code>
1571 * @effects <code>board</code> has a <code>LeftFlipper</code> added
1572 * to it.
1573 */
1574 public void addLeftFlipperToBoard() {
1575 assert checkRep();
1576 assert SwingUtilities.isEventDispatchThread();
1577 assert getMode() == Mode.BUILD_MODE;
1578
1579 String name = JOptionPane.showInputDialog(this,
1580 "What would you like to name the new left flipper?",
1581 "Add Left Flipper", JOptionPane.QUESTION_MESSAGE);
1582
1583 if (name == null) {
1584 // User hit cancel
1585 assert checkRep();
1586 return;
1587 }
1588
1589 if (board.getNames().contains(name) ||
1590 (currentGizmo != null &&
1591 name.equals(currentGizmo.getName()))) {
1592 JOptionPane.showMessageDialog(this,
1593 "There already is a gizmo named " + name + ".",
1594 "Naming Error", JOptionPane.ERROR_MESSAGE);
1595 } else {
1596 Gizmo newGizmo = new LeftFlipper(new Vect3(0, 0, 0), 0);
1597 newGizmo.setName(name);
1598
1599 boolean placeWasFound = false;
1600 zLocationLoop:
1601 for (int z = 9; z >= 0; z--) {
1602 for (int y = 18; y >= 0; y--) {
1603 xLocationLoop:
1604 for (int x = 18; x >= 0; x--) {
1605 newGizmo.moveTo(new Vect3(x, y, z));
1606
1607 for (Gizmo g : board) {
1608 if (!(g instanceof OuterWalls) &&
1609 (GizmoBoundCalculator.doOverlap(
1610 newGizmo, g))) {
1611 continue xLocationLoop;
1612 }
1613 }
1614
1615 if (currentGizmo != null &&
1616 (GizmoBoundCalculator.doOverlap(
1617 newGizmo, currentGizmo))) {
1618 continue xLocationLoop;
1619 }
1620
1621 placeWasFound = true;
1622 break zLocationLoop;
1623 }
1624 }
1625 }
1626
1627 if (placeWasFound) {
1628 try {
1629 board.addGizmo(newGizmo);
1630 } catch (NonUniqueGizmoNameException e) {
1631 e.printStackTrace(); // Cry
1632 }
1633 buildModePanel.refreshGizmosCombo();
1634 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1635 // The setSelection will indirectly trigger a redraw...
1636 } else {
1637 JOptionPane.showMessageDialog(this,
1638 "A space could not be found to add the new gizmo.",
1639 "Space Error", JOptionPane.ERROR_MESSAGE);
1640 }
1641 }
1642
1643 assert checkRep();
1644 }
1645
1646 /**
1647 * Adds a <code>RightFlipper</code> to the currently loaded
1648 * <code>Board</code>.
1649 *
1650 * @requires <code>mode = Mode.BUILD_MODE</code>
1651 * @effects <code>board</code> has a <code>RightFlipper</code> added
1652 * to it.
1653 */
1654 public void addRightFlipperToBoard() {
1655 assert checkRep();
1656 assert SwingUtilities.isEventDispatchThread();
1657 assert getMode() == Mode.BUILD_MODE;
1658
1659 String name = JOptionPane.showInputDialog(this,
1660 "What would you like to name the new right flipper?",
1661 "Add Right Flipper", JOptionPane.QUESTION_MESSAGE);
1662
1663 if (name == null) {
1664 // User hit cancel
1665 assert checkRep();
1666 return;
1667 }
1668
1669 if (board.getNames().contains(name) ||
1670 (currentGizmo != null &&
1671 name.equals(currentGizmo.getName()))) {
1672 JOptionPane.showMessageDialog(this,
1673 "There already is a gizmo named " + name + ".",
1674 "Naming Error", JOptionPane.ERROR_MESSAGE);
1675 } else {
1676 Gizmo newGizmo = new RightFlipper(new Vect3(0, 0, 0), 0);
1677 newGizmo.setName(name);
1678
1679 boolean placeWasFound = false;
1680 zLocationLoop:
1681 for (int z = 9; z >= 0; z--) {
1682 for (int y = 18; y >= 0; y--) {
1683 xLocationLoop:
1684 for (int x = 18; x >= 0; x--) {
1685 newGizmo.moveTo(new Vect3(x, y, z));
1686
1687 for (Gizmo g : board) {
1688 if (!(g instanceof OuterWalls) &&
1689 (GizmoBoundCalculator.doOverlap(
1690 newGizmo, g))) {
1691 continue xLocationLoop;
1692 }
1693 }
1694
1695 if (currentGizmo != null &&
1696 (GizmoBoundCalculator.doOverlap(
1697 newGizmo, currentGizmo))) {
1698 continue xLocationLoop;
1699 }
1700
1701 placeWasFound = true;
1702 break zLocationLoop;
1703 }
1704 }
1705 }
1706
1707 if (placeWasFound) {
1708 try {
1709 board.addGizmo(newGizmo);
1710 } catch (NonUniqueGizmoNameException e) {
1711 e.printStackTrace(); // Cry
1712 }
1713 buildModePanel.refreshGizmosCombo();
1714 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1715 // The setSelection will indirectly trigger a redraw...
1716 } else {
1717 JOptionPane.showMessageDialog(this,
1718 "A space could not be found to add the new gizmo.",
1719 "Space Error", JOptionPane.ERROR_MESSAGE);
1720 }
1721 }
1722
1723 assert checkRep();
1724 }
1725
1726 /**
1727 * Adds an <code>Absorber</code> to the currently loaded
1728 * <code>Board</code>.
1729 *
1730 * @requires <code>mode = Mode.BUILD_MODE</code>
1731 * @effects <code>board</code> has an <code>Absorber</code> added to
1732 * it.
1733 */
1734 public void addAbsorberToBoard() {
1735 assert checkRep();
1736 assert SwingUtilities.isEventDispatchThread();
1737 assert getMode() == Mode.BUILD_MODE;
1738
1739 // XXX: Four dialogs to create an absorber is really annoying...
1740
1741 String name = JOptionPane.showInputDialog(this,
1742 "What would you like to name the new absorber?",
1743 "Add Absorber (Step 1 of 4)", JOptionPane.QUESTION_MESSAGE);
1744 if (name == null) {
1745 // User hit cancel
1746 assert checkRep();
1747 return;
1748 } else if (board.getNames().contains(name) ||
1749 (currentGizmo != null &&
1750 name.equals(currentGizmo.getName()))) {
1751 JOptionPane.showMessageDialog(this,
1752 "There already is a gizmo named " + name + ".",
1753 "Naming Error", JOptionPane.ERROR_MESSAGE);
1754 assert checkRep();
1755 return;
1756 }
1757
1758 String widthString = JOptionPane.showInputDialog(this,
1759 "What would you like the width of the new absorber to be?",
1760 "Add Absorber (Step 2 of 4)", JOptionPane.QUESTION_MESSAGE);
1761 if (widthString == null) {
1762 // User hit cancle
1763 assert checkRep();
1764 return;
1765 }
1766 int width;
1767 try {
1768 width = Integer.parseInt(widthString);
1769 } catch (NumberFormatException e) {
1770 JOptionPane.showMessageDialog(this,
1771 widthString + " is not an integer.", "Format Error",
1772 JOptionPane.ERROR_MESSAGE);
1773 assert checkRep();
1774 return;
1775 }
1776 if (width <= 0) {
1777 JOptionPane.showMessageDialog(this,
1778 "The width must be a positive integer.", "Sign Error",
1779 JOptionPane.ERROR_MESSAGE);
1780 }
1781
1782 String heightString = JOptionPane.showInputDialog(this,
1783 "What would you like the height of the new absorber to be?",
1784 "Add Absorber (Step 3 of 4)", JOptionPane.QUESTION_MESSAGE);
1785 if (heightString == null) {
1786 // User hit cancle
1787 assert checkRep();
1788 return;
1789 }
1790 int height;
1791 try {
1792 height = Integer.parseInt(heightString);
1793 } catch (NumberFormatException e) {
1794 JOptionPane.showMessageDialog(this,
1795 heightString + " is not an integer.", "Format Error",
1796 JOptionPane.ERROR_MESSAGE);
1797 assert checkRep();
1798 return;
1799 }
1800 if (height <= 0) {
1801 JOptionPane.showMessageDialog(this,
1802 "The height must be a positive integer.", "Sign Error",
1803 JOptionPane.ERROR_MESSAGE);
1804 }
1805
1806 String depthString = JOptionPane.showInputDialog(this,
1807 "What would you like the depth of the new absorber to be?",
1808 "Add Absorber (Step 4 of 4)", JOptionPane.QUESTION_MESSAGE);
1809 if (depthString == null) {
1810 // User hit cancle
1811 assert checkRep();
1812 return;
1813 }
1814 int depth;
1815 try {
1816 depth = Integer.parseInt(depthString);
1817 } catch (NumberFormatException e) {
1818 JOptionPane.showMessageDialog(this,
1819 depthString + " is not an integer.", "Format Error",
1820 JOptionPane.ERROR_MESSAGE);
1821 assert checkRep();
1822 return;
1823 }
1824 if (depth <= 0) {
1825 JOptionPane.showMessageDialog(this,
1826 "The depth must be a positive integer.", "Sign Error",
1827 JOptionPane.ERROR_MESSAGE);
1828 }
1829
1830
1831 Gizmo newGizmo = new Absorber(new Vect3(0, 0, 0),
1832 width, height, depth);
1833 newGizmo.setName(name);
1834
1835 boolean placeWasFound = false;
1836 zLocationLoop:
1837 for (int z = 10 - depth; z >= 0; z--) {
1838 for (int y = 20 - height; y >= 0; y--) {
1839 xLocationLoop:
1840 for (int x = 20 - width; x >= 0; x--) {
1841 newGizmo.moveTo(new Vect3(x, y, z));
1842
1843 for (Gizmo g : board) {
1844 if (!(g instanceof OuterWalls) &&
1845 (GizmoBoundCalculator.doOverlap(
1846 newGizmo, g))) {
1847 continue xLocationLoop;
1848 }
1849 }
1850
1851 if (currentGizmo != null &&
1852 (GizmoBoundCalculator.doOverlap(
1853 newGizmo, currentGizmo))) {
1854 continue xLocationLoop;
1855 }
1856
1857 placeWasFound = true;
1858 break zLocationLoop;
1859 }
1860 }
1861 }
1862
1863 if (placeWasFound) {
1864 try {
1865 board.addGizmo(newGizmo);
1866 } catch (NonUniqueGizmoNameException e) {
1867 e.printStackTrace(); // Cry
1868 }
1869 buildModePanel.refreshGizmosCombo();
1870 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1871 // The setSelection will indirectly trigger a redraw...
1872 } else {
1873 JOptionPane.showMessageDialog(this,
1874 "A space could not be found to add the new gizmo.",
1875 "Space Error", JOptionPane.ERROR_MESSAGE);
1876 }
1877
1878 assert checkRep();
1879 }
1880
1881 /**
1882 * Adds an <code>Ball</code> to the currently loaded
1883 * <code>Board</code>.
1884 *
1885 * @requires <code>mode = Mode.BUILD_MODE</code>
1886 * @effects <code>board</code> has an <code>Ball</code> added to it.
1887 */
1888 public void addBallToBoard() {
1889 assert checkRep();
1890 assert SwingUtilities.isEventDispatchThread();
1891 assert getMode() == Mode.BUILD_MODE;
1892
1893 if (debug > 0)
1894 System.out.println("addBallToBoard() was called.");
1895
1896 assert getMode() == Mode.BUILD_MODE;
1897
1898 String name = JOptionPane.showInputDialog(this,
1899 "What would you like to name the new ball?",
1900 "Add Ball", JOptionPane.QUESTION_MESSAGE);
1901
1902 if (name == null) {
1903 // User hit cancel
1904 assert checkRep();
1905 return;
1906 }
1907
1908 if (board.getNames().contains(name) ||
1909 (currentGizmo != null &&
1910 name.equals(currentGizmo.getName()))) {
1911 JOptionPane.showMessageDialog(this,
1912 "There already is a gizmo named " + name + ".",
1913 "Naming Error", JOptionPane.ERROR_MESSAGE);
1914 } else {
1915 //String xPos = JOptionPane.showInputDialog(this,
1916 // "Starting X coordinate?",
1917 // "Add Ball", JOptionPane.QUESTION_MESSAGE);
1918
1919
1920 Gizmo newGizmo = new Ball(new Vect3(0, 0, 0), new Vect3(0,0,0));
1921 newGizmo.setName(name);
1922
1923 boolean placeWasFound = false;
1924 zLocationLoop:
1925 for (int z = 9; z >= 0; z--) {
1926 for (int y = 18; y >= 0; y--) {
1927 xLocationLoop:
1928 for (int x = 18; x >= 0; x--) {
1929 newGizmo.moveTo(new Vect3(x, y, z));
1930
1931 for (Gizmo g : board) {
1932 if (!(g instanceof OuterWalls) &&
1933 (GizmoBoundCalculator.doOverlap(
1934 newGizmo, g))) {
1935 continue xLocationLoop;
1936 }
1937 }
1938
1939 if (currentGizmo != null &&
1940 (GizmoBoundCalculator.doOverlap(
1941 newGizmo, currentGizmo))) {
1942 continue xLocationLoop;
1943 }
1944
1945 placeWasFound = true;
1946 break zLocationLoop;
1947 }
1948 }
1949 }
1950
1951 if (placeWasFound) {
1952 try {
1953 board.addGizmo(newGizmo);
1954 } catch (NonUniqueGizmoNameException e) {
1955 e.printStackTrace(); // Cry
1956 }
1957 buildModePanel.refreshGizmosCombo();
1958 buildModePanel.gizmosCombo.setSelectedItem(newGizmo.getName());
1959 // The setSelection will indirectly trigger a redraw...
1960 } else {
1961 JOptionPane.showMessageDialog(this,
1962 "A space could not be found to add the new gizmo.",
1963 "Space Error", JOptionPane.ERROR_MESSAGE);
1964 }
1965 }
1966
1967
1968 assert checkRep();
1969 }
1970
1971
1972 /*------------------------------------------------------------------
1973 * Connections menu item handlers
1974 *----------------------------------------------------------------*/
1975
1976 /**
1977 * Provides the user an interface to modify gizmo-gizmo connections.
1978 */
1979 public void gizmoConnectModify() {
1980
1981 assert checkRep();
1982 assert SwingUtilities.isEventDispatchThread();
1983
1984 String sourceGizmo = JOptionPane.showInputDialog(this,
1985 "What is the name of the source gizmo?"
1986 , JOptionPane.QUESTION_MESSAGE);
1987 String targetGizmo = JOptionPane.showInputDialog(this,
1988 "What is the name of the target gizmo?"
1989 , JOptionPane.QUESTION_MESSAGE);
1990
1991
1992 if((board.getNames().contains(sourceGizmo) || currentGizmo == null ||
1993 currentGizmo.getName().equals(sourceGizmo))
1994 && (board.getNames().contains(targetGizmo) || currentGizmo ==
1995 null || currentGizmo.getName().equals(targetGizmo))){
1996
1997 Gizmo sourceGizmoObject;
1998 Gizmo targetGizmoObject;
1999
2000 if(!sourceGizmo.equals(currentGizmo.getName())){
2001 sourceGizmoObject = board.checkoutGizmo(sourceGizmo);
2002 }
2003 else{
2004 sourceGizmoObject = currentGizmo;
2005 }
2006 if(!targetGizmo.equals(currentGizmo.getName())){
2007 targetGizmoObject = board.checkoutGizmo(targetGizmo);
2008 }
2009 else{
2010 targetGizmoObject = currentGizmo;
2011 }
2012 try{
2013 targetGizmoObject.connectToTriggerGenerator(sourceGizmoObject);
2014 if(!sourceGizmoObject.equals(targetGizmoObject)){
2015 board.addGizmo(sourceGizmoObject);
2016 }
2017 board.addGizmo(targetGizmoObject);
2018 }
2019 catch(Exception e)
2020 {
2021 JOptionPane.showMessageDialog(this, "Connect failed");
2022 }
2023 }
2024 else{
2025 JOptionPane.showMessageDialog(this, "Invalid Gizmo name(s), not found "
2026 + "in board");
2027 }
2028
2029 assert checkRep();
2030 }
2031
2032 /**
2033 * Provides the user an interface to modify key connections.
2034 */
2035 public void keyConnectModify() {
2036 assert checkRep();
2037 assert SwingUtilities.isEventDispatchThread();
2038
2039 String asciiInput = JOptionPane.showInputDialog(this,
2040 "What is the ASCII key code of the key you want to modify?",
2041 "Edit Keyboard Connections", JOptionPane.QUESTION_MESSAGE);
2042
2043 if (asciiInput == null) {
2044 // User pressed Cancel
2045 assert checkRep();
2046 return;
2047 }
2048
2049 int asciiCode;
2050 try {
2051 asciiCode = Integer.parseInt(asciiInput);
2052 } catch (NumberFormatException e) {
2053 JOptionPane.showMessageDialog(this,
2054 "The ASCII key code must be a number between 0 and 127.",
2055 "Format Error", JOptionPane.ERROR_MESSAGE);
2056 assert checkRep();
2057 return;
2058 }
2059
2060 if (asciiCode < 0 || asciiCode > 127) {
2061 JOptionPane.showMessageDialog(this,
2062 "The ASCII key code must be a number between 0 and 127.",
2063 "Format Error", JOptionPane.ERROR_MESSAGE);
2064 assert checkRep();
2065 return;
2066 }
2067
2068 Object[] options = { "Key Pressed", "Key Released" };
2069 int n = JOptionPane.showOptionDialog(this,
2070 "Which type of key events are you interested in?",
2071 "Edit Keyboard Connections", JOptionPane.YES_NO_OPTION,
2072 JOptionPane.QUESTION_MESSAGE, null, options, options[0]);
2073
2074 KeyboardTriggerGenerator ktg;
2075 if (n == 0) {
2076 ktg = keyboardInput.getKeyboardTriggerGenerator(asciiCode,
2077 KeyboardInput.EventType.KEY_UP);
2078 } else {
2079 ktg = keyboardInput.getKeyboardTriggerGenerator(asciiCode,
2080 KeyboardInput.EventType.KEY_DOWN);
2081 }
2082
2083 String gizmoName = JOptionPane.showInputDialog(this,
2084 "What is the name of the gizmo you'd like to be trigger by this key?",
2085 "Edit Keyboard Connections", JOptionPane.QUESTION_MESSAGE);
2086
2087 if (gizmoName == null) {
2088 assert checkRep();
2089 return;
2090 }
2091
2092 if (currentGizmo != null && gizmoName.equals(currentGizmo.getName())) {
2093 currentGizmo.connectToTriggerGenerator(ktg);
2094 } else if (board.getNames().contains(gizmoName)) {
2095 Gizmo gizmo = board.checkoutGizmo(gizmoName);
2096 gizmo.connectToTriggerGenerator(ktg);
2097 try {
2098 board.addGizmo(gizmo);
2099 } catch (NonUniqueGizmoNameException e) {
2100 e.printStackTrace(); // Cry
2101 }
2102 } else {
2103 JOptionPane.showMessageDialog(this,
2104 "The gizmo " + gizmoName + " was not found.", "Name Error",
2105 JOptionPane.ERROR_MESSAGE);
2106 }
2107
2108 assert checkRep();
2109 }
2110
2111
2112 /*------------------------------------------------------------------
2113 * Help menu item handlers
2114 *----------------------------------------------------------------*/
2115
2116 /**
2117 * Launches a web browser that shows the Gizmoball user guide.
2118 */
2119 public void userGuideFromHelpMenu() {
2120 assert checkRep();
2121 assert SwingUtilities.isEventDispatchThread();
2122
2123 BareBonesBrowserLaunch.openURL("http://web.mit.edu/gleitz/Public/gizmoball/manual/");
2124 if (debug > 0)
2125 System.out.println("userGuideFromHelpMenu() was called.");
2126
2127 assert checkRep();
2128 }
2129
2130
2131 /**
2132 * Shows the About dialog.
2133 */
2134 public void aboutFromHelpMenu() {
2135 assert checkRep();
2136 assert SwingUtilities.isEventDispatchThread();
2137
2138 // display a dialog with icon
2139 JOptionPane.showMessageDialog(null,
2140 "Gizmoball\n" +
2141 "Copyright Gadgix 2007\n" +
2142 "Version 2.1.1\n" +
2143 "$Id: Display.java 691 2007-05-14 23:50:08Z billmag $\n" +
2144 "\n" +
2145 "Created by billmag, eefi, gleitz, and jhawk\n");
2146 if (debug > 0)
2147 System.out.println("aboutFromHelpMenu() was called.");
2148
2149 assert checkRep();
2150 }
2151
2152
2153 /*------------------------------------------------------------------
2154 * public enum Mode
2155 *----------------------------------------------------------------*/
2156
2157 /**
2158 * The different modes the Gizmoball application supports.
2159 */
2160 public enum Mode {
2161
2162 /**
2163 * A mode in which the user may modify the current
2164 * <code>Board</code>.
2165 */
2166 BUILD_MODE,
2167
2168 /**
2169 * A mode in which the application simulates the
2170 * <code>Board</code>.
2171 */
2172 RUN_MODE;
2173 }
2174
2175
2176 /*------------------------------------------------------------------
2177 * private class RunModePanel
2178 *----------------------------------------------------------------*/
2179
2180 // The right bar in running mode.
2181 private class RunModePanel extends JPanel {
2182
2183 // Starts the clock.
2184 private final JButton playButton;
2185
2186 // Stops the clock.
2187 private final JButton pauseButton;
2188
2189 // Resets the board. (Moves the balls back to their initial
2190 // positions and velocities.)
2191 // private final JButton resetButton;
2192
2193 // XXX: To quench a compiler warning; should be set to better
2194 // value later.
2195 private static final long serialVersionUID = 1L;
2196
2197 // Constructs a RunModePanel with all the controls constructed
2198 // and actions hooked up. This RunModePanel will be set up for
2199 // paused mode.
2200 public RunModePanel() {
2201 SpringLayout layout = new SpringLayout();
2202 setLayout(layout);
2203 SpringLayout.Constraints panelCons = layout.getConstraints(this);
2204
2205 // Control construction ------------------------------------
2206 playButton = new JButton("Play");
2207 add(playButton);
2208 SpringLayout.Constraints playButtonCons =
2209 layout.getConstraints(playButton);
2210
2211 pauseButton = new JButton("Pause");
2212 pauseButton.setEnabled(false);
2213 add(pauseButton);
2214 SpringLayout.Constraints pauseButtonCons =
2215 layout.getConstraints(pauseButton);
2216
2217 // resetButton = new JButton("Reset");
2218 // add(resetButton);
2219 // SpringLayout.Constraints resetButtonCons =
2220 // layout.getConstraints(resetButton);
2221
2222
2223 // Control layout ------------------------------------------
2224
2225 Spring padding = Spring.constant(12);
2226
2227 // Line up left edges of all the buttons along the left edge
2228 // of the panel
2229 playButtonCons.setX(Spring.sum(panelCons.getX(), padding));
2230 pauseButtonCons.setX(playButtonCons.getX());
2231 // resetButtonCons.setX(playButtonCons.getX());
2232
2233 // Set widths of all buttons to width of widest one.
2234 // XXX: Pause is currently hardcoded as the widest. Should
2235 // be more flexible...
2236 playButtonCons.setWidth(pauseButtonCons.getWidth());
2237 // resetButtonCons.setWidth(pauseButtonCons.getWidth());
2238
2239 // Put Play, Pause, and Reset at top and space them out
2240 playButtonCons.setY(Spring.sum(panelCons.getY(), padding));
2241 pauseButtonCons.setY(
2242 Spring.sum(playButtonCons.getConstraint(SpringLayout.SOUTH),
2243 padding));
2244 // resetButtonCons.setY(
2245 // Spring.sum(pauseButtonCons.getConstraint(SpringLayout.SOUTH),
2246 // padding));
2247
2248 // Event handling ------------------------------------------
2249
2250 playButton.addActionListener(new ActionListener() {
2251 public void actionPerformed(ActionEvent e) {
2252 startTime();
2253 }
2254 });
2255
2256 pauseButton.addActionListener(new ActionListener() {
2257 public void actionPerformed(ActionEvent e) {
2258 stopTime();
2259 }
2260 });
2261
2262 // resetButton.addActionListener(new ActionListener() {
2263 // public void actionPerformed(ActionEvent e) {
2264 // resetBoard();
2265 // }
2266 // });
2267
2268 }
2269
2270 // Enables or disables buttons depending on whether the clock is
2271 // running. Expects true if the clock is running and false if
2272 // the clock is not running.
2273 public void setClockRunning(boolean isClockRunning) {
2274 if (isClockRunning) {
2275 playButton.setEnabled(false);
2276 pauseButton.setEnabled(true);
2277 // resetButton.setEnabled(false);
2278 } else {
2279 playButton.setEnabled(true);
2280 pauseButton.setEnabled(false);
2281 // resetButton.setEnabled(true);
2282 }
2283 }
2284 }
2285
2286
2287 /*------------------------------------------------------------------
2288 * private class BuildModePanel
2289 *----------------------------------------------------------------*/
2290
2291 // The right bar in building mode.
2292 private class BuildModePanel extends JPanel {
2293
2294 // Combo box containing list of Gizmo names
2295 private final JComboBox gizmosCombo;
2296
2297 // XXX: To quench a compiler warning; should be set to better
2298 // value later.
2299 private static final long serialVersionUID = 1L;
2300
2301 // Constructs a BuildModePanel with all the controls constructed
2302 // and actions hooked up.
2303 private BuildModePanel() {
2304 SpringLayout layout = new SpringLayout();
2305 setLayout(layout);
2306 SpringLayout.Constraints panelCons =
2307 layout.getConstraints(this);
2308
2309 // Control construction ------------------------------------
2310
2311 JLabel gizmosComboLabel = new JLabel("Current gizmo:");
2312 add(gizmosComboLabel);
2313 SpringLayout.Constraints gizmosComboLabelCons =
2314 layout.getConstraints(gizmosComboLabel);
2315
2316 gizmosCombo = new JComboBox();
2317 add(gizmosCombo);
2318 SpringLayout.Constraints gizmosComboCons =
2319 layout.getConstraints(gizmosCombo);
2320
2321 // JButton rename = new JButton("Rename Gizmo");
2322 // add(rename);
2323 // SpringLayout.Constraints renameCons =
2324 // layout.getConstraints(rename);
2325
2326 JLabel moveLabel = new JLabel("Move Gizmo");
2327 add(moveLabel);
2328 SpringLayout.Constraints moveLabelCons =
2329 layout.getConstraints(moveLabel);
2330
2331 JButton moveUp = new JButton("Up");
2332 add(moveUp);
2333 SpringLayout.Constraints moveUpCons =
2334 layout.getConstraints(moveUp);
2335
2336 JButton moveLeft = new JButton("Left");
2337 add(moveLeft);
2338 SpringLayout.Constraints moveLeftCons =
2339 layout.getConstraints(moveLeft);
2340
2341 JButton moveRight = new JButton("Right");
2342 add(moveRight);
2343 SpringLayout.Constraints moveRightCons =
2344 layout.getConstraints(moveRight);
2345
2346 JButton moveDown = new JButton("Down");
2347 add(moveDown);
2348 SpringLayout.Constraints moveDownCons =
2349 layout.getConstraints(moveDown);
2350
2351 JButton moveBackward = new JButton("Backward");
2352 add(moveBackward);
2353 SpringLayout.Constraints moveBackwardCons =
2354 layout.getConstraints(moveBackward);
2355
2356 JButton moveForward = new JButton("Forward");
2357 add(moveForward);
2358 SpringLayout.Constraints moveForwardCons =
2359 layout.getConstraints(moveForward);
2360
2361 JLabel rotateLabel = new JLabel("Rotate Gizmo");
2362 add(rotateLabel);
2363 SpringLayout.Constraints rotateLabelCons =
2364 layout.getConstraints(rotateLabel);
2365
2366 JButton rotateClockwise = new JButton("Clockwise");
2367 add(rotateClockwise);
2368 SpringLayout.Constraints rotateClockwiseCons =
2369 layout.getConstraints(rotateClockwise);
2370
2371 JButton rotateCounterclockwise = new JButton("Counterclockwise");
2372 add(rotateCounterclockwise);
2373 SpringLayout.Constraints rotateCounterclockwiseCons =
2374 layout.getConstraints(rotateCounterclockwise);
2375
2376 JButton deleteGizmo = new JButton("Delete Gizmo");
2377 add(deleteGizmo);
2378 SpringLayout.Constraints deleteGizmoCons =
2379 layout.getConstraints(deleteGizmo);
2380
2381
2382 // Control layout ------------------------------------------
2383
2384 Spring padding = Spring.constant(12);
2385 Spring groupPadding = Spring.constant(36);
2386 Spring labelPadding = Spring.constant(6);
2387 Spring indent = Spring.constant(18);
2388
2389 Spring leftEdge = Spring.sum(panelCons.getX(), padding);
2390 Spring leftIndentedEdge = Spring.sum(leftEdge, indent);
2391
2392 gizmosComboLabelCons.setX(leftEdge);
2393 gizmosComboLabelCons.setY(Spring.sum(panelCons.getY(), padding));
2394
2395 gizmosComboCons.setY(Spring.sum(
2396 gizmosComboLabelCons.getConstraint(SpringLayout.SOUTH),
2397 labelPadding));
2398 // Setting x edges needs to wait until we have the width
2399 // from the move diamond.
2400
2401 // renameCons.setY(Spring.sum(
2402 // gizmosComboCons.getConstraint(SpringLayout.SOUTH), padding));
2403 // Setting x location needs to wait until we have the width
2404 // from the move diamond.
2405
2406 moveLabelCons.setX(leftEdge);
2407 moveLabelCons.setY(Spring.sum(
2408 gizmosComboCons.getConstraint(SpringLayout.SOUTH),
2409 groupPadding));
2410
2411 moveUpCons.setY(Spring.sum(
2412 moveLabelCons.getConstraint(SpringLayout.SOUTH), padding));
2413 moveLeftCons.setY(Spring.sum(
2414 moveUpCons.getConstraint(SpringLayout.SOUTH), padding));
2415 moveRightCons.setY(moveLeftCons.getY());
2416 moveDownCons.setY(Spring.sum(
2417 moveLeftCons.getConstraint(SpringLayout.SOUTH), padding));
2418
2419 // XXX: Assumes "Right" is wider than "Left" and "Down" is
2420 // wider than "Up".
2421 moveLeftCons.setWidth(moveRightCons.getWidth());
2422 moveUpCons.setWidth(moveDownCons.getWidth());
2423
2424 moveLeftCons.setX(leftIndentedEdge);
2425 moveUpCons.setX(Spring.sum(
2426 moveLeftCons.getConstraint(SpringLayout.EAST), padding));
2427 moveDownCons.setX(moveUpCons.getX());
2428 moveRightCons.setX(Spring.sum(
2429 moveUpCons.getConstraint(SpringLayout.EAST), padding));
2430
2431 Spring rightEdge = moveRightCons.getConstraint(SpringLayout.EAST);
2432 panelCons.setConstraint(SpringLayout.EAST,
2433 Spring.sum(rightEdge, padding));
2434 gizmosComboCons.setConstraint(SpringLayout.EAST, rightEdge);
2435 gizmosComboCons.setX(leftEdge);
2436 //renameCons.setConstraint(SpringLayout.EAST, rightEdge);
2437
2438 // XXX: Assumes "Backward" is wider than "Forward" and that
2439 // the two side by side are less wide than the diamond
2440 // above.
2441 moveForwardCons.setWidth(moveBackwardCons.getWidth());
2442 moveBackwardCons.setX(leftIndentedEdge);
2443 moveBackwardCons.setY(Spring.sum(
2444 moveDownCons.getConstraint(SpringLayout.SOUTH), padding));
2445 moveForwardCons.setConstraint(SpringLayout.EAST, rightEdge);
2446 moveForwardCons.setY(moveBackwardCons.getY());
2447
2448 rotateLabelCons.setX(leftEdge);
2449 rotateLabelCons.setY(Spring.sum(
2450 moveBackwardCons.getConstraint(SpringLayout.SOUTH),
2451 groupPadding));
2452
2453 rotateClockwiseCons.setX(leftIndentedEdge);
2454 rotateClockwiseCons.setY(Spring.sum(
2455 rotateLabelCons.getConstraint(SpringLayout.SOUTH), padding));
2456 rotateCounterclockwiseCons.setX(leftIndentedEdge);
2457 rotateCounterclockwiseCons.setY(Spring.sum(
2458 rotateClockwiseCons.getConstraint(SpringLayout.SOUTH),
2459 padding));
2460
2461 // XXX: Assumes "Counterclockwise" is wider than
2462 // "Clockwise".
2463 rotateClockwiseCons.setWidth(
2464 rotateCounterclockwiseCons.getWidth());
2465
2466 deleteGizmoCons.setX(leftEdge);
2467 deleteGizmoCons.setY(Spring.sum(
2468 rotateCounterclockwiseCons.getConstraint(SpringLayout.SOUTH),
2469 groupPadding));
2470
2471
2472 // Event handling ------------------------------------------
2473
2474 gizmosCombo.addItemListener(new ItemListener() {
2475 public void itemStateChanged(ItemEvent e) {
2476 if (rightBar.getSelectedIndex() == 0) {
2477 // We're in run mode, so ignore everything
2478 return;
2479 }
2480
2481 if (currentGizmo != null) {
2482 try {
2483 board.addGizmo(currentGizmo);
2484 } catch (NonUniqueGizmoNameException ex) {
2485 // Cry
2486 System.err.println("Unexpected error!");
2487 ex.printStackTrace();
2488 }
2489 }
2490 currentGizmo = board.checkoutGizmo(
2491 (String)gizmosCombo.getSelectedItem());
2492 boardView.setSelectedGizmo(currentGizmo);
2493 if (board != null) {
2494 boardView.setBoard(board);
2495 }
2496 boardView.redraw();
2497 }
2498 });
2499
2500 //rename.addActionListener(new ActionListener() {
2501 // public void actionPerformed(ActionEvent e) {
2502 // renameCurrentGizmo();
2503 // }
2504 //});
2505
2506 moveUp.addActionListener(new ActionListener() {
2507 public void actionPerformed(ActionEvent e) {
2508 moveCurrentGizmoUp();
2509 }
2510 });
2511
2512 moveLeft.addActionListener(new ActionListener() {
2513 public void actionPerformed(ActionEvent e) {
2514 moveCurrentGizmoLeft();
2515 }
2516 });
2517
2518 moveRight.addActionListener(new ActionListener() {
2519 public void actionPerformed(ActionEvent e) {
2520 moveCurrentGizmoRight();
2521 }
2522 });
2523
2524 moveDown.addActionListener(new ActionListener() {
2525 public void actionPerformed(ActionEvent e) {
2526 moveCurrentGizmoDown();
2527 }
2528 });
2529
2530 moveBackward.addActionListener(new ActionListener() {
2531 public void actionPerformed(ActionEvent e) {
2532 moveCurrentGizmoBackward();
2533 }
2534 });
2535
2536 moveForward.addActionListener(new ActionListener() {
2537 public void actionPerformed(ActionEvent e) {
2538 moveCurrentGizmoForward();
2539 }
2540 });
2541
2542 rotateClockwise.addActionListener(new ActionListener() {
2543 public void actionPerformed(ActionEvent e) {
2544 rotateCurrentGizmoClockwise();
2545 }
2546 });
2547
2548 rotateCounterclockwise.addActionListener(new ActionListener() {
2549 public void actionPerformed(ActionEvent e) {
2550 rotateCurrentGizmoCounterclockwise();
2551 }
2552 });
2553
2554 deleteGizmo.addActionListener(new ActionListener() {
2555 public void actionPerformed(ActionEvent e) {
2556 deleteCurrentGizmo();
2557 }
2558 });
2559 }
2560
2561 // Brings the list of Gizmos up to date. Clears the list and
2562 // repopulates it from the current Board.
2563 private void refreshGizmosCombo() {
2564 SortedSet<String> names = new TreeSet<String>(board.getNames());
2565 if (currentGizmo != null) {
2566 names.add(currentGizmo.getName());
2567 }
2568 buildModePanel.gizmosCombo.removeAllItems();
2569 for (String name : names) {
2570 if (name.equals("OuterWalls")) {
2571 continue;
2572 }
2573
2574 buildModePanel.gizmosCombo.addItem(name);
2575 }
2576 }
2577 }
2578 }
2579
2580
2581
2582 /*
2583 Indentation spec for emacs:
2584 Local Variables:
2585 c-basic-offset:4
2586 End:
2587 */