001 package view; 002 003 import controller.IDisplayAdapter; 004 import controller.IEnvAdapter; 005 import controller.IScrollAdapter; 006 import controller.ISimAdapter; 007 import lrs.LRStruct; 008 import lrs.visitor.Apply; 009 import model.ILambda; 010 import model.RandNumGenerator; 011 import sysModel.NoOpLambda; 012 import sysModel.env.AEnvFactory; 013 014 import javax.swing.*; 015 import javax.swing.event.HyperlinkEvent; 016 import javax.swing.event.HyperlinkListener; 017 import java.awt.*; 018 import java.awt.event.ActionEvent; 019 import java.awt.event.ActionListener; 020 import java.awt.event.KeyEvent; 021 import java.awt.event.WindowEvent; 022 import java.net.URL; 023 import java.io.IOException; 024 025 /** 026 * The main frame class of the GUI. 027 * 028 * @author Mathias Ricken 029 */ 030 public class MBSView extends JFrame { 031 /** 032 * Program date. 033 */ 034 private static final String VERSION_DATE = "July 2004"; 035 036 /** 037 * List of menu items that are only enabled if an environment is present. 038 */ 039 private LRStruct _componentsThatNeedAnEnvironment; 040 041 /** 042 * List of menu items that are disabled if the simulation is running. 043 */ 044 private LRStruct _componentsDisabledDuringRun; 045 046 /** 047 * Edit toolbar. 048 */ 049 private EditToolbar _editToolbar; 050 051 /** 052 * Simulation toolbar. 053 */ 054 private SimToolbar _simToolbar; 055 056 /** 057 * Display panel. 058 */ 059 public DisplayPanel _displayPanel; 060 061 /** 062 * Display viewport. 063 */ 064 private DisplayViewport _displayViewport; 065 066 /** 067 * Scroll panel. 068 */ 069 private JScrollPane _scrollPane; 070 071 /** 072 * Environment file chooser dialog. 073 */ 074 private EnvFileChooser _fileChooser; 075 076 /** 077 * Create environment dialog. 078 */ 079 private CreateEnvDialog _createEnvDialog; 080 081 /** 082 * Seed value. 083 */ 084 private int _randSeed = RandNumGenerator.instance().getSeed(); 085 086 /** 087 * Step count. 088 */ 089 private int _stepCount = 10; 090 091 /** 092 * Iteration command to run indefinitely. 093 */ 094 private ILambda _indefinitelyIterLambda = new ILambda() { 095 public Object apply(Object param) { 096 // don't do anything, just redraw 097 _displayViewport.repaint(); 098 return null; 099 } 100 }; 101 102 /** 103 * Display adapter. 104 */ 105 IDisplayAdapter _displayAdapter; 106 107 /** 108 * Simulation adapter. 109 */ 110 ISimAdapter _simAdapter; 111 112 /** 113 * Environment adapter to the model. 114 */ 115 IEnvAdapter _envAdapter; 116 117 /** 118 * Scroll adapter. 119 */ 120 IScrollAdapter _scrollAdapter; 121 122 /** 123 * Processes window events occurring on this component. Hides the window or disposes of it, as specified by the 124 * setting of the <code>defaultCloseOperation</code> property. 125 * 126 * @param e the window event 127 * 128 * @see #setDefaultCloseOperation 129 * @see Window#processWindowEvent 130 */ 131 protected void processWindowEvent(WindowEvent e) { 132 super.processWindowEvent(e); 133 134 if (WindowEvent.WINDOW_CLOSING == e.getID()) { 135 _envAdapter.getSecurityAdapter().setProtected(false); 136 System.exit(0); 137 } 138 } 139 140 141 /** 142 * Create a new MBSGUIFrame and all its controls. 143 * 144 * @param da display adapter 145 * @param sa simulation adapter 146 * @param ea environment adapter 147 */ 148 public MBSView(IDisplayAdapter da, ISimAdapter sa, IEnvAdapter ea) { 149 _displayAdapter = da; 150 _simAdapter = sa; 151 _envAdapter = ea; 152 _scrollAdapter = new IScrollAdapter() { 153 public void setCorner(int x, int y) { 154 _displayPanel.setCorner(x, y); 155 } 156 157 public void resetScrolling() { 158 _displayPanel.resetPan(); 159 _displayViewport.resetViewport(); 160 } 161 }; 162 163 _componentsThatNeedAnEnvironment = new LRStruct(); 164 _componentsDisabledDuringRun = new LRStruct(); 165 166 System.setProperty("sun.awt.exception.handler", GUIExceptionHandler.class.getName()); 167 168 setTitle("Rice Marine Biology Simulation"); 169 setLocation(25, 15); 170 171 172 JPanel content = new JPanel(); 173 setContentPane(content); 174 content.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); 175 content.setLayout(new BorderLayout()); 176 177 _displayPanel = new DisplayPanel(_displayAdapter, _envAdapter); 178 _scrollPane = new JScrollPane(_displayPanel, 179 JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, 180 JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 181 _displayViewport = new DisplayViewport(_scrollPane, _displayAdapter); 182 _scrollPane.setViewport(_displayViewport); 183 _scrollPane.setViewportView(_displayPanel); 184 _scrollPane.setPreferredSize(_displayPanel.getPreferredSize()); 185 186 content.add(_scrollPane, BorderLayout.CENTER); 187 188 _editToolbar = new EditToolbar(this, _envAdapter, _displayAdapter); 189 content.add(_editToolbar, BorderLayout.EAST); 190 191 _simToolbar = new SimToolbar(_simAdapter, new IRunIdleAdapter() { 192 public void setRunState() { 193 setComponentsEnabled(_componentsDisabledDuringRun, false); 194 setEditModeEnable(false); 195 _displayViewport.repaint(); 196 } 197 198 public void setIdleState() { 199 setComponentsEnabled(_componentsDisabledDuringRun, true); 200 _displayViewport.repaint(); 201 } 202 }); 203 _simToolbar.setVisible(true); 204 _simToolbar.setEnabled(false); 205 content.add(_simToolbar, BorderLayout.SOUTH); 206 _componentsThatNeedAnEnvironment.insertFront(_simToolbar); 207 208 _fileChooser = new EnvFileChooser(); 209 _createEnvDialog = new CreateEnvDialog(this, _envAdapter); 210 211 setEditModeEnable(false); 212 makeMenus(); 213 214 _simAdapter.setIterationLambda(_indefinitelyIterLambda); 215 } 216 217 /** 218 * Create the drop-down menus on the frame. 219 */ 220 private void makeMenus() { 221 int menuMask = getToolkit().getMenuShortcutKeyMask(); 222 JMenuBar mbar = new JMenuBar(); 223 JMenu menu; 224 JMenuItem mItem; 225 226 mbar.add(menu = new JMenu("File")); 227 menu.add(mItem = new JMenuItem("Open environment file...")); 228 mItem.addActionListener(new ActionListener() { 229 public void actionPerformed(ActionEvent e) { 230 if (JFileChooser.APPROVE_OPTION == _fileChooser.showOpenDialog(MBSView.this)) { 231 _simAdapter.stop(); 232 _simToolbar.setControlsInIdleState(); 233 setComponentsEnabled(_componentsThatNeedAnEnvironment, true); 234 setEditModeEnable(false); 235 _envAdapter.loadEnvironment(_fileChooser.getSelectedFile().getAbsoluteFile().toString()); 236 _displayPanel.revalidate(); 237 _scrollAdapter.resetScrolling(); 238 _displayAdapter.returnHome(_scrollAdapter); 239 } 240 } 241 }); 242 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, menuMask)); 243 244 menu.add(mItem = new JMenuItem("Create new environment...")); 245 mItem.addActionListener(new ActionListener() { 246 public void actionPerformed(ActionEvent e) { 247 AEnvFactory settings = _createEnvDialog.showDialog(); 248 if (null != settings) { 249 _simAdapter.stop(); 250 _simToolbar.setControlsInIdleState(); 251 setComponentsEnabled(_componentsThatNeedAnEnvironment, true); 252 setEditModeEnable(false); 253 _envAdapter.createEnvironment(settings); 254 _displayPanel.revalidate(); 255 _scrollAdapter.resetScrolling(); 256 _displayAdapter.returnHome(_scrollAdapter); 257 } 258 } 259 }); 260 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, menuMask)); 261 262 menu.add(mItem = new JMenuItem("Edit environment...")); 263 mItem.addActionListener(new ActionListener() { 264 public void actionPerformed(ActionEvent e) { 265 setEditModeEnable(true); 266 } 267 }); 268 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, menuMask)); 269 _componentsThatNeedAnEnvironment.insertFront(mItem); 270 271 menu.add(mItem = new JMenuItem("Save environment as...")); 272 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, menuMask)); 273 mItem.addActionListener(new ActionListener() { 274 public void actionPerformed(ActionEvent e) { 275 if (JFileChooser.APPROVE_OPTION == _fileChooser.showSaveDialog(MBSView.this)) { 276 setComponentsEnabled(_componentsThatNeedAnEnvironment, true); 277 setEditModeEnable(false); 278 _envAdapter.saveEnvironment(_fileChooser.getSelectedFile().getAbsoluteFile().toString()); 279 } 280 } 281 }); 282 _componentsThatNeedAnEnvironment.insertFront(mItem); 283 284 menu.add(mItem = new JMenuItem("Quit")); 285 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, menuMask)); 286 mItem.addActionListener(new ActionListener() { 287 public void actionPerformed(ActionEvent e) { 288 _envAdapter.getSecurityAdapter().setProtected(false); 289 System.exit(0); 290 } 291 }); 292 293 mbar.add(menu = new JMenu("Seed")); 294 ButtonGroup bGroup = new ButtonGroup(); 295 menu.add(mItem = new JRadioButtonMenuItem("Don't change seed", true)); 296 bGroup.add(mItem); 297 mItem.addActionListener(new ActionListener() { 298 public void actionPerformed(ActionEvent e) { 299 // don't do anything 300 _envAdapter.setSeedLambda(NoOpLambda.instance()); 301 } 302 }); 303 menu.add(mItem = new JRadioButtonMenuItem("Use fixed seed...")); 304 bGroup.add(mItem); 305 mItem.addActionListener(new ActionListener() { 306 public void actionPerformed(ActionEvent e) { 307 final Integer seed = queryForInteger("Enter seed for random number generator:", 308 "Input", 309 (0 == _randSeed) ? 17 : _randSeed, 310 MBSView.this); 311 if (null != seed) { 312 _randSeed = seed.intValue(); 313 _envAdapter.setSeedLambda(new ILambda() { 314 public Object apply(Object param) { 315 // set fixed seed 316 RandNumGenerator.instance().setSeed(seed.intValue()); 317 return null; 318 } 319 }); 320 } 321 } 322 }); 323 menu.add(mItem = new JRadioButtonMenuItem("Prompt for seed")); 324 bGroup.add(mItem); 325 mItem.addActionListener(new ActionListener() { 326 public void actionPerformed(ActionEvent e) { 327 _envAdapter.setSeedLambda(new ILambda() { 328 public Object apply(Object param) { 329 Integer seed = queryForInteger("Enter seed for random number generator:", 330 "Input", 331 (0 == _randSeed) ? 17 : _randSeed, 332 MBSView.this); 333 if (null != seed) { 334 _randSeed = seed.intValue(); 335 RandNumGenerator.instance().setSeed(seed.intValue()); 336 } 337 return null; 338 } 339 }); 340 } 341 }); 342 343 mbar.add(menu = new JMenu("Run")); 344 bGroup = new ButtonGroup(); 345 menu.add(mItem = new JRadioButtonMenuItem("Run Indefinitely", true)); 346 bGroup.add(mItem); 347 mItem.addActionListener(new ActionListener() { 348 public void actionPerformed(ActionEvent e) { 349 _simAdapter.setStartLambda(new ILambda() { 350 public Object apply(Object param) { 351 // don't do anything 352 return null; 353 } 354 }); 355 _simAdapter.setIterationLambda(_indefinitelyIterLambda); 356 } 357 }); 358 _componentsDisabledDuringRun.insertFront(mItem); 359 360 menu.add(mItem = new JRadioButtonMenuItem("Use fixed number of steps...")); 361 bGroup.add(mItem); 362 mItem.addActionListener(new ActionListener() { 363 public void actionPerformed(ActionEvent e) { 364 final Integer steps = queryForInteger("Enter number of steps:", "Input", _stepCount, MBSView.this); 365 if (null != steps) { 366 _stepCount = steps.intValue(); 367 final StepItLambda itLambda = new StepItLambda(_simAdapter, _displayViewport, _simToolbar); 368 _simAdapter.setStartLambda(new ILambda() { 369 public Object apply(Object param) { 370 // change counter 371 itLambda.setSteps(steps.intValue()); 372 return null; 373 } 374 }); 375 _simAdapter.setIterationLambda(itLambda); 376 } 377 } 378 }); 379 _componentsDisabledDuringRun.insertFront(mItem); 380 381 menu.add(mItem = new JRadioButtonMenuItem("Prompt for number of steps")); 382 bGroup.add(mItem); 383 mItem.addActionListener(new ActionListener() { 384 public void actionPerformed(ActionEvent e) { 385 final StepItLambda itLambda = new StepItLambda(_simAdapter, _displayViewport, _simToolbar); 386 _simAdapter.setStartLambda(new ILambda() { 387 public Object apply(Object param) { 388 Integer steps = queryForInteger("Enter number of steps:", 389 "Input", 390 _stepCount, 391 MBSView.this); 392 if (null != steps) { 393 _stepCount = steps.intValue(); 394 itLambda.setSteps(steps.intValue()); 395 } 396 return null; 397 } 398 }); 399 _simAdapter.setIterationLambda(itLambda); 400 } 401 }); 402 _componentsDisabledDuringRun.insertFront(mItem); 403 404 mbar.add(menu = new JMenu("View")); 405 menu.add(mItem = new JMenuItem("Zoom in")); 406 mItem.addActionListener(new ActionListener() { 407 public void actionPerformed(ActionEvent e) { 408 _displayPanel.zoomIn(); 409 } 410 }); 411 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_UP, menuMask)); 412 _componentsThatNeedAnEnvironment.insertFront(mItem); 413 414 menu.add(mItem = new JMenuItem("Zoom out")); 415 mItem.addActionListener(new ActionListener() { 416 public void actionPerformed(ActionEvent e) { 417 _displayPanel.zoomOut(); 418 } 419 }); 420 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, menuMask)); 421 _componentsThatNeedAnEnvironment.insertFront(mItem); 422 423 menu.add(mItem = new JMenuItem("Bring (0, 0) to upper left")); 424 mItem.addActionListener(new ActionListener() { 425 public void actionPerformed(ActionEvent e) { 426 _scrollAdapter.resetScrolling(); 427 _displayAdapter.returnHome(_scrollAdapter); 428 } 429 }); 430 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, menuMask)); 431 _componentsThatNeedAnEnvironment.insertFront(mItem); 432 433 mbar.add(menu = new JMenu("Help")); 434 menu.add(mItem = new JMenuItem("About RiceMBS...")); 435 mItem.addActionListener(new ActionListener() { 436 public void actionPerformed(ActionEvent e) { 437 showAboutPanel(); 438 } 439 }); 440 menu.add(mItem = new JMenuItem("Help...")); 441 mItem.addActionListener(new ActionListener() { 442 public void actionPerformed(ActionEvent e) { 443 showHelp(); 444 } 445 }); 446 mItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_HELP, menuMask)); 447 448 setComponentsEnabled(_componentsThatNeedAnEnvironment, false); 449 setComponentsEnabled(_componentsDisabledDuringRun, true); 450 setJMenuBar(mbar); 451 } 452 453 /** 454 * Set the enabled status of those menu items that need an environment to be valid. 455 * 456 * @param list list of components 457 * @param enable true if components should be enabled 458 */ 459 public static void setComponentsEnabled(LRStruct list, final boolean enable) { 460 list.execute(Apply.Singleton, new ILambda() { 461 /** 462 * Execute command. 463 */ 464 public Object apply(Object param) { 465 ((JComponent)param).setEnabled(enable); 466 return null; 467 } 468 }); 469 } 470 471 /** 472 * Enable or disable the edit mode. 473 * 474 * @param enable true if edit mode should be enabled 475 */ 476 public void setEditModeEnable(boolean enable) { 477 if (enable != _editToolbar.isEnabled()) { 478 _editToolbar.setEnabled(enable); 479 _displayPanel.enableMouseAdapter(enable); 480 } 481 } 482 483 /** 484 * Brings up a simple dialog with some general information. 485 */ 486 private void showAboutPanel() { 487 String html = "<html><h2>Rice Marine Biology Simulation Program</h2>" + "A tool for running and viewing the AP® Computer Science<p>" + "Marine Biology Simulation case study program.<p><p>" + "<font size=-1>Version: " + VERSION_DATE + "</font><p>" + "<font size=-1>Copyright© 2002 College Entrance Examination Board " + "(www.collegeboard.com).<br>" + "<font size=-1>Copyright© 2003 Rice University " + "(www.rice.edu).</font></font></html>"; 488 JOptionPane.showMessageDialog(this, new JLabel(html), "About RiceMBS", JOptionPane.INFORMATION_MESSAGE, null); 489 } 490 491 /** 492 * Brings up a window with a scrolling text pane that display the help information for the simulation. 493 */ 494 private void showHelp() { 495 JDialog dialog = new JDialog(this, "RiceMBS Help"); 496 final JEditorPane helpText = new JEditorPane(); 497 try { 498 helpText.setPage( 499 new URL("file", 500 "", 501 System.getProperty("user.dir") + java.io.File.separator + "Resources" + java.io.File.separator + "MBSHelp.html")); 502 } 503 catch(IOException e) { 504 helpText.setText("Couldn't load help file."); 505 } 506 helpText.setEditable(false); 507 helpText.addHyperlinkListener(new HyperlinkListener() { 508 public void hyperlinkUpdate(HyperlinkEvent ev) { 509 if (ev.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 510 try { 511 helpText.setPage(ev.getURL()); 512 } 513 catch(IOException ex) { 514 System.err.println(ex); 515 } 516 } 517 } 518 }); 519 JScrollPane sp = new JScrollPane(helpText); 520 sp.setPreferredSize(new Dimension(650, 500)); 521 dialog.getContentPane().add(sp); 522 dialog.setLocation(getX() + getWidth() - 200, getY() + 50); 523 dialog.pack(); 524 dialog.setVisible(true); 525 } 526 527 /** 528 * Handle an exception that occurred during the simulation. 529 * @param t exception caught during simulation 530 */ 531 public void handleException(Throwable t) { 532 Object[] options = { "OK" }; 533 StringBuffer message = new StringBuffer(t.toString()); 534 if (null != t.getCause()) { 535 message.append('\n'); 536 message.append(t.getCause().toString()); 537 } 538 JOptionPane.showOptionDialog(JOptionPane.getFrameForComponent(this), 539 message, 540 "Error Loading Fish", 541 JOptionPane.DEFAULT_OPTION, 542 JOptionPane.WARNING_MESSAGE, 543 null, 544 options, 545 options[0]); 546 } 547 548 /** 549 * Nested class that is registered as the handler for exceptions on the Swing event thread. The handler will put up 550 * an alert panel, dump the stack trace to the console, and then exit entire program rather than persist in an 551 * inconsistent state, which would be the default behavior. 552 */ 553 public class GUIExceptionHandler { 554 /** 555 * Handle the exception by displaying a dialog box and exiting. 556 * 557 * @param e exception that occurred 558 */ 559 public void handle(Throwable e) { 560 e.printStackTrace(); 561 JOptionPane.showMessageDialog(null, 562 "An error occurred. The simulation must exit." + "\nReason: " + e, 563 "Error", 564 JOptionPane.ERROR_MESSAGE); 565 _envAdapter.getSecurityAdapter().setProtected(false); 566 System.exit(0); 567 } 568 } 569 570 /** 571 * Query for an integer. 572 * 573 * @param message message to display 574 * @param prompt prompt to display 575 * @param suggestion suggested value 576 * @param parentFrame parent frame 577 * 578 * @return Integer object or null if error 579 */ 580 private static Integer queryForInteger(String message, String prompt, int suggestion, JFrame parentFrame) { 581 String str = (String)JOptionPane.showInputDialog(parentFrame, 582 message, 583 prompt, 584 JOptionPane.QUESTION_MESSAGE, 585 null, 586 null, 587 Integer.toString(suggestion)); 588 if (null != str) { 589 try { 590 return new Integer(Integer.parseInt(str.trim())); 591 } 592 catch(NumberFormatException e) { 593 Toolkit.getDefaultToolkit().beep(); 594 } 595 } 596 return null; 597 } 598 599 /** 600 * Return name of current fish class. 601 * 602 * @return name of current fish class 603 */ 604 public String getCurrentFish() { 605 return _editToolbar.getCurrentFish(); 606 } 607 608 /** 609 * Return current color. 610 * 611 * @return current color 612 */ 613 public Color getCurrentColor() { 614 return _editToolbar.getCurrentColor(); 615 } 616 617 /** 618 * Iteration command for a set number of steps. 619 */ 620 private static class StepItLambda implements ILambda { 621 int _steps; 622 ISimAdapter _simAdapter; 623 DisplayViewport _displayViewport; 624 SimToolbar _simToolbar; 625 626 public StepItLambda(ISimAdapter sa, DisplayViewport vp, SimToolbar st) { 627 _steps = 0; 628 _simAdapter = sa; 629 _displayViewport = vp; 630 _simToolbar = st; 631 } 632 633 public void setSteps(int steps) { 634 _steps = steps; 635 } 636 637 public Object apply(Object param) { 638 _steps--; 639 if (0 >= _steps) { 640 _simAdapter.stop(); 641 _simToolbar.setControlsInIdleState(); 642 } 643 _displayViewport.repaint(); 644 return null; 645 } 646 } 647 }