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&reg; Computer Science<p>" + "Marine Biology Simulation case study program.<p><p>" + "<font size=-1>Version: " + VERSION_DATE + "</font><p>" + "<font size=-1>Copyright&copy; 2002 College Entrance Examination Board " + "(www.collegeboard.com).<br>" + "<font size=-1>Copyright&copy; 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    }