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 }