001/* Top-level window containing a plotter.
002
003 Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026 */
027package ptolemy.actor.gui;
028
029import java.awt.BorderLayout;
030import java.awt.Color;
031import java.awt.Graphics;
032import java.awt.event.ActionEvent;
033import java.awt.event.ActionListener;
034import java.awt.event.KeyEvent;
035import java.awt.print.PageFormat;
036import java.awt.print.Printable;
037import java.awt.print.PrinterException;
038import java.io.File;
039import java.io.FileOutputStream;
040import java.io.IOException;
041import java.io.OutputStream;
042import java.lang.reflect.Constructor;
043import java.util.LinkedList;
044import java.util.Locale;
045
046import javax.swing.AbstractAction;
047import javax.swing.Action;
048import javax.swing.JFileChooser;
049import javax.swing.JMenu;
050import javax.swing.JMenuItem;
051import javax.swing.JOptionPane;
052import javax.swing.KeyStroke;
053import javax.swing.filechooser.FileFilter;
054
055import ptolemy.actor.injection.PortablePlaceable;
056import ptolemy.data.expr.StringParameter;
057import ptolemy.gui.ExtensionFilenameFilter;
058import ptolemy.gui.ImageExportable;
059import ptolemy.gui.JFileChooserBugFix;
060import ptolemy.gui.Top;
061import ptolemy.kernel.util.InternalErrorException;
062import ptolemy.plot.Plot;
063import ptolemy.plot.PlotBox;
064import ptolemy.plot.PlotFormatter;
065import ptolemy.util.MessageHandler;
066import ptolemy.util.StringUtilities;
067
068///////////////////////////////////////////////////////////////////
069//// PlotTableauFrame
070
071/**
072
073 PlotTableauFrame is a version of PlotFrame in the plot package that
074 works more closely with the Ptolemy actor.gui infrastructure.
075 In particular, the File menu commands will open Ptolemy models
076 and HTML files, not just PlotML files. It contains an instance
077 of PlotBox. If not specified in the constructor, the default
078 is to contain a Plot object, where Plot extends PlotBox. This
079 field is set once in the constructor and immutable afterwards.
080
081 @see Plot
082 @see PlotBox
083 @author Edward A. Lee
084 @version $Id$
085 @since Ptolemy II 2.1
086 @Pt.ProposedRating Yellow (cxh)
087 @Pt.AcceptedRating Yellow (cxh)
088 */
089@SuppressWarnings("serial")
090public class PlotTableauFrame extends TableauFrame
091        implements Printable, ImageExportable {
092    /** Construct a plot frame with a default title and by default contains
093     *  an instance of Plot. After constructing this, it is necessary
094     *  to call setVisible(true) to make the plot appear.
095     */
096    public PlotTableauFrame() {
097        this(null);
098    }
099
100    /** Construct a plot frame in the corresponding Tableau with the
101     *  specified instance of PlotBox.
102     *  After constructing this, it is necessary
103     *  to call setVisible(true) to make the plot appear.
104     *  @param tableau The tableau where the window is placed
105     */
106    public PlotTableauFrame(Tableau tableau) {
107        this(tableau, new Plot());
108    }
109
110    /** Construct a plot frame with the specified title and by default
111     *  contains an instance of Plot. After constructing this, it is necessary
112     *  to call setVisible(true) to make the plot appear.
113     *  @param tableau The tableau where the window is placed.
114     *  @param plotBox the plot object to put in the frame, or null to create
115     *   an instance of Plot.
116     */
117    public PlotTableauFrame(Tableau tableau, PlotBox plotBox) {
118        this(tableau, plotBox, (Placeable) null);
119    }
120
121    /** Construct a plot frame with the specified title and by default
122     *  contains an instance of Plot. After constructing this, it is necessary
123     *  to call setVisible(true) to make the plot appear.
124     *  @param tableau The tableau where the window is placed.
125     *  @param plotBox the plot object to put in the frame, or null to create
126     *   an instance of Plot.
127     *  @param placeable The associated plot actor, or null if none.
128     */
129    public PlotTableauFrame(Tableau tableau, PlotBox plotBox,
130            Placeable placeable) {
131        super(tableau, null, placeable);
132        plot = plotBox;
133
134        // We don't define a file name filter here because we are
135        // only supporting PlotML files.  .plt files are not supported
136        // for opening here.
137
138        // Background color is a light grey.
139        plot.setBackground(new Color(0xe5e5e5));
140        getContentPane().add(plot, BorderLayout.CENTER);
141        _initialSaveAsFileName = "plot.plt";
142    }
143
144    /** Construct a plot frame with the specified title and by default
145     *  contains an instance of Plot. After constructing this, it is necessary
146     *  to call setVisible(true) to make the plot appear.
147     *  @param tableau The tableau where the window is placed.
148     *  @param plotBox the plot object to put in the frame, or null to create
149     *   an instance of Plot.
150     *  @param placeable The associated plot actor, or null if none.
151     */
152    public PlotTableauFrame(Tableau tableau, PlotBox plotBox,
153            PortablePlaceable placeable) {
154        super(tableau, null, placeable);
155        plot = plotBox;
156
157        // We don't define a file name filter here because we are
158        // only supporting PlotML files.  .plt files are not supported
159        // for opening here.
160
161        // Background color is a light grey.
162        plot.setBackground(new Color(0xe5e5e5));
163        getContentPane().add(plot, BorderLayout.CENTER);
164        _initialSaveAsFileName = "plot.plt";
165    }
166
167    ///////////////////////////////////////////////////////////////////
168    ////                         public methods                    ////
169
170    /** Print the plot to a printer,
171     *  which is represented by the specified graphics object.
172     *  @param graphics The context into which the page is drawn.
173     *  @param format The size and orientation of the page being drawn.
174     *  @param index The zero based index of the page to be drawn.
175     *  @return PAGE_EXISTS if the page is rendered successfully, or
176     *   NO_SUCH_PAGE if pageIndex specifies a non-existent page.
177     *  @exception PrinterException If the print job is terminated.
178     */
179    @Override
180    public int print(Graphics graphics, PageFormat format, int index)
181            throws PrinterException {
182        // Note that the Plot print menu does not directly call
183        // this method, instead it calls _print().  This method is
184        // included so that this class implement printable and the
185        // print menu choice is enabled.
186        return plot.print(graphics, format, index);
187    }
188
189    /** Create a sample plot.
190     */
191    public void samplePlot() {
192        _file = null;
193        _directory = null;
194        plot.samplePlot();
195    }
196
197    /** Dispose of this frame.
198     *
199     *  <p>Override this dispose() method to unattach any listeners
200     *  that may keep this model from getting garbage collected.  This
201     *  method invokes the dispose() method of the superclass,
202     *  {@link ptolemy.gui.Top}.</p>
203     */
204    @Override
205    public void dispose() {
206        if (_debugClosing) {
207            System.out.println("TableauFrame.dispose() : " + this.getName());
208        }
209        super.dispose();
210    }
211
212    /** Write an image to the specified output stream in the specified format.
213     *  Supported formats include at least "gif" and "png", standard image file formats.
214     *  The image is a rendition of the current view of the model.
215     *  @param stream The output stream to write to.
216     *  @param format The image format to generate.
217     *  @exception IOException If writing to the stream fails.
218     *  @exception PrinterException  If the specified format is not supported.
219     */
220    @Override
221    public void writeImage(OutputStream stream, String format)
222            throws PrinterException, IOException {
223        if (plot == null) {
224            throw new IOException("No plot to write image from!");
225        }
226        plot.exportImage(stream, format);
227    }
228
229    ///////////////////////////////////////////////////////////////////
230    ////                         public variables                  ////
231
232    /** The plot object held by this frame. */
233    public final PlotBox plot;
234
235    ///////////////////////////////////////////////////////////////////
236    ////                         protected variables               ////
237
238    /** Directory that contains the input file. */
239    protected File _directory = null;
240
241    /** The export to PDF action. */
242    protected Action _exportPDFAction;
243
244    /** Edit menu for this frame. */
245    protected JMenu _editMenu;
246
247    /** The export to GIF action. */
248    protected Action _exportGIFAction;
249
250    /** The export to PNG action. */
251    protected Action _exportPNGAction;
252
253    /** The input file. */
254    protected File _file = null;
255
256    /** Special menu for this frame. */
257    protected JMenu _specialMenu;
258
259    ///////////////////////////////////////////////////////////////////
260    ////                         protected methods                 ////
261
262    /** Create the menus that are used by this frame.
263     */
264    @Override
265    protected void _addMenus() {
266        super._addMenus();
267
268        // Edit menu
269        _editMenu = new JMenu("Edit");
270        _editMenu.setMnemonic(KeyEvent.VK_E);
271        _menubar.add(_editMenu);
272
273        FormatAction formatAction = new FormatAction();
274        // Avoid a dependency on diva/gui/GUIUtilities.java here, but
275        // add some code duplication.
276        JMenuItem item = _editMenu.add(formatAction);
277        item.setText((String) formatAction.getValue(Action.NAME));
278        item.setMnemonic((Integer) formatAction.getValue(Action.MNEMONIC_KEY));
279        item.setToolTipText((String) formatAction.getValue("tooltip"));
280
281        KeyStroke key = (KeyStroke) formatAction
282                .getValue(Action.ACCELERATOR_KEY);
283        item.setAccelerator(key);
284        formatAction.putValue("menuItem", item);
285        // End of duplicated code from diva GUIUtilities.
286
287        // Special menu
288        _specialMenu = new JMenu("Special");
289        _specialMenu.setMnemonic(KeyEvent.VK_S);
290        _menubar.add(_specialMenu);
291
292        JMenuItem[] specialMenuItems = { new JMenuItem("Clear", KeyEvent.VK_C),
293                new JMenuItem("Export", KeyEvent.VK_E),
294                new JMenuItem("Fill", KeyEvent.VK_F),
295                new JMenuItem("Reset axes", KeyEvent.VK_R),
296                new JMenuItem("Sample plot", KeyEvent.VK_S), };
297        SpecialMenuListener sml = new SpecialMenuListener();
298
299        // Set the action command and listener for each menu item.
300        for (JMenuItem specialMenuItem : specialMenuItems) {
301            specialMenuItem.setActionCommand(specialMenuItem.getText());
302            specialMenuItem.addActionListener(sml);
303            _specialMenu.add(specialMenuItem);
304        }
305    }
306
307    /** Clear the current plot.  This class checks to see whether
308     *  the contents have been modified, and if so, then prompts the user
309     *  to save them.  A return value of false
310     *  indicates that the user has canceled the action.
311     *  @return False if the user cancels the clear.
312     */
313    @Override
314    protected boolean _clear() {
315        boolean result = super._clear();
316
317        // The false argument prevents clearing decorations.
318        plot.clear(false);
319        return result;
320    }
321
322    /** Create the items in the File menu's Export section
323     *  This method adds a menu items to export images of the plot
324     *  in GIF, PNG, and possibly PDF.
325     *  @return The items in the File menu.
326     */
327    @Override
328    protected JMenuItem[] _createFileMenuItems() {
329        JMenuItem[] fileMenuItems = super._createFileMenuItems();
330
331        JMenu exportMenu = (JMenu) fileMenuItems[_EXPORT_MENU_INDEX];
332        exportMenu.setEnabled(true);
333
334        try {
335            // Get the "export PDF" action classname from the configuration.
336            // This may or many not be included because it depends on GPL'd code,
337            // and hence cannot be included included in any pure BSD distribution.
338            // NOTE: Cannot use getConfiguration() because the configuration is
339            // not set when this method is called. Hence, we assume that there
340            // is only one configuration, or that if there are multiple configurations
341            // in this execution, that the first one will determine whether PDF
342            // export is provided.
343            Configuration configuration = Configuration.configurations().get(0);
344            // NOTE: Configuration should not be null, but just in case:
345            if (configuration != null) {
346                // Deal with the PDF Action first.
347                StringParameter exportPDFActionClassNameParameter = (StringParameter) configuration
348                        .getAttribute("_exportPDFActionClassName",
349                                StringParameter.class);
350
351                if (exportPDFActionClassNameParameter != null) {
352                    if (_exportPDFAction == null) {
353                        String exportPDFActionClassName = exportPDFActionClassNameParameter
354                                .stringValue();
355                        try {
356                            Class exportPDFActionClass = Class
357                                    .forName(exportPDFActionClassName);
358                            Constructor exportPDFActionConstructor = exportPDFActionClass
359                                    .getDeclaredConstructor(Top.class);
360                            _exportPDFAction = (AbstractAction) exportPDFActionConstructor
361                                    .newInstance(this);
362                        } catch (Throwable throwable) {
363                            throw new InternalErrorException(null, throwable,
364                                    "Failed to construct export PDF class \""
365                                            + exportPDFActionClassName
366                                            + "\", which was read from the configuration.");
367                        }
368                    }
369                }
370            }
371        } catch (Exception ex) {
372            // We do not want to abort at this point because the worst
373            // case is that we will have no Export PDF in the menu.
374            // That is better than preventing the user from opening a model.
375            System.err.println(
376                    "Warning: Tried to create Export PDF menu item, but failed: "
377                            + ex);
378        }
379
380        // Uncomment the next block to have Export PDF *ALWAYS* enabled.
381        // We don't want it always enabled because ptiny, the applets and
382        // Web Start should not included this AGPL'd piece of software
383
384        // NOTE: Comment out the entire block with lines that begin with //
385        // so that the test in adm notices that the block is commented out.
386
387        //         if (_exportPDFAction == null) {
388        //             //String exportPDFActionClassName = exportPDFActionClassNameParameter.stringValue();
389        //             String exportPDFActionClassName = "ptolemy.vergil.basic.export.itextpdf.ExportPDFAction";
390        //             try {
391        //                 Class exportPDFActionClass = Class
392        //                         .forName(exportPDFActionClassName);
393        //                 Constructor exportPDFActionConstructor = exportPDFActionClass
394        //                         .getDeclaredConstructor(Top.class);
395        //                 _exportPDFAction = (AbstractAction) exportPDFActionConstructor
396        //                         .newInstance(this);
397        //             } catch (Throwable throwable) {
398        //                 new InternalErrorException(null, throwable,
399        //                         "Failed to construct export PDF class \""
400        //                                 + exportPDFActionClassName
401        //                                 + "\", which was read from the configuration.");
402        //             }
403        //         }
404        // End of block to uncomment.
405
406        if (_exportPDFAction != null) {
407            // Insert the Export PDF item.
408            JMenuItem exportItem = new JMenuItem(_exportPDFAction);
409            exportMenu.add(exportItem);
410        }
411
412        // Next do the export GIF action.
413        if (_exportGIFAction == null) {
414            _exportGIFAction = new ExportImageAction("GIF");
415        }
416        JMenuItem exportItem = new JMenuItem(_exportGIFAction);
417        exportMenu.add(exportItem);
418
419        // Next do the export PNG action.
420        if (_exportPNGAction == null) {
421            _exportPNGAction = new ExportImageAction("PNG");
422        }
423        exportItem = new JMenuItem(_exportPNGAction);
424        exportMenu.add(exportItem);
425
426        return fileMenuItems;
427    }
428
429    /** Interactively edit the file format in a modal dialog.
430     */
431    protected void _editFormat() {
432        PlotFormatter formatter = new PlotFormatter(plot);
433        formatter.openModal();
434    }
435
436    /** Query the user for a filename and export the plot to that file.
437     *  Currently, the only supported format is EPS.
438     */
439    protected void _export() {
440        JFileChooser fileDialog = new JFileChooser();
441        fileDialog.addChoosableFileFilter(new EPSFileFilter());
442        fileDialog.setDialogTitle("Export EPS to...");
443
444        if (_directory != null) {
445            fileDialog.setCurrentDirectory(_directory);
446        } else {
447            // The default on Windows is to open at user.home, which is
448            // typically an absurd directory inside the O/S installation.
449            // So we use the current directory instead.
450            String cwd = StringUtilities.getProperty("user.dir");
451
452            if (cwd != null) {
453                fileDialog.setCurrentDirectory(new File(cwd));
454            }
455        }
456
457        fileDialog.setSelectedFile(
458                new File(fileDialog.getCurrentDirectory(), "plot.eps"));
459
460        int returnVal = fileDialog.showDialog(this, "Export");
461
462        if (returnVal == JFileChooser.APPROVE_OPTION) {
463            File file = fileDialog.getSelectedFile();
464            FileOutputStream fout = null;
465
466            try {
467                fout = new FileOutputStream(file);
468                plot.export(fout);
469            } catch (IOException ex) {
470                JOptionPane.showMessageDialog(this,
471                        "Error exporting plot to '" + file + "': " + ex,
472                        "Ptolemy II Error", JOptionPane.WARNING_MESSAGE);
473            } finally {
474                if (fout != null) {
475                    try {
476                        fout.close();
477                    } catch (Throwable throwable) {
478                        System.out.println("Ignoring failure to close stream "
479                                + "on " + file);
480                        throwable.printStackTrace();
481                    }
482                }
483            }
484        }
485    }
486
487    /** Display more detailed information than given by _about().
488     */
489    @Override
490    protected void _help() {
491        JOptionPane.showMessageDialog(this,
492                "PlotTableauFrame is a plot in a top-level window.\n"
493                        + "  File formats understood: Ptplot ASCII.\n"
494                        + "  Left mouse button: Zooming.",
495                "About Ptolemy Plot", JOptionPane.INFORMATION_MESSAGE);
496    }
497
498    /** Write the plot to the specified file in PlotML syntax.
499     *  @param file The file to which to write.
500     *  @exception IOException If the write fails.
501     */
502    @Override
503    protected void _writeFile(File file) throws IOException {
504        FileOutputStream out = null;
505        try {
506            out = new FileOutputStream(file);
507            plot.write(out);
508        } finally {
509            if (out != null) {
510                out.close();
511            }
512        }
513    }
514
515    ///////////////////////////////////////////////////////////////////
516    ////                         inner classes                     ////
517
518    ///////////////////////////////////////////////////////////////////
519    //// ExportImageAction
520
521    /** Export an image of a plot. */
522    public class ExportImageAction extends AbstractAction {
523
524        /** Create a new action to export an image.
525         *  @param formatName The name of the format, currently PNG and
526         *  GIF are supported.
527         */
528        public ExportImageAction(String formatName) {
529            super("Export " + formatName);
530            _formatName = formatName.toLowerCase(Locale.getDefault());
531            putValue("tooltip", "Export " + formatName + " image to a file.");
532            // putValue(GUIUtilities.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_G));
533        }
534
535        ///////////////////////////////////////////////////////////////////
536        ////                         public methods                   ////
537
538        /** Export an image.
539         *  @param e The ActionEvent that invoked this action.
540         */
541        @Override
542        public void actionPerformed(ActionEvent e) {
543            JFileChooserBugFix jFileChooserBugFix = new JFileChooserBugFix();
544            Color background = null;
545            try {
546                background = jFileChooserBugFix.saveBackground();
547
548                JFileChooser fileDialog = new JFileChooser();
549                fileDialog.setDialogTitle("Specify a file to write to.");
550                LinkedList extensions = new LinkedList();
551                extensions.add(_formatName);
552                fileDialog.addChoosableFileFilter(
553                        new ExtensionFilenameFilter(extensions));
554
555                if (_directory != null) {
556                    fileDialog.setCurrentDirectory(_directory);
557                } else {
558                    // The default on Windows is to open at user.home, which is
559                    // typically an absurd directory inside the O/S installation.
560                    // So we use the current directory instead.
561                    // This will throw a security exception in an applet.
562                    // FIXME: we should support users under applets opening files
563                    // on the server.
564                    String currentWorkingDirectory = StringUtilities
565                            .getProperty("user.dir");
566                    if (currentWorkingDirectory != null) {
567                        fileDialog.setCurrentDirectory(
568                                new File(currentWorkingDirectory));
569                    }
570                }
571
572                int returnVal = fileDialog.showDialog(PlotTableauFrame.this,
573                        "Export "
574                                + _formatName.toUpperCase(Locale.getDefault()));
575
576                if (returnVal == JFileChooser.APPROVE_OPTION) {
577                    _directory = fileDialog.getCurrentDirectory();
578                    File file = fileDialog.getSelectedFile().getCanonicalFile();
579
580                    if (file.getName().indexOf(".") == -1) {
581                        // If the user has not given the file an extension, add it
582                        file = new File(
583                                file.getAbsolutePath() + "." + _formatName);
584                    }
585                    if (file.exists()) {
586                        if (!MessageHandler.yesNoQuestion(
587                                "Overwrite " + file.getName() + "?")) {
588                            return;
589                        }
590                    }
591                    OutputStream out = null;
592                    try {
593                        out = new FileOutputStream(file);
594                        plot.exportImage(out, _formatName);
595                    } finally {
596                        if (out != null) {
597                            out.close();
598                        }
599                    }
600
601                    // Open the PNG file.
602                    // FIXME: We don't do the right thing with PNG files.
603                    // It just opens in a text editor.
604                    // _read(file.toURI().toURL());
605                    MessageHandler.message(
606                            "Image file exported to " + file.getName());
607                }
608            } catch (Exception ex) {
609                MessageHandler.error("Export to "
610                        + _formatName.toUpperCase(Locale.getDefault())
611                        + " failed", ex);
612            } finally {
613                jFileChooserBugFix.restoreBackground(background);
614            }
615        }
616
617        private String _formatName;
618    }
619
620    /** Format the plot. */
621    private class FormatAction extends AbstractAction {
622        /** Create a new action to format the plot. */
623        public FormatAction() {
624            super("Format");
625            putValue("tooltip", "Open a dialog to format the plot.");
626            putValue(Action.MNEMONIC_KEY, Integer.valueOf(KeyEvent.VK_F));
627        }
628
629        @Override
630        public void actionPerformed(ActionEvent e) {
631            try {
632                _editFormat();
633            } catch (Exception exception) {
634                // If we do not catch exceptions here, then they
635                // disappear to stdout, which is bad if we launched
636                // where there is no stdout visible.
637                JOptionPane.showMessageDialog(null,
638                        "Format Exception:\n" + exception.toString(),
639                        "Ptolemy Plot Error", JOptionPane.WARNING_MESSAGE);
640            }
641
642            // NOTE: The following should not be needed, but there jdk1.3beta
643            // appears to have a bug in swing where repainting doesn't
644            // properly occur.
645            repaint();
646        }
647    }
648
649    class SpecialMenuListener implements ActionListener {
650        @Override
651        public void actionPerformed(ActionEvent e) {
652            JMenuItem target = (JMenuItem) e.getSource();
653            String actionCommand = target.getActionCommand();
654
655            try {
656                if (actionCommand.equals("Fill")) {
657                    plot.fillPlot();
658                } else if (actionCommand.equals("Reset axes")) {
659                    plot.resetAxes();
660                } else if (actionCommand.equals("Clear")) {
661                    plot.clear(false);
662                    plot.repaint();
663                } else if (actionCommand.equals("Export")) {
664                    _export();
665                } else if (actionCommand.equals("Sample plot")) {
666                    plot.clear(true);
667                    samplePlot();
668                }
669            } catch (Exception exception) {
670                // If we do not catch exceptions here, then they
671                // disappear to stdout, which is bad if we launched
672                // where there is no stdout visible.
673                JOptionPane.showMessageDialog(null,
674                        "Special Menu Exception:\n" + exception.toString(),
675                        "Ptolemy Plot Error", JOptionPane.WARNING_MESSAGE);
676            }
677
678            // NOTE: The following should not be needed, but there jdk1.3beta
679            // appears to have a bug in swing where repainting doesn't
680            // properly occur.
681            repaint();
682        }
683    }
684
685    ///////////////////////////////////////////////////////////////////
686    ////                         inner classes                     ////
687
688    /** Display only .eps files. */
689    static class EPSFileFilter extends FileFilter {
690        // FindBugs suggests making this class static so as to decrease
691        // the size of instances and avoid dangling references.
692
693        /** Accept only .eps files.
694         *  @param fileOrDirectory The file to be checked.
695         *  @return true if the file is a directory, a .eps file
696         */
697        @Override
698        public boolean accept(File fileOrDirectory) {
699            if (fileOrDirectory.isDirectory()) {
700                return true;
701            }
702
703            String fileOrDirectoryName = fileOrDirectory.getName();
704            int dotIndex = fileOrDirectoryName.lastIndexOf('.');
705
706            if (dotIndex == -1) {
707                return false;
708            }
709
710            String extension = fileOrDirectoryName.substring(dotIndex);
711
712            if (extension != null) {
713                if (extension.equalsIgnoreCase(".eps")) {
714                    return true;
715                } else {
716                    return false;
717                }
718            }
719
720            return false;
721        }
722
723        /**  The description of this filter */
724        @Override
725        public String getDescription() {
726            return "Encapsulated PostScript (.eps) files";
727        }
728    }
729}