Back to index

wims  3.65+svn20090927
PlotBox.java
Go to the documentation of this file.
00001 /* A labeled box for signal plots.
00002 
00003  @Copyright (c) 1997-2008 The Regents of the University of California.
00004  All rights reserved.
00005 
00006  Permission is hereby granted, without written agreement and without
00007  license or royalty fees, to use, copy, modify, and distribute this
00008  software and its documentation for any purpose, provided that the
00009  above copyright notice and the following two paragraphs appear in all
00010  copies of this software.
00011 
00012  IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
00013  FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
00014  ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
00015  THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
00016  SUCH DAMAGE.
00017 
00018  THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
00019  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
00020  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
00021  PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
00022  CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
00023  ENHANCEMENTS, OR MODIFICATIONS.
00024 
00025  PT_COPYRIGHT_VERSION_2
00026  COPYRIGHTENDKEY
00027  */
00028 package ptolemy.plot;
00029 
00030 import java.awt.Color;
00031 import java.awt.Component;
00032 import java.awt.Dimension;
00033 import java.awt.EventQueue;
00034 import java.awt.FlowLayout;
00035 import java.awt.Font;
00036 import java.awt.FontMetrics;
00037 import java.awt.Graphics;
00038 import java.awt.Graphics2D;
00039 import java.awt.Rectangle;
00040 import java.awt.RenderingHints;
00041 import java.awt.event.ActionEvent;
00042 import java.awt.event.ActionListener;
00043 import java.awt.event.InputEvent;
00044 import java.awt.event.KeyEvent;
00045 import java.awt.event.KeyListener;
00046 import java.awt.event.MouseEvent;
00047 import java.awt.event.MouseListener;
00048 import java.awt.event.MouseMotionListener;
00049 import java.awt.image.BufferedImage;
00050 import java.awt.print.PageFormat;
00051 import java.awt.print.Printable;
00052 import java.awt.print.PrinterException;
00053 import java.awt.print.PrinterJob;
00054 import java.io.BufferedOutputStream;
00055 import java.io.BufferedReader;
00056 import java.io.BufferedWriter;
00057 import java.io.DataInputStream;
00058 import java.io.FileInputStream;
00059 import java.io.FileNotFoundException;
00060 import java.io.IOException;
00061 import java.io.InputStream;
00062 import java.io.InputStreamReader;
00063 import java.io.OutputStream;
00064 import java.io.OutputStreamWriter;
00065 import java.io.PrintWriter;
00066 import java.io.Writer;
00067 import java.net.MalformedURLException;
00068 import java.net.URL;
00069 import java.util.Enumeration;
00070 import java.util.Iterator;
00071 import java.util.LinkedList;
00072 import java.util.List;
00073 import java.util.Vector;
00074 
00075 import javax.print.attribute.HashPrintRequestAttributeSet;
00076 import javax.print.attribute.PrintRequestAttributeSet;
00077 import javax.swing.ImageIcon;
00078 import javax.swing.JButton;
00079 import javax.swing.JOptionPane;
00080 import javax.swing.JPanel;
00081 import javax.swing.SwingUtilities;
00082 
00083 import ptolemy.util.FileUtilities;
00084 import ptolemy.util.StringUtilities;
00085 
00086 // TO DO:
00087 //   - Augment getColorByName to support a full complement of colors
00088 //     (get the color list from Tycho).
00091 
00244 public class PlotBox extends JPanel implements Printable {
00245     // jm.evers //                                                                                                                         
00246     public String ReadPlotApplet(){                                                                                                        
00247         String reply="error\nReadApplet() not implemented\nNO SENSIBLE USER INTERACTION PRESENT";                                          
00248         return reply;                                                                                                                      
00249     }                                                                                                                                      
00250                                                                                                                                            
00251     // jm.evers //  
00254 
00256     public PlotBox() {
00257         // If we make this transparent, the background shows through.
00258         // However, we assume that the user will set the background.
00259         // NOTE: A component is transparent by default (?).
00260         // setOpaque(false);
00261         setOpaque(true);
00262 
00263         // Create a right-justified layout with spacing of 2 pixels.
00264         setLayout(new FlowLayout(FlowLayout.RIGHT, 2, 2));
00265         addMouseListener(new ZoomListener());
00266         addKeyListener(new CommandListener());
00267         addMouseMotionListener(new DragListener());
00268 
00269         // This is something we want to do only once...
00270         _measureFonts();
00271 
00272         // Request the focus so that key events are heard.
00273         // NOTE: no longer needed?
00274         // requestFocus();
00275     }
00276 
00279 
00288     public synchronized void addLegend(int dataset, String legend) {
00289         // Changing legend means we need to repaint the offscreen buffer.
00290         _plotImage = null;
00291 
00292         if ((legend == null) || legend.equals("")) {
00293             return;
00294         }
00295 
00296         _legendStrings.addElement(legend);
00297         _legendDatasets.addElement(Integer.valueOf(dataset));
00298     }
00299 
00308     public synchronized void addXTick(String label, double position) {
00309         // Changing legend means we need to repaint the offscreen buffer.
00310         _plotImage = null;
00311 
00312         if (_xticks == null) {
00313             _xticks = new Vector();
00314             _xticklabels = new Vector();
00315         }
00316 
00317         _xticks.addElement(Double.valueOf(position));
00318         _xticklabels.addElement(label);
00319     }
00320 
00329     public synchronized void addYTick(String label, double position) {
00330         // Changing legend means we need to repaint the offscreen buffer.
00331         _plotImage = null;
00332 
00333         if (_yticks == null) {
00334             _yticks = new Vector();
00335             _yticklabels = new Vector();
00336         }
00337 
00338         _yticks.addElement(Double.valueOf(position));
00339         _yticklabels.addElement(label);
00340     }
00341 
00348     public synchronized void clear(boolean axes) {
00349         // We need to repaint the offscreen buffer.
00350         _plotImage = null;
00351 
00352         _xBottom = Double.MAX_VALUE;
00353         _xTop = -Double.MAX_VALUE;
00354         _yBottom = Double.MAX_VALUE;
00355         _yTop = -Double.MAX_VALUE;
00356 
00357         if (axes) {
00358             // Protected members first.
00359             _yMax = 0;
00360             _yMin = 0;
00361             _xMax = 0;
00362             _xMin = 0;
00363             _xRangeGiven = false;
00364             _yRangeGiven = false;
00365             _originalXRangeGiven = false;
00366             _originalYRangeGiven = false;
00367             _rangesGivenByZooming = false;
00368             _xlog = false;
00369             _ylog = false;
00370             _grid = true;
00371             _wrap = false;
00372             _usecolor = true;
00373 
00374             // Private members next...
00375             _filespec = null;
00376             _xlabel = null;
00377             _ylabel = null;
00378             _title = null;
00379             _legendStrings = new Vector();
00380             _legendDatasets = new Vector();
00381             _xticks = null;
00382             _xticklabels = null;
00383             _yticks = null;
00384             _yticklabels = null;
00385         }
00386     }
00387 
00390     public synchronized void clearLegends() {
00391         // Changing legend means we need to repaint the offscreen buffer.
00392         _plotImage = null;
00393 
00394         _legendStrings = new Vector();
00395         _legendDatasets = new Vector();
00396     }
00397 
00413     public void deferIfNecessary(Runnable action) {
00414         // In swing, updates to showing graphics must be done in the
00415         // event thread.  If we are in the event thread, then proceed.
00416         // Otherwise, queue a request or add to a pending request.
00417         if (EventQueue.isDispatchThread()) {
00418             action.run();
00419         } else {
00420             if (_deferredActions == null) {
00421                 _deferredActions = new LinkedList();
00422             }
00423 
00424             // Add the specified action to the list of actions to perform.
00425             _deferredActions.add(action);
00426 
00427             // If it hasn't already been requested, request that actions
00428             // be performed in the event dispatch thread.
00429             if (!_actionsDeferred) {
00430                 Runnable doActions = new Runnable() {
00431                     public void run() {
00432                         _executeDeferredActions();
00433                     }
00434                 };
00435 
00436                 try {
00437                     // NOTE: Using invokeAndWait() here risks causing
00438                     // deadlock.  Don't do it!
00439                     SwingUtilities.invokeLater(doActions);
00440                 } catch (Exception ex) {
00441                     // Ignore InterruptedException.
00442                     // Other exceptions should not occur.
00443                 }
00444 
00445                 _actionsDeferred = true;
00446             }
00447         }
00448     }
00449 
00459     public synchronized void export(OutputStream out) {
00460         try {
00461             EPSGraphics g = new EPSGraphics(out, _width, _height);
00462             _drawPlot(g, false);
00463             g.showpage();
00464         } catch (RuntimeException ex) {
00465             String message = "Export failed: " + ex.getMessage();
00466             JOptionPane.showMessageDialog(this, message,
00467                     "Ptolemy Plot Message", JOptionPane.ERROR_MESSAGE);
00468 
00469             // Rethrow the exception so that we don't report success,
00470             // and so the stack trace is displayed on standard out.
00471             throw (RuntimeException) ex.fillInStackTrace();
00472         }
00473     }
00474 
00475     // CONTRIBUTED CODE.
00476     // I wanted the ability to use the Plot object in a servlet and to
00477     // write out the resultant images. The following routines,
00478     // particularly exportImage(), permit this. I also had to make some
00479     // minor changes elsewhere. Rob Kroeger, May 2001.
00480     // NOTE: This code has been modified by EAL to conform with Ptolemy II
00481     // coding style.
00482 
00490     public synchronized BufferedImage exportImage() {
00491         Rectangle rectangle = new Rectangle(_preferredWidth, _preferredHeight);
00492         return exportImage(new BufferedImage(rectangle.width, rectangle.height,
00493                 BufferedImage.TYPE_INT_ARGB), rectangle,
00494                 _defaultImageRenderingHints(), false);
00495     }
00496 
00505     public synchronized BufferedImage exportImage(Rectangle rectangle) {
00506         return exportImage(new BufferedImage(rectangle.width, rectangle.height,
00507                 BufferedImage.TYPE_INT_ARGB), rectangle,
00508                 _defaultImageRenderingHints(), false);
00509     }
00510 
00525     public synchronized BufferedImage exportImage(BufferedImage bufferedImage,
00526             Rectangle rectangle, RenderingHints hints, boolean transparent) {
00527         Graphics2D graphics = bufferedImage.createGraphics();
00528         graphics.addRenderingHints(_defaultImageRenderingHints());
00529 
00530         if (!transparent) {
00531             graphics.setColor(Color.white); // set the background color
00532             graphics.fill(rectangle);
00533         }
00534 
00535         _drawPlot(graphics, false, rectangle);
00536         return bufferedImage;
00537     }
00538 
00550     public synchronized BufferedImage exportImage(BufferedImage bufferedImage) {
00551         return exportImage(bufferedImage, new Rectangle(bufferedImage
00552                 .getWidth(), bufferedImage.getHeight()),
00553                 _defaultImageRenderingHints(), true);
00554     }
00555 
00563     public synchronized void fillPlot() {
00564         // NOTE: These used to be _setXRange() and _setYRange() to avoid
00565         // confusing this with user-specified ranges.  But we want to treat
00566         // a fill command as a user specified range.
00567         // EAL, 6/12/00.
00568         setXRange(_xBottom, _xTop);
00569         setYRange(_yBottom, _yTop);
00570         repaint();
00571 
00572         // Reacquire the focus so that key bindings work.
00573         // NOTE: no longer needed?
00574         // requestFocus();
00575     }
00576 
00580     public boolean getColor() {
00581         return _usecolor;
00582     }
00583 
00588     public Color[] getColors() {
00589         return _colors;
00590     }
00591 
00597     public static Color getColorByName(String name) {
00598         try {
00599             // Check to see if it is a hexadecimal
00600             if (name.startsWith("#")) {
00601                 name = name.substring(1);
00602             }
00603 
00604             Color col = new Color(Integer.parseInt(name, 16));
00605             return col;
00606         } catch (NumberFormatException e) {
00607         }
00608 
00609         // FIXME: This is a poor excuse for a list of colors and values.
00610         // We should use a hash table here.
00611         // Note that Color decode() wants the values to start with 0x.
00612         String[][] names = { { "black", "00000" }, { "white", "ffffff" },
00613                 { "red", "ff0000" }, { "green", "00ff00" },
00614                 { "blue", "0000ff" } };
00615 
00616         for (int i = 0; i < names.length; i++) {
00617             if (name.equals(names[i][0])) {
00618                 try {
00619                     Color col = new Color(Integer.parseInt(names[i][1], 16));
00620                     return col;
00621                 } catch (NumberFormatException e) {
00622                 }
00623             }
00624         }
00625 
00626         return null;
00627     }
00628 
00633     public String getDataurl() {
00634         return _filespec;
00635     }
00636 
00641     public URL getDocumentBase() {
00642         return _documentBase;
00643     }
00644 
00648     public boolean getGrid() {
00649         return _grid;
00650     }
00651 
00657     public synchronized String getLegend(int dataset) {
00658         int idx = _legendDatasets.indexOf(Integer.valueOf(dataset), 0);
00659 
00660         if (idx != -1) {
00661             return (String) _legendStrings.elementAt(idx);
00662         } else {
00663             return null;
00664         }
00665     }
00666 
00674     public synchronized int getLegendDataset(String legend) {
00675         int index = _legendStrings.indexOf(legend);
00676 
00677         if (index == -1) {
00678             return -1;
00679         }
00680 
00681         return ((Integer) _legendDatasets.get(index)).intValue();
00682     }
00683 
00691     //     public synchronized Dimension getMaximumSize() {
00692     //         if (_sizeHasBeenSet) {
00693     //             return new Dimension(_preferredWidth, _preferredHeight);
00694     //         } else {
00695     //             return super.getMaximumSize();
00696     //         }
00697     //     }
00705     //     public synchronized Dimension getMinimumSize() {
00706     //         if (_sizeHasBeenSet) {
00707     //             return new Dimension(_preferredWidth, _preferredHeight);
00708     //         } else {
00709     //             return super.getMinimumSize();
00710     //         }
00711     //     }
00722     public Rectangle getPlotRectangle() {
00723         return new Rectangle(_ulx, _uly, _lrx - _ulx, _lry - _uly);
00724     }
00725 
00732     public synchronized Dimension getPreferredSize() {
00733         return new Dimension(_preferredWidth, _preferredHeight);
00734     }
00735 
00739     public synchronized String getTitle() {
00740         if (_title == null) {
00741             return "";
00742         }
00743 
00744         return _title;
00745     }
00746 
00754     public synchronized double[] getXAutoRange() {
00755         double[] result = new double[2];
00756         result[0] = _xBottom;
00757         result[1] = _xTop;
00758         return result;
00759     }
00760 
00765     public synchronized String getXLabel() {
00766         return _xlabel;
00767     }
00768 
00772     public boolean getXLog() {
00773         return _xlog;
00774     }
00775 
00786     public synchronized double[] getXRange() {
00787         double[] result = new double[2];
00788 
00789         if (_xRangeGiven) {
00790             result[0] = _xlowgiven;
00791             result[1] = _xhighgiven;
00792         } else {
00793             // Have to first correct for the padding.
00794             result[0] = _xMin + ((_xMax - _xMin) * _padding);
00795             result[1] = _xMax - ((_xMax - _xMin) * _padding);
00796             ;
00797         }
00798 
00799         return result;
00800     }
00801 
00808     public synchronized Vector[] getXTicks() {
00809         if (_xticks == null) {
00810             return null;
00811         }
00812 
00813         Vector[] result = new Vector[2];
00814         result[0] = _xticks;
00815         result[1] = _xticklabels;
00816         return result;
00817     }
00818 
00826     public synchronized double[] getYAutoRange() {
00827         double[] result = new double[2];
00828         result[0] = _yBottom;
00829         result[1] = _yTop;
00830         return result;
00831     }
00832 
00837     public String getYLabel() {
00838         return _ylabel;
00839     }
00840 
00844     public boolean getYLog() {
00845         return _ylog;
00846     }
00847 
00858     public synchronized double[] getYRange() {
00859         double[] result = new double[2];
00860 
00861         if (_yRangeGiven) {
00862             result[0] = _ylowgiven;
00863             result[1] = _yhighgiven;
00864         } else {
00865             // Have to first correct for the padding.
00866             result[0] = _yMin + ((_yMax - _yMin) * _padding);
00867             result[1] = _yMax - ((_yMax - _yMin) * _padding);
00868             ;
00869         }
00870 
00871         return result;
00872     }
00873 
00880     public synchronized Vector[] getYTicks() {
00881         if (_yticks == null) {
00882             return null;
00883         }
00884 
00885         Vector[] result = new Vector[2];
00886         result[0] = _yticks;
00887         result[1] = _yticklabels;
00888         return result;
00889     }
00890 
00896     public void init() {
00897         setButtons(true);
00898 
00899         if (_filespec != null) {
00900             parseFile(_filespec, _documentBase);
00901         }
00902     }
00903 
00908     public synchronized void paintComponent(Graphics graphics) {
00909         //  super.paintComponent(graphics);
00910         //         _drawPlot(graphics, true);
00911         BufferedImage newPlotImage = _plotImage;
00912 
00913         if (newPlotImage == null) {
00914             Rectangle bounds = getBounds();
00915             newPlotImage = new BufferedImage(bounds.width, bounds.height,
00916                     BufferedImage.TYPE_3BYTE_BGR);
00917             _plotImage = newPlotImage;
00918 
00919             Graphics2D offScreenGraphics = newPlotImage.createGraphics();
00920             super.paintComponent(offScreenGraphics);
00921             _drawPlot(offScreenGraphics, true);
00922         }
00923 
00924         // Blit the offscreen image onto the screen.
00925         graphics.drawImage(newPlotImage, 0, 0, null);
00926 
00927         // Acquire the focus so that key bindings work.
00928         // NOTE: no longer needed?
00929         // requestFocus();
00930     }
00931 
00938     public void parseFile(String filespec) {
00939         parseFile(filespec, (URL) null);
00940     }
00941 
00945     public synchronized void parseFile(String filespec, URL documentBase) {
00946         DataInputStream in = null;
00947 
00948         if ((filespec == null) || (filespec.length() == 0)) {
00949             // Open up stdin
00950             in = new DataInputStream(System.in);
00951         } else {
00952             try {
00953                 URL url = null;
00954 
00955                 if ((documentBase == null) && (_documentBase != null)) {
00956                     documentBase = _documentBase;
00957                 }
00958 
00959                 if (documentBase == null) {
00960                     url = new URL(filespec);
00961                 } else {
00962                     try {
00963                         url = new URL(documentBase, filespec);
00964                     } catch (NullPointerException e) {
00965                         // If we got a NullPointerException, then perhaps we
00966                         // are calling this as an application, not as an applet
00967                         url = new URL(filespec);
00968                     }
00969                 }
00970 
00971                 in = new DataInputStream(url.openStream());
00972             } catch (MalformedURLException e) {
00973                 try {
00974                     // Just try to open it as a file.
00975                     in = new DataInputStream(new FileInputStream(filespec));
00976                 } catch (FileNotFoundException me) {
00977                     _errorMsg = new String[2];
00978                     _errorMsg[0] = "File not found: " + filespec;
00979                     _errorMsg[1] = me.getMessage();
00980                     return;
00981                 } catch (SecurityException me) {
00982                     _errorMsg = new String[2];
00983                     _errorMsg[0] = "Security Exception: " + filespec;
00984                     _errorMsg[1] = me.getMessage();
00985                     return;
00986                 }
00987             } catch (IOException ioe) {
00988                 _errorMsg = new String[3];
00989                 _errorMsg[0] = "Failure opening URL: ";
00990                 _errorMsg[1] = " " + filespec;
00991                 _errorMsg[2] = ioe.getMessage();
00992                 return;
00993             }
00994         }
00995 
00996         // At this point, we've opened the data source, now read it in
00997         try {
00998             BufferedReader din = new BufferedReader(new InputStreamReader(in));
00999             String line = din.readLine();
01000 
01001             while (line != null) {
01002                 _parseLine(line);
01003                 line = din.readLine();
01004             }
01005         } catch (MalformedURLException e) {
01006             _errorMsg = new String[2];
01007             _errorMsg[0] = "Malformed URL: " + filespec;
01008             _errorMsg[1] = e.getMessage();
01009             return;
01010         } catch (IOException e) {
01011             _errorMsg = new String[2];
01012             _errorMsg[0] = "Failure reading data: " + filespec;
01013             _errorMsg[1] = e.getMessage();
01014             _errorMsg[1] = e.getMessage();
01015         } finally {
01016             try {
01017                 in.close();
01018             } catch (IOException me) {
01019             }
01020         }
01021     }
01022 
01032     public synchronized int print(Graphics graphics, PageFormat format,
01033             int index) throws PrinterException {
01034 
01035         if (graphics == null) {
01036             return Printable.NO_SUCH_PAGE;
01037         }
01038 
01039         // We only print on one page.
01040         if (index >= 1) {
01041             return Printable.NO_SUCH_PAGE;
01042         }
01043 
01044         Graphics2D graphics2D = (Graphics2D) graphics;
01045 
01046         // Scale the printout to fit the pages.
01047         // Contributed by Laurent ETUR, Schlumberger Riboud Product Center
01048         double scalex = format.getImageableWidth() / getWidth();
01049         double scaley = format.getImageableHeight() / getHeight();
01050         double scale = Math.min(scalex, scaley);
01051         graphics2D.translate((int) format.getImageableX(), (int) format
01052                 .getImageableY());
01053         graphics2D.scale(scale, scale);
01054         _drawPlot(graphics, true);
01055         return Printable.PAGE_EXISTS;
01056     }
01057 
01088     public synchronized void read(InputStream in) throws IOException {
01089         try {
01090             // NOTE: I tried to use exclusively the jdk 1.1 Reader classes,
01091             // but they provide no support like DataInputStream, nor
01092             // support for URL accesses.  So I use the older classes
01093             // here in a strange mixture.
01094             BufferedReader din = new BufferedReader(new InputStreamReader(in));
01095 
01096             try {
01097                 String line = din.readLine();
01098 
01099                 while (line != null) {
01100                     _parseLine(line);
01101                     line = din.readLine();
01102                 }
01103             } finally {
01104                 din.close();
01105             }
01106         } catch (IOException e) {
01107             _errorMsg = new String[2];
01108             _errorMsg[0] = "Failure reading input data.";
01109             _errorMsg[1] = e.getMessage();
01110             throw e;
01111         }
01112     }
01113 
01118     public synchronized void read(String command) {
01119         _parseLine(command);
01120     }
01121 
01127     public synchronized void removeLegend(int dataset) {
01128         final int len = _legendDatasets.size();
01129         int foundIndex = -1;
01130         boolean found = false;
01131 
01132         for (int i = 0; (i < len) && !found; ++i) {
01133             if (((Integer) _legendDatasets.get(i)).intValue() == dataset) {
01134                 foundIndex = i;
01135                 found = true;
01136             }
01137         }
01138 
01139         if (found) {
01140             _legendDatasets.remove(foundIndex);
01141             _legendStrings.remove(foundIndex);
01142         }
01143     }
01144 
01151     public synchronized void renameLegend(int dataset, String newName) {
01152         int index = _legendDatasets.indexOf(Integer.valueOf(dataset), 0);
01153 
01154         if (index != -1) {
01155             _legendStrings.setElementAt(newName, index);
01156 
01157             // Changing legend means we need to repaint the offscreen buffer.
01158             _plotImage = null;
01159         }
01160     }
01161 
01168     public synchronized void resetAxes() {
01169         setXRange(_originalXlow, _originalXhigh);
01170         setYRange(_originalYlow, _originalYhigh);
01171         repaint();
01172     }
01173 
01177     public void samplePlot() {
01178         // Empty default implementation.
01179     }
01180 
01184     public synchronized void setBackground(Color background) {
01185         // Changing legend means we need to repaint the offscreen buffer.
01186         _plotImage = null;
01187 
01188         _background = background;
01189         super.setBackground(_background);
01190     }
01191 
01201     public synchronized void setBounds(int x, int y, int width, int height) {
01202         _width = width;
01203         _height = height;
01204 
01205         // Resizing the component means we need to redraw the buffer.
01206         _plotImage = null;
01207 
01208         super.setBounds(x, y, _width, _height);
01209     }
01210 
01221     public synchronized void setButtons(boolean visible) {
01222         // Changing legend means we need to repaint the offscreen buffer.
01223         _plotImage = null;
01224 
01225         if (_printButton == null) {
01226             // Load the image by using the absolute path to the gif.
01227             URL img = null;
01228             try {
01229                 // FindBugs: Usage of GetResource may be unsafe if
01230                 // class is extended
01231                 img = FileUtilities.nameToURL(
01232                         "$CLASSPATH/ptolemy/plot/img/print.gif", null, null);
01233             } catch (IOException ex) {
01234                 ex.printStackTrace();
01235             }
01236 
01237             if (img != null) {
01238                 ImageIcon printIcon = new ImageIcon(img);
01239                 _printButton = new JButton(printIcon);
01240                 _printButton.setBorderPainted(false);
01241             } else {
01242                 // Backup in case something goes wrong with the
01243                 // class loader.
01244                 _printButton = new JButton("P");
01245             }
01246 
01247             // FIXME: If we failed to get an image, then the letter "P"
01248             // Is not likely to fit into a 20x20 button.
01249             _printButton.setPreferredSize(new Dimension(20, 20));
01250             _printButton.setToolTipText("Print the plot.");
01251             _printButton.addActionListener(new ButtonListener());
01252             add(_printButton);
01253         }
01254 
01255         _printButton.setVisible(visible);
01256 
01257         if (_resetButton == null) {
01258             // Load the image by using the absolute path to the gif.
01259             URL img = null;
01260             try {
01261                 // FindBugs: Usage of GetResource may be unsafe if
01262                 // class is extended
01263                 img = FileUtilities.nameToURL(
01264                         "$CLASSPATH/ptolemy/plot/img/reset.gif", null, null);
01265             } catch (IOException ex) {
01266                 ex.printStackTrace();
01267             }
01268             if (img != null) {
01269                 ImageIcon resetIcon = new ImageIcon(img);
01270                 _resetButton = new JButton(resetIcon);
01271                 _resetButton.setBorderPainted(false);
01272             } else {
01273                 // Backup in case something goes wrong with the
01274                 // class loader.
01275                 _resetButton = new JButton("R");
01276             }
01277 
01278             // FIXME: If we failed to get an image, then the letter "R"
01279             // Is not likely to fit into a 20x20 button.
01280             _resetButton.setPreferredSize(new Dimension(20, 20));
01281             _resetButton
01282                     .setToolTipText("Reset X and Y ranges to their original values");
01283             _resetButton.addActionListener(new ButtonListener());
01284             add(_resetButton);
01285         }
01286 
01287         _resetButton.setVisible(visible);
01288 
01289         if (_formatButton == null) {
01290             // Load the image by using the absolute path to the gif.
01291             URL img = null;
01292             try {
01293                 // FindBugs: Usage of GetResource may be unsafe if
01294                 // class is extended
01295                 img = FileUtilities.nameToURL(
01296                         "$CLASSPATH/ptolemy/plot/img/format.gif", null, null);
01297             } catch (IOException ex) {
01298                 ex.printStackTrace();
01299             }
01300             if (img != null) {
01301                 ImageIcon formatIcon = new ImageIcon(img);
01302                 _formatButton = new JButton(formatIcon);
01303                 _formatButton.setBorderPainted(false);
01304             } else {
01305                 // Backup in case something goes wrong with the
01306                 // class loader.
01307                 _formatButton = new JButton("S");
01308             }
01309 
01310             // FIXME: If we failed to get an image, then the letter "S"
01311             // Is not likely to fit into a 20x20 button.
01312             _formatButton.setPreferredSize(new Dimension(20, 20));
01313             _formatButton.setToolTipText("Set the plot format");
01314             _formatButton.addActionListener(new ButtonListener());
01315             add(_formatButton);
01316         }
01317 
01318         _formatButton.setVisible(visible);
01319 
01320         if (_fillButton == null) {
01321             // Load the image by using the absolute path to the gif.
01322             URL img = null;
01323             try {
01324                 // FindBugs: Usage of GetResource may be unsafe if
01325                 // class is extended
01326                 img = FileUtilities.nameToURL(
01327                         "$CLASSPATH/ptolemy/plot/img/fill.gif", null, null);
01328             } catch (IOException ex) {
01329                 ex.printStackTrace();
01330             }
01331             if (img != null) {
01332                 ImageIcon fillIcon = new ImageIcon(img);
01333                 _fillButton = new JButton(fillIcon);
01334                 _fillButton.setBorderPainted(false);
01335             } else {
01336                 // Backup in case something goes wrong with the
01337                 // class loader.
01338                 _fillButton = new JButton("F");
01339             }
01340 
01341             // FIXME: If we failed to get an image, then the letter "F"
01342             // Is not likely to fit into a 20x20 button.
01343             _fillButton.setPreferredSize(new Dimension(20, 20));
01344             _fillButton.setToolTipText("Rescale the plot to fit the data");
01345             _fillButton.addActionListener(new ButtonListener());
01346             add(_fillButton);
01347         }
01348 
01349         _fillButton.setVisible(visible);
01350 
01351         // Request the focus so that key events are heard.
01352         // NOTE: no longer needed?
01353         // requestFocus();
01354     }
01355 
01360     public synchronized void setColor(boolean useColor) {
01361         // Changing legend means we need to repaint the offscreen buffer.
01362         _plotImage = null;
01363 
01364         _usecolor = useColor;
01365     }
01366 
01373     public synchronized void setColors(Color[] colors) {
01374         // Changing legend means we need to repaint the offscreen buffer.
01375         _plotImage = null;
01376 
01377         _colors = colors;
01378     }
01379 
01384     public void setDataurl(String filespec) {
01385         _filespec = filespec;
01386     }
01387 
01392     public void setDocumentBase(URL documentBase) {
01393         _documentBase = documentBase;
01394     }
01395 
01399     public synchronized void setForeground(Color foreground) {
01400         // Changing legend means we need to repaint the offscreen buffer.
01401         _plotImage = null;
01402 
01403         _foreground = foreground;
01404         super.setForeground(_foreground);
01405     }
01406 
01410     public synchronized void setGrid(boolean grid) {
01411         // Changing legend means we need to repaint the offscreen buffer.
01412         _plotImage = null;
01413 
01414         _grid = grid;
01415     }
01416 
01422     public synchronized void setLabelFont(String name) {
01423         // Changing legend means we need to repaint the offscreen buffer.
01424         _plotImage = null;
01425 
01426         _labelFont = Font.decode(name);
01427         _labelFontMetrics = getFontMetrics(_labelFont);
01428     }
01429 
01435     public synchronized void setPlotRectangle(Rectangle rectangle) {
01436         // Changing legend means we need to repaint the offscreen buffer.
01437         _plotImage = null;
01438 
01439         _specifiedPlotRectangle = rectangle;
01440     }
01441 
01453     public synchronized void setSize(int width, int height) {
01454         // Changing legend means we need to repaint the offscreen buffer.
01455         _plotImage = null;
01456 
01457         _width = width;
01458         _height = height;
01459         _preferredWidth = width;
01460         _preferredHeight = height;
01461 
01462         //_sizeHasBeenSet = true;
01463         super.setSize(width, height);
01464     }
01465 
01469     public synchronized void setTitle(String title) {
01470         // Changing legend means we need to repaint the offscreen buffer.
01471         _plotImage = null;
01472 
01473         _title = title;
01474     }
01475 
01481     public synchronized void setTitleFont(String name) {
01482         // Changing legend means we need to repaint the offscreen buffer.
01483         _plotImage = null;
01484 
01485         _titleFont = Font.decode(name);
01486         _titleFontMetrics = getFontMetrics(_titleFont);
01487     }
01488 
01497     public synchronized void setWrap(boolean wrap) {
01498         // Changing legend means we need to repaint the offscreen buffer.
01499         _plotImage = null;
01500 
01501         _wrap = wrap;
01502 
01503         if (!_xRangeGiven) {
01504             if (_xBottom > _xTop) {
01505                 // have nothing to go on.
01506                 setXRange(0, 0);
01507             } else {
01508                 setXRange(_xBottom, _xTop);
01509             }
01510         }
01511 
01512         _wrapLow = _xlowgiven;
01513         _wrapHigh = _xhighgiven;
01514     }
01515 
01519     public synchronized void setXLabel(String label) {
01520         // Changing legend means we need to repaint the offscreen buffer.
01521         _plotImage = null;
01522 
01523         _xlabel = label;
01524     }
01525 
01532     public synchronized void setXLog(boolean xlog) {
01533         // Changing legend means we need to repaint the offscreen buffer.
01534         _plotImage = null;
01535 
01536         _xlog = xlog;
01537     }
01538 
01546     public synchronized void setXRange(double min, double max) {
01547         // Changing legend means we need to repaint the offscreen buffer.
01548         _plotImage = null;
01549 
01550         _xRangeGiven = true;
01551         _xlowgiven = min;
01552         _xhighgiven = max;
01553         _setXRange(min, max);
01554     }
01555 
01559     public synchronized void setYLabel(String label) {
01560         // Changing legend means we need to repaint the offscreen buffer.
01561         _plotImage = null;
01562 
01563         _ylabel = label;
01564     }
01565 
01572     public synchronized void setYLog(boolean ylog) {
01573         // Changing legend means we need to repaint the offscreen buffer.
01574         _plotImage = null;
01575 
01576         _ylog = ylog;
01577     }
01578 
01586     public synchronized void setYRange(double min, double max) {
01587         // Changing legend means we need to repaint the offscreen buffer.
01588         _plotImage = null;
01589 
01590         _yRangeGiven = true;
01591         _ylowgiven = min;
01592         _yhighgiven = max;
01593         _setYRange(min, max);
01594     }
01595 
01607     public void write(OutputStream out) {
01608         write(out, null);
01609     }
01610 
01625     public synchronized void write(OutputStream out, String dtd) {
01626         write(new OutputStreamWriter(out), dtd);
01627     }
01628 
01641     public synchronized void write(Writer out, String dtd) {
01642         // Auto-flush is disabled.
01643         PrintWriter output = new PrintWriter(new BufferedWriter(out), false);
01644 
01645         if (dtd == null) {
01646             output.println("<?xml version=\"1.0\" standalone=\"yes\"?>");
01647             output
01648                     .println("<!DOCTYPE plot PUBLIC \"-//UC Berkeley//DTD PlotML 1//EN\"");
01649             output
01650                     .println("    \"http://ptolemy.eecs.berkeley.edu/xml/dtd/PlotML_1.dtd\">");
01651         } else {
01652             output.println("<?xml version=\"1.0\" standalone=\"no\"?>");
01653             output.println("<!DOCTYPE plot SYSTEM \"" + dtd + "\">");
01654         }
01655 
01656         output.println("<plot>");
01657         output.println("<!-- Ptolemy plot, version " + PTPLOT_RELEASE
01658                 + " , PlotML format. -->");
01659         writeFormat(output);
01660         writeData(output);
01661         output.println("</plot>");
01662         output.flush();
01663 
01664         // NOTE: We used to close the stream, but if this is part
01665         // of an exportMoML operation, that is the wrong thing to do.
01666         // if (out != System.out) {
01667         //    output.close();
01668         // }
01669     }
01670 
01676     public synchronized void writeData(PrintWriter output) {
01677     }
01678 
01685     public synchronized void writeFormat(PrintWriter output) {
01686         // NOTE: If you modify this, you should change the _DTD variable
01687         // accordingly.
01688         if (_title != null) {
01689             output.println("<title>" + _title + "</title>");
01690         }
01691 
01692         if (_xlabel != null) {
01693             output.println("<xLabel>" + _xlabel + "</xLabel>");
01694         }
01695 
01696         if (_ylabel != null) {
01697             output.println("<yLabel>" + _ylabel + "</yLabel>");
01698         }
01699 
01700         if (_xRangeGiven) {
01701             output.println("<xRange min=\"" + _xlowgiven + "\" max=\""
01702                     + _xhighgiven + "\"/>");
01703         }
01704 
01705         if (_yRangeGiven) {
01706             output.println("<yRange min=\"" + _ylowgiven + "\" max=\""
01707                     + _yhighgiven + "\"/>");
01708         }
01709 
01710         if ((_xticks != null) && (_xticks.size() > 0)) {
01711             output.println("<xTicks>");
01712 
01713             int last = _xticks.size() - 1;
01714 
01715             for (int i = 0; i <= last; i++) {
01716                 output.println("  <tick label=\""
01717                         + (String) _xticklabels.elementAt(i) + "\" position=\""
01718                         + _xticks.elementAt(i) + "\"/>");
01719             }
01720 
01721             output.println("</xTicks>");
01722         }
01723 
01724         if ((_yticks != null) && (_yticks.size() > 0)) {
01725             output.println("<yTicks>");
01726 
01727             int last = _yticks.size() - 1;
01728 
01729             for (int i = 0; i <= last; i++) {
01730                 output.println("  <tick label=\""
01731                         + (String) _yticklabels.elementAt(i) + "\" position=\""
01732                         + _yticks.elementAt(i) + "\"/>");
01733             }
01734 
01735             output.println("</yTicks>");
01736         }
01737 
01738         if (_xlog) {
01739             output.println("<xLog/>");
01740         }
01741 
01742         if (_ylog) {
01743             output.println("<yLog/>");
01744         }
01745 
01746         if (!_grid) {
01747             output.println("<noGrid/>");
01748         }
01749 
01750         if (_wrap) {
01751             output.println("<wrap/>");
01752         }
01753 
01754         if (!_usecolor) {
01755             output.println("<noColor/>");
01756         }
01757     }
01758 
01767     public synchronized void writeOldSyntax(OutputStream out) {
01768         // Auto-flush is disabled.
01769         PrintWriter output = new PrintWriter(new BufferedOutputStream(out),
01770                 false);
01771         _writeOldSyntax(output);
01772         output.flush();
01773 
01774         // Avoid closing standard out.
01775         if (out != System.out) {
01776             output.close();
01777         }
01778     }
01779 
01787     public synchronized void zoom(double lowx, double lowy, double highx,
01788             double highy) {
01789         setXRange(lowx, highx);
01790         setYRange(lowy, highy);
01791         repaint();
01792     }
01793 
01796     public static final String PTPLOT_RELEASE = "5.7";
01797 
01800 
01814     protected synchronized void _drawPlot(Graphics graphics, boolean clearfirst) {
01815         Rectangle bounds = getBounds();
01816         _drawPlot(graphics, clearfirst, bounds);
01817     }
01818 
01834     protected synchronized void _drawPlot(Graphics graphics,
01835             boolean clearfirst, Rectangle drawRect) {
01836         // Ignore if there is no graphics object to draw on.
01837         if (graphics == null) {
01838             return;
01839         }
01840 
01841         graphics.setPaintMode();
01842 
01843         /* NOTE: The following seems to be unnecessary with Swing...
01844          if (clearfirst) {
01845          // NOTE: calling clearRect() here permits the background
01846          // color to show through, but it messes up printing.
01847          // Printing results in black-on-black title and axis labels.
01848          graphics.setColor(_background);
01849          graphics.drawRect(0, 0, drawRect.width, drawRect.height);
01850          graphics.setColor(Color.black);
01851          }
01852          */
01853 
01854         // If an error message has been set, display it and return.
01855         if (_errorMsg != null) {
01856             int fheight = _labelFontMetrics.getHeight() + 2;
01857             int msgy = fheight;
01858             graphics.setColor(Color.black);
01859 
01860             for (int i = 0; i < _errorMsg.length; i++) {
01861                 graphics.drawString(_errorMsg[i], 10, msgy);
01862                 msgy += fheight;
01863                 System.err.println(_errorMsg[i]);
01864             }
01865 
01866             return;
01867         }
01868 
01869         // Make sure we have an x and y range
01870         if (!_xRangeGiven) {
01871             if (_xBottom > _xTop) {
01872                 // have nothing to go on.
01873                 _setXRange(0, 0);
01874             } else {
01875                 _setXRange(_xBottom, _xTop);
01876             }
01877         }
01878 
01879         if (!_yRangeGiven) {
01880             if (_yBottom > _yTop) {
01881                 // have nothing to go on.
01882                 _setYRange(0, 0);
01883             } else {
01884                 _setYRange(_yBottom, _yTop);
01885             }
01886         }
01887 
01888         // If user specified a plot rectangle, compute
01889         // a working plot rectangle which lies inside the
01890         // drawRect at the user specified coordinates
01891         Rectangle workingPlotRectangle = null;
01892 
01893         if (_specifiedPlotRectangle != null) {
01894             workingPlotRectangle = new Rectangle(Math.max(0,
01895                     _specifiedPlotRectangle.x), Math.max(0,
01896                     _specifiedPlotRectangle.y), Math.min(drawRect.width,
01897                     _specifiedPlotRectangle.width), Math.min(drawRect.height,
01898                     _specifiedPlotRectangle.height));
01899         }
01900 
01901         // Vertical space for title, if appropriate.
01902         // NOTE: We assume a one-line title.
01903         int titley = 0;
01904         int titlefontheight = _titleFontMetrics.getHeight();
01905 
01906         if (_title == null) {
01907             // NOTE: If the _title is null, then set it to the empty
01908             // string to solve the problem where the fill button overlaps
01909             // the legend if there is no title.  The fix here would
01910             // be to modify the legend printing text so that it takes
01911             // into account the case where there is no title by offsetting
01912             // just enough for the button.
01913             _title = "";
01914         }
01915 
01916         if ((_title != null) || (_yExp != 0)) {
01917             titley = titlefontheight + _topPadding;
01918         }
01919 
01920         // Number of vertical tick marks depends on the height of the font
01921         // for labeling ticks and the height of the window.
01922         Font previousFont = graphics.getFont();
01923         graphics.setFont(_labelFont);
01924         graphics.setColor(_foreground); // foreground color not set here  --Rob.
01925 
01926         int labelheight = _labelFontMetrics.getHeight();
01927         int halflabelheight = labelheight / 2;
01928 
01929         // Draw scaling annotation for x axis.
01930         // NOTE: 5 pixel padding on bottom.
01931         int ySPos = drawRect.height - 5;
01932         int xSPos = drawRect.width - _rightPadding;
01933 
01934         if (_xlog) {
01935             _xExp = (int) Math.floor(_xtickMin);
01936         }
01937 
01938         if ((_xExp != 0) && (_xticks == null)) {
01939             String superscript = Integer.toString(_xExp);
01940             xSPos -= _superscriptFontMetrics.stringWidth(superscript);
01941             graphics.setFont(_superscriptFont);
01942 
01943             if (!_xlog) {
01944                 graphics
01945                         .drawString(superscript, xSPos, ySPos - halflabelheight);
01946                 xSPos -= _labelFontMetrics.stringWidth("x10");
01947                 graphics.setFont(_labelFont);
01948                 graphics.drawString("x10", xSPos, ySPos);
01949             }
01950 
01951             // NOTE: 5 pixel padding on bottom
01952             _bottomPadding = ((3 * labelheight) / 2) + 5;
01953         }
01954 
01955         // NOTE: 5 pixel padding on the bottom.
01956         if ((_xlabel != null) && (_bottomPadding < (labelheight + 5))) {
01957             _bottomPadding = labelheight + 5;
01958         }
01959 
01960         // Compute the space needed around the plot, starting with vertical.
01961         // NOTE: padding of 5 pixels below title.
01962         if (workingPlotRectangle != null) {
01963             _uly = workingPlotRectangle.y;
01964         } else {
01965             _uly = titley + 5;
01966         }
01967 
01968         // NOTE: 3 pixels above bottom labels.
01969         if (workingPlotRectangle != null) {
01970             _lry = workingPlotRectangle.y + workingPlotRectangle.height;
01971         } else {
01972             _lry = drawRect.height - labelheight - _bottomPadding - 3;
01973         }
01974 
01975         int height = _lry - _uly;
01976         _yscale = height / (_yMax - _yMin);
01977         _ytickscale = height / (_ytickMax - _ytickMin);
01978 
01980         // Number of y tick marks.
01981         // NOTE: subjective spacing factor.
01982         int ny = 2 + (height / (labelheight + 10));
01983 
01984         // Compute y increment.
01985         double yStep = _roundUp((_ytickMax - _ytickMin) / ny);
01986 
01987         // Compute y starting point so it is a multiple of yStep.
01988         double yStart = yStep * Math.ceil(_ytickMin / yStep);
01989 
01990         // NOTE: Following disables first tick.  Not a good idea?
01991         // if (yStart == _ytickMin) yStart += yStep;
01992         // Define the strings that will label the y axis.
01993         // Meanwhile, find the width of the widest label.
01994         // The labels are quantized so that they don't have excess resolution.
01995         int widesty = 0;
01996 
01997         // These do not get used unless ticks are automatic, but the
01998         // compiler is not smart enough to allow us to reference them
01999         // in two distinct conditional clauses unless they are
02000         // allocated outside the clauses.
02001         String[] ylabels = new String[ny];
02002         int[] ylabwidth = new int[ny];
02003 
02004         int ind = 0;
02005 
02006         if (_yticks == null) {
02007             Vector ygrid = null;
02008 
02009             if (_ylog) {
02010                 ygrid = _gridInit(yStart, yStep, true, null);
02011             }
02012 
02013             // automatic ticks
02014             // First, figure out how many digits after the decimal point
02015             // will be used.
02016             int numfracdigits = _numFracDigits(yStep);
02017 
02018             // NOTE: Test cases kept in case they are needed again.
02019             // System.out.println("0.1 with 3 digits: " + _formatNum(0.1, 3));
02020             // System.out.println("0.0995 with 3 digits: " +
02021             //                    _formatNum(0.0995, 3));
02022             // System.out.println("0.9995 with 3 digits: " +
02023             //                    _formatNum(0.9995, 3));
02024             // System.out.println("1.9995 with 0 digits: " +
02025             //                    _formatNum(1.9995, 0));
02026             // System.out.println("1 with 3 digits: " + _formatNum(1, 3));
02027             // System.out.println("10 with 0 digits: " + _formatNum(10, 0));
02028             // System.out.println("997 with 3 digits: " + _formatNum(997, 3));
02029             // System.out.println("0.005 needs: " + _numFracDigits(0.005));
02030             // System.out.println("1 needs: " + _numFracDigits(1));
02031             // System.out.println("999 needs: " + _numFracDigits(999));
02032             // System.out.println("999.0001 needs: "+_numFracDigits(999.0001));
02033             // System.out.println("0.005 integer digits: " +
02034             //                    _numIntDigits(0.005));
02035             // System.out.println("1 integer digits: " + _numIntDigits(1));
02036             // System.out.println("999 integer digits: " + _numIntDigits(999));
02037             // System.out.println("-999.0001 integer digits: " +
02038             //                    _numIntDigits(999.0001));
02039             double yTmpStart = yStart;
02040 
02041             if (_ylog) {
02042                 yTmpStart = _gridStep(ygrid, yStart, yStep, _ylog);
02043             }
02044 
02045             for (double ypos = yTmpStart; ypos <= _ytickMax; ypos = _gridStep(
02046                     ygrid, ypos, yStep, _ylog)) {
02047                 // Prevent out of bounds exceptions
02048                 if (ind >= ny) {
02049                     break;
02050                 }
02051 
02052                 String yticklabel;
02053 
02054                 if (_ylog) {
02055                     yticklabel = _formatLogNum(ypos, numfracdigits);
02056                 } else {
02057                     yticklabel = _formatNum(ypos, numfracdigits);
02058                 }
02059 
02060                 ylabels[ind] = yticklabel;
02061 
02062                 int lw = _labelFontMetrics.stringWidth(yticklabel);
02063                 ylabwidth[ind++] = lw;
02064 
02065                 if (lw > widesty) {
02066                     widesty = lw;
02067                 }
02068             }
02069         } else {
02070             // explicitly specified ticks
02071             Enumeration nl = _yticklabels.elements();
02072 
02073             while (nl.hasMoreElements()) {
02074                 String label = (String) nl.nextElement();
02075                 int lw = _labelFontMetrics.stringWidth(label);
02076 
02077                 if (lw > widesty) {
02078                     widesty = lw;
02079                 }
02080             }
02081         }
02082 
02083         // Next we do the horizontal spacing.
02084         if (workingPlotRectangle != null) {
02085             _ulx = workingPlotRectangle.x;
02086         } else {
02087             if (_ylabel != null) {
02088                 _ulx = widesty + _labelFontMetrics.stringWidth("W")
02089                         + _leftPadding;
02090             } else {
02091                 _ulx = widesty + _leftPadding;
02092             }
02093         }
02094 
02095         int legendwidth = _drawLegend(graphics, drawRect.width - _rightPadding,
02096                 _uly);
02097 
02098         if (workingPlotRectangle != null) {
02099             _lrx = workingPlotRectangle.x + workingPlotRectangle.width;
02100         } else {
02101             _lrx = drawRect.width - legendwidth - _rightPadding;
02102         }
02103 
02104         int width = _lrx - _ulx;
02105         _xscale = width / (_xMax - _xMin);
02106 
02107         _xtickscale = width / (_xtickMax - _xtickMin);
02108 
02109         // Background for the plotting rectangle.
02110         // Always use a white background because the dataset colors
02111         // were designed for a white background.
02112         graphics.setColor(Color.white);
02113         graphics.fillRect(_ulx, _uly, width, height);
02114 
02115         graphics.setColor(_foreground);
02116         graphics.drawRect(_ulx, _uly, width, height);
02117 
02118         // NOTE: subjective tick length.
02119         int tickLength = 5;
02120         int xCoord1 = _ulx + tickLength;
02121         int xCoord2 = _lrx - tickLength;
02122 
02123         if (_yticks == null) {
02124             // auto-ticks
02125             Vector ygrid = null;
02126             double yTmpStart = yStart;
02127 
02128             if (_ylog) {
02129                 ygrid = _gridInit(yStart, yStep, true, null);
02130                 yTmpStart = _gridStep(ygrid, yStart, yStep, _ylog);
02131                 ny = ind;
02132             }
02133 
02134             ind = 0;
02135 
02136             // Set to false if we don't need the exponent
02137             boolean needExponent = _ylog;
02138 
02139             for (double ypos = yTmpStart; ypos <= _ytickMax; ypos = _gridStep(
02140                     ygrid, ypos, yStep, _ylog)) {
02141                 // Prevent out of bounds exceptions
02142                 if (ind >= ny) {
02143                     break;
02144                 }
02145 
02146                 int yCoord1 = _lry - (int) ((ypos - _ytickMin) * _ytickscale);
02147 
02148                 // The lowest label is shifted up slightly to avoid
02149                 // colliding with x labels.
02150                 int offset = 0;
02151 
02152                 if ((ind > 0) && !_ylog) {
02153                     offset = halflabelheight;
02154                 }
02155 
02156                 graphics.drawLine(_ulx, yCoord1, xCoord1, yCoord1);
02157                 graphics.drawLine(_lrx, yCoord1, xCoord2, yCoord1);
02158 
02159                 if (_grid && (yCoord1 != _uly) && (yCoord1 != _lry)) {
02160                     graphics.setColor(Color.lightGray);
02161                     graphics.drawLine(xCoord1, yCoord1, xCoord2, yCoord1);
02162                     graphics.setColor(_foreground);
02163                 }
02164 
02165                 // Check to see if any of the labels printed contain
02166                 // the exponent.  If we don't see an exponent, then print it.
02167                 if (_ylog && (ylabels[ind].indexOf('e') != -1)) {
02168                     needExponent = false;
02169                 }
02170 
02171                 // NOTE: 4 pixel spacing between axis and labels.
02172                 graphics.drawString(ylabels[ind], _ulx - ylabwidth[ind++] - 4,
02173                         yCoord1 + offset);
02174             }
02175 
02176             if (_ylog) {
02177                 // Draw in grid lines that don't have labels.
02178                 Vector unlabeledgrid = _gridInit(yStart, yStep, false, ygrid);
02179 
02180                 if (unlabeledgrid.size() > 0) {
02181                     // If the step is greater than 1, clamp it to 1 so that
02182                     // we draw the unlabeled grid lines for each
02183                     //integer interval.
02184                     double tmpStep = (yStep > 1.0) ? 1.0 : yStep;
02185 
02186                     for (double ypos = _gridStep(unlabeledgrid, yStart,
02187                             tmpStep, _ylog); ypos <= _ytickMax; ypos = _gridStep(
02188                             unlabeledgrid, ypos, tmpStep, _ylog)) {
02189                         int yCoord1 = _lry
02190                                 - (int) ((ypos - _ytickMin) * _ytickscale);
02191 
02192                         if (_grid && (yCoord1 != _uly) && (yCoord1 != _lry)) {
02193                             graphics.setColor(Color.lightGray);
02194                             graphics.drawLine(_ulx + 1, yCoord1, _lrx - 1,
02195                                     yCoord1);
02196                             graphics.setColor(_foreground);
02197                         }
02198                     }
02199                 }
02200 
02201                 if (needExponent) {
02202                     // We zoomed in, so we need the exponent
02203                     _yExp = (int) Math.floor(yTmpStart);
02204                 } else {
02205                     _yExp = 0;
02206                 }
02207             }
02208 
02209             // Draw scaling annotation for y axis.
02210             if (_yExp != 0) {
02211                 graphics.drawString("x10", 2, titley);
02212                 graphics.setFont(_superscriptFont);
02213                 graphics.drawString(Integer.toString(_yExp), _labelFontMetrics
02214                         .stringWidth("x10") + 2, titley - halflabelheight);
02215                 graphics.setFont(_labelFont);
02216             }
02217         } else {
02218             // ticks have been explicitly specified
02219             Enumeration nt = _yticks.elements();
02220             Enumeration nl = _yticklabels.elements();
02221 
02222             while (nl.hasMoreElements()) {
02223                 String label = (String) nl.nextElement();
02224                 double ypos = ((Double) (nt.nextElement())).doubleValue();
02225 
02226                 if ((ypos > _yMax) || (ypos < _yMin)) {
02227                     continue;
02228                 }
02229 
02230                 int yCoord1 = _lry - (int) ((ypos - _yMin) * _yscale);
02231                 int offset = 0;
02232 
02233                 if (ypos < (_lry - labelheight)) {
02234                     offset = halflabelheight;
02235                 }
02236 
02237                 graphics.drawLine(_ulx, yCoord1, xCoord1, yCoord1);
02238                 graphics.drawLine(_lrx, yCoord1, xCoord2, yCoord1);
02239 
02240                 if (_grid && (yCoord1 != _uly) && (yCoord1 != _lry)) {
02241                     graphics.setColor(Color.lightGray);
02242                     graphics.drawLine(xCoord1, yCoord1, xCoord2, yCoord1);
02243                     graphics.setColor(_foreground);
02244                 }
02245 
02246                 // NOTE: 3 pixel spacing between axis and labels.
02247                 graphics.drawString(label, _ulx
02248                         - _labelFontMetrics.stringWidth(label) - 3, yCoord1
02249                         + offset);
02250             }
02251         }
02252 
02254         int yCoord1 = _uly + tickLength;
02255         int yCoord2 = _lry - tickLength;
02256         int charwidth = _labelFontMetrics.stringWidth("8");
02257 
02258         if (_xticks == null) {
02259             // auto-ticks
02260             // Number of x tick marks.
02261             // Need to start with a guess and converge on a solution here.
02262             int nx = 10;
02263             double xStep = 0.0;
02264             int numfracdigits = 0;
02265 
02266             if (_xlog) {
02267                 // X axes log labels will be at most 6 chars: -1E-02
02268                 nx = 2 + (width / ((charwidth * 6) + 10));
02269             } else {
02270                 // Limit to 10 iterations
02271                 int count = 0;
02272 
02273                 while (count++ <= 10) {
02274                     xStep = _roundUp((_xtickMax - _xtickMin) / nx);
02275 
02276                     // Compute the width of a label for this xStep
02277                     numfracdigits = _numFracDigits(xStep);
02278 
02279                     // Number of integer digits is the maximum of two endpoints
02280                     int intdigits = _numIntDigits(_xtickMax);
02281                     int inttemp = _numIntDigits(_xtickMin);
02282 
02283                     if (intdigits < inttemp) {
02284                         intdigits = inttemp;
02285                     }
02286 
02287                     // Allow two extra digits (decimal point and sign).
02288                     int maxlabelwidth = charwidth
02289                             * (numfracdigits + 2 + intdigits);
02290 
02291                     // Compute new estimate of number of ticks.
02292                     int savenx = nx;
02293 
02294                     // NOTE: 10 additional pixels between labels.
02295                     // NOTE: Try to ensure at least two tick marks.
02296                     nx = 2 + (width / (maxlabelwidth + 10));
02297 
02298                     if (((nx - savenx) <= 1) || ((savenx - nx) <= 1)) {
02299                         break;
02300                     }
02301                 }
02302             }
02303 
02304             xStep = _roundUp((_xtickMax - _xtickMin) / nx);
02305             numfracdigits = _numFracDigits(xStep);
02306 
02307             // Compute x starting point so it is a multiple of xStep.
02308             double xStart = xStep * Math.ceil(_xtickMin / xStep);
02309 
02310             // NOTE: Following disables first tick.  Not a good idea?
02311             // if (xStart == _xMin) xStart += xStep;
02312             Vector xgrid = null;
02313             double xTmpStart = xStart;
02314 
02315             if (_xlog) {
02316                 xgrid = _gridInit(xStart, xStep, true, null);
02317 
02318                 //xgrid = _gridInit(xStart, xStep);
02319                 xTmpStart = _gridRoundUp(xgrid, xStart);
02320             }
02321 
02322             // Set to false if we don't need the exponent
02323             boolean needExponent = _xlog;
02324 
02325             // Label the x axis.  The labels are quantized so that
02326             // they don't have excess resolution.
02327             for (double xpos = xTmpStart; xpos <= _xtickMax; xpos = _gridStep(
02328                     xgrid, xpos, xStep, _xlog)) {
02329                 String xticklabel;
02330 
02331                 if (_xlog) {
02332                     xticklabel = _formatLogNum(xpos, numfracdigits);
02333 
02334                     if (xticklabel.indexOf('e') != -1) {
02335                         needExponent = false;
02336                     }
02337                 } else {
02338                     xticklabel = _formatNum(xpos, numfracdigits);
02339                 }
02340 
02341                 xCoord1 = _ulx + (int) ((xpos - _xtickMin) * _xtickscale);
02342                 graphics.drawLine(xCoord1, _uly, xCoord1, yCoord1);
02343                 graphics.drawLine(xCoord1, _lry, xCoord1, yCoord2);
02344 
02345                 if (_grid && (xCoord1 != _ulx) && (xCoord1 != _lrx)) {
02346                     graphics.setColor(Color.lightGray);
02347                     graphics.drawLine(xCoord1, yCoord1, xCoord1, yCoord2);
02348                     graphics.setColor(_foreground);
02349                 }
02350 
02351                 int labxpos = xCoord1
02352                         - (_labelFontMetrics.stringWidth(xticklabel) / 2);
02353 
02354                 // NOTE: 3 pixel spacing between axis and labels.
02355                 graphics
02356                         .drawString(xticklabel, labxpos, _lry + 3 + labelheight);
02357             }
02358 
02359             if (_xlog) {
02360                 // Draw in grid lines that don't have labels.
02361                 // If the step is greater than 1, clamp it to 1 so that
02362                 // we draw the unlabeled grid lines for each
02363                 // integer interval.
02364                 double tmpStep = (xStep > 1.0) ? 1.0 : xStep;
02365 
02366                 // Recalculate the start using the new step.
02367                 xTmpStart = tmpStep * Math.ceil(_xtickMin / tmpStep);
02368 
02369                 Vector unlabeledgrid = _gridInit(xTmpStart, tmpStep, false,
02370                         xgrid);
02371 
02372                 if (unlabeledgrid.size() > 0) {
02373                     for (double xpos = _gridStep(unlabeledgrid, xTmpStart,
02374                             tmpStep, _xlog); xpos <= _xtickMax; xpos = _gridStep(
02375                             unlabeledgrid, xpos, tmpStep, _xlog)) {
02376                         xCoord1 = _ulx
02377                                 + (int) ((xpos - _xtickMin) * _xtickscale);
02378 
02379                         if (_grid && (xCoord1 != _ulx) && (xCoord1 != _lrx)) {
02380                             graphics.setColor(Color.lightGray);
02381                             graphics.drawLine(xCoord1, _uly + 1, xCoord1,
02382                                     _lry - 1);
02383                             graphics.setColor(_foreground);
02384                         }
02385                     }
02386                 }
02387 
02388                 if (needExponent) {
02389                     _xExp = (int) Math.floor(xTmpStart);
02390                     graphics.setFont(_superscriptFont);
02391                     graphics.drawString(Integer.toString(_xExp), xSPos, ySPos
02392                             - halflabelheight);
02393                     xSPos -= _labelFontMetrics.stringWidth("x10");
02394                     graphics.setFont(_labelFont);
02395                     graphics.drawString("x10", xSPos, ySPos);
02396                 } else {
02397                     _xExp = 0;
02398                 }
02399             }
02400         } else {
02401             // ticks have been explicitly specified
02402             Enumeration nt = _xticks.elements();
02403             Enumeration nl = _xticklabels.elements();
02404 
02405             // Code contributed by Jun Wu (jwu@inin.com.au)
02406             double preLength = 0.0;
02407 
02408             while (nl.hasMoreElements()) {
02409                 String label = (String) nl.nextElement();
02410                 double xpos = ((Double) (nt.nextElement())).doubleValue();
02411 
02412                 // If xpos is out of range, ignore.
02413                 if ((xpos > _xMax) || (xpos < _xMin)) {
02414                     continue;
02415                 }
02416 
02417                 // Find the center position of the label.
02418                 xCoord1 = _ulx + (int) ((xpos - _xMin) * _xscale);
02419 
02420                 // Find  the start position of x label.
02421                 int labxpos = xCoord1
02422                         - (_labelFontMetrics.stringWidth(label) / 2);
02423 
02424                 // If the labels are not overlapped, proceed.
02425                 if (labxpos > preLength) {
02426                     // calculate the length of the label
02427                     preLength = xCoord1
02428                             + (_labelFontMetrics.stringWidth(label) / 2) + 10;
02429 
02430                     // Draw the label.
02431                     // NOTE: 3 pixel spacing between axis and labels.
02432                     graphics.drawString(label, labxpos, _lry + 3 + labelheight);
02433 
02434                     // Draw the label mark on the axis
02435                     graphics.drawLine(xCoord1, _uly, xCoord1, yCoord1);
02436                     graphics.drawLine(xCoord1, _lry, xCoord1, yCoord2);
02437 
02438                     // Draw the grid line
02439                     if (_grid && (xCoord1 != _ulx) && (xCoord1 != _lrx)) {
02440                         graphics.setColor(Color.lightGray);
02441                         graphics.drawLine(xCoord1, yCoord1, xCoord1, yCoord2);
02442                         graphics.setColor(_foreground);
02443                     }
02444                 }
02445             }
02446         }
02447 
02449         // Center the title and X label over the plotting region, not
02450         // the window.
02451         graphics.setColor(_foreground);
02452 
02453         if (_title != null) {
02454             graphics.setFont(_titleFont);
02455 
02456             int titlex = _ulx
02457                     + ((width - _titleFontMetrics.stringWidth(_title)) / 2);
02458             graphics.drawString(_title, titlex, titley);
02459         }
02460 
02461         graphics.setFont(_labelFont);
02462 
02463         if (_xlabel != null) {
02464             int labelx = _ulx
02465                     + ((width - _labelFontMetrics.stringWidth(_xlabel)) / 2);
02466             graphics.drawString(_xlabel, labelx, ySPos);
02467         }
02468 
02469         int charcenter = 2 + (_labelFontMetrics.stringWidth("W") / 2);
02470 
02471         if (_ylabel != null) {
02472             int yl = _ylabel.length();
02473 
02474             if (graphics instanceof Graphics2D) {
02475                 int starty = (_uly + ((_lry - _uly) / 2) + (_labelFontMetrics
02476                         .stringWidth(_ylabel) / 2))
02477                         - charwidth;
02478                 Graphics2D g2d = (Graphics2D) graphics;
02479 
02480                 // NOTE: Fudge factor so label doesn't touch axis labels.
02481                 int startx = (charcenter + halflabelheight) - 2;
02482                 g2d.rotate(Math.toRadians(-90), startx, starty);
02483                 g2d.drawString(_ylabel, startx, starty);
02484                 g2d.rotate(Math.toRadians(90), startx, starty);
02485             } else {
02486                 // Not graphics 2D, no support for rotation.
02487                 // Vertical label is fairly complex to draw.
02488                 int starty = (_uly + ((_lry - _uly) / 2))
02489                         - (yl * halflabelheight) + labelheight;
02490 
02491                 for (int i = 0; i < yl; i++) {
02492                     String nchar = _ylabel.substring(i, i + 1);
02493                     int cwidth = _labelFontMetrics.stringWidth(nchar);
02494                     graphics.drawString(nchar, charcenter - (cwidth / 2),
02495                             starty);
02496                     starty += labelheight;
02497                 }
02498             }
02499         }
02500 
02501         graphics.setFont(previousFont);
02502     }
02503 
02524     protected void _drawPoint(Graphics graphics, int dataset, long xpos,
02525             long ypos, boolean clip) {
02526         // Ignore if there is no graphics object to draw on.
02527         if (graphics == null) {
02528             return;
02529         }
02530 
02531         boolean pointinside = (ypos <= _lry) && (ypos >= _uly)
02532                 && (xpos <= _lrx) && (xpos >= _ulx);
02533 
02534         if (!pointinside && clip) {
02535             return;
02536         }
02537 
02538         graphics.fillRect((int) xpos - 6, (int) ypos - 6, 6, 6);
02539     }
02540 
02543     protected void _help() {
02544         String message = "Ptolemy plot package\n"
02545                 + "By: Edward A. Lee\n"
02546                 + "and Christopher Brooks\n"
02547                 + "Version "
02548                 + PTPLOT_RELEASE
02549                 + ", Build: $Id: PlotBox.java,v 1.283.4.3 2008/04/01 01:19:02 cxh Exp $\n\n"
02550                 + "Key bindings:\n"
02551                 + "   Cntrl-c:  copy plot to clipboard (EPS format), if permitted\n"
02552                 + "   D: dump plot data to standard out\n"
02553                 + "   E: export plot to standard out (EPS format)\n"
02554                 + "   F: fill plot\n"
02555                 + "   H or ?: print help message (this message)\n"
02556                 + "   Cntrl-D or Q: quit\n" + "For more information, see\n"
02557                 + "http://ptolemy.eecs.berkeley.edu/java/ptplot\n";
02558         JOptionPane.showMessageDialog(this, message,
02559                 "Ptolemy Plot Help Window", JOptionPane.INFORMATION_MESSAGE);
02560     }
02561 
02568     protected boolean _parseLine(String line) {
02569         // If you modify this method, you should also modify write()
02570         // We convert the line to lower case so that the command
02571         // names are case insensitive.
02572         String lcLine = line.toLowerCase();
02573 
02574         if (lcLine.startsWith("#")) {
02575             // comment character
02576             return true;
02577         } else if (lcLine.startsWith("titletext:")) {
02578             setTitle((line.substring(10)).trim());
02579             return true;
02580         } else if (lcLine.startsWith("title:")) {
02581             // Tolerate alternative tag.
02582             setTitle((line.substring(6)).trim());
02583             return true;
02584         } else if (lcLine.startsWith("xlabel:")) {
02585             setXLabel((line.substring(7)).trim());
02586             return true;
02587         } else if (lcLine.startsWith("ylabel:")) {
02588             setYLabel((line.substring(7)).trim());
02589             return true;
02590         } else if (lcLine.startsWith("xrange:")) {
02591             int comma = line.indexOf(",", 7);
02592 
02593             if (comma > 0) {
02594                 String min = (line.substring(7, comma)).trim();
02595                 String max = (line.substring(comma + 1)).trim();
02596 
02597                 try {
02598                     Double dmin = Double.valueOf(min);
02599                     Double dmax = Double.valueOf(max);
02600                     setXRange(dmin.doubleValue(), dmax.doubleValue());
02601                 } catch (NumberFormatException e) {
02602                     // ignore if format is bogus.
02603                 }
02604             }
02605 
02606             return true;
02607         } else if (lcLine.startsWith("yrange:")) {
02608             int comma = line.indexOf(",", 7);
02609 
02610             if (comma > 0) {
02611                 String min = (line.substring(7, comma)).trim();
02612                 String max = (line.substring(comma + 1)).trim();
02613 
02614                 try {
02615                     Double dmin = Double.valueOf(min);
02616                     Double dmax = Double.valueOf(max);
02617                     setYRange(dmin.doubleValue(), dmax.doubleValue());
02618                 } catch (NumberFormatException e) {
02619                     // ignore if format is bogus.
02620                 }
02621             }
02622 
02623             return true;
02624         } else if (lcLine.startsWith("xticks:")) {
02625             // example:
02626             // XTicks "label" 0, "label" 1, "label" 3
02627             _parsePairs(line.substring(7), true);
02628             return true;
02629         } else if (lcLine.startsWith("yticks:")) {
02630             // example:
02631             // YTicks "label" 0, "label" 1, "label" 3
02632             _parsePairs(line.substring(7), false);
02633             return true;
02634         } else if (lcLine.startsWith("xlog:")) {
02635             if (lcLine.indexOf("off", 5) >= 0) {
02636                 _xlog = false;
02637             } else {
02638                 _xlog = true;
02639             }
02640 
02641             return true;
02642         } else if (lcLine.startsWith("ylog:")) {
02643             if (lcLine.indexOf("off", 5) >= 0) {
02644                 _ylog = false;
02645             } else {
02646                 _ylog = true;
02647             }
02648 
02649             return true;
02650         } else if (lcLine.startsWith("grid:")) {
02651             if (lcLine.indexOf("off", 5) >= 0) {
02652                 _grid = false;
02653             } else {
02654                 _grid = true;
02655             }
02656 
02657             return true;
02658         } else if (lcLine.startsWith("wrap:")) {
02659             if (lcLine.indexOf("off", 5) >= 0) {
02660                 _wrap = false;
02661             } else {
02662                 _wrap = true;
02663             }
02664 
02665             return true;
02666         } else if (lcLine.startsWith("color:")) {
02667             if (lcLine.indexOf("off", 6) >= 0) {
02668                 _usecolor = false;
02669             } else {
02670                 _usecolor = true;
02671             }
02672 
02673             return true;
02674         }
02675 
02676         return false;
02677     }
02678 
02683     protected void _setButtonsVisibility(boolean vis) {
02684         // Changing legend means we need to repaint the offscreen buffer.
02685         _plotImage = null;
02686 
02687         _printButton.setVisible(vis);
02688         _fillButton.setVisible(vis);
02689         _formatButton.setVisible(vis);
02690         _resetButton.setVisible(vis);
02691     }
02692 
02700     protected void _setPadding(double padding) {
02701         // Changing legend means we need to repaint the offscreen buffer.
02702         _plotImage = null;
02703 
02704         _padding = padding;
02705     }
02706 
02716     protected void _writeOldSyntax(PrintWriter output) {
02717         output.println("# Ptolemy plot, version 2.0");
02718 
02719         if (_title != null) {
02720             output.println("TitleText: " + _title);
02721         }
02722 
02723         if (_xlabel != null) {
02724             output.println("XLabel: " + _xlabel);
02725         }
02726 
02727         if (_ylabel != null) {
02728             output.println("YLabel: " + _ylabel);
02729         }
02730 
02731         if (_xRangeGiven) {
02732             output.println("XRange: " + _xlowgiven + ", " + _xhighgiven);
02733         }
02734 
02735         if (_yRangeGiven) {
02736             output.println("YRange: " + _ylowgiven + ", " + _yhighgiven);
02737         }
02738 
02739         if ((_xticks != null) && (_xticks.size() > 0)) {
02740             output.print("XTicks: ");
02741 
02742             int last = _xticks.size() - 1;
02743 
02744             for (int i = 0; i < last; i++) {
02745                 output.print("\"" + (String) _xticklabels.elementAt(i) + "\" "
02746                         + _xticks.elementAt(i) + ", ");
02747             }
02748 
02749             output.println("\"" + (String) _xticklabels.elementAt(last) + "\" "
02750                     + _xticks.elementAt(last));
02751         }
02752 
02753         if ((_yticks != null) && (_yticks.size() > 0)) {
02754             output.print("YTicks: ");
02755 
02756             int last = _yticks.size() - 1;
02757 
02758             for (int i = 0; i < last; i++) {
02759                 output.print("\"" + (String) _yticklabels.elementAt(i) + "\" "
02760                         + _yticks.elementAt(i) + ", ");
02761             }
02762 
02763             output.println("\"" + (String) _yticklabels.elementAt(last) + "\" "
02764                     + _yticks.elementAt(last));
02765         }
02766 
02767         if (_xlog) {
02768             output.println("XLog: on");
02769         }
02770 
02771         if (_ylog) {
02772             output.println("YLog: on");
02773         }
02774 
02775         if (!_grid) {
02776             output.println("Grid: off");
02777         }
02778 
02779         if (_wrap) {
02780             output.println("Wrap: on");
02781         }
02782 
02783         if (!_usecolor) {
02784             output.println("Color: off");
02785         }
02786     }
02787 
02790     // The range of the data to be plotted.
02791     protected transient double _yMax = 0;
02792 
02795     // The range of the data to be plotted.
02796     protected transient double _yMin = 0;
02797 
02800     // The range of the data to be plotted.
02801     protected transient double _xMax = 0;
02802 
02805     // The range of the data to be plotted.
02806     protected transient double _xMin = 0;
02807 
02810     protected double _padding = 0.05;
02811 
02812     // Whether the ranges have been given.
02813     protected transient boolean _xRangeGiven = false;
02814 
02815     protected transient boolean _yRangeGiven = false;
02816 
02817     protected transient boolean _rangesGivenByZooming = false;
02818 
02825     protected double _xlowgiven;
02826 
02833     protected double _xhighgiven;
02834 
02841     protected double _ylowgiven;
02842 
02849     protected double _yhighgiven;
02850 
02852     protected double _xBottom = Double.MAX_VALUE;
02853 
02855     protected double _xTop = -Double.MAX_VALUE;
02856 
02858     protected double _yBottom = Double.MAX_VALUE;
02859 
02861     protected double _yTop = -Double.MAX_VALUE;
02862 
02864     protected boolean _xlog = false;
02865 
02867     protected boolean _ylog = false;
02868 
02869     // For use in calculating log base 10. A log times this is a log base 10.
02870     protected static final double _LOG10SCALE = 1 / Math.log(10);
02871 
02873     protected boolean _grid = true;
02874 
02876     protected boolean _wrap = false;
02877 
02879     protected double _wrapHigh;
02880 
02882     protected double _wrapLow;
02883 
02885     protected Color _background = Color.white;
02886 
02888     protected Color _foreground = Color.black;
02889 
02893     protected int _topPadding = 10;
02894 
02898     protected int _bottomPadding = 5;
02899 
02903     protected int _rightPadding = 10;
02904 
02908     protected int _leftPadding = 10;
02909 
02910     // The naming convention is: "_ulx" = "upper left x", where "x" is
02911     // the horizontal dimension.
02912 
02914     protected int _ulx = 1;
02915 
02917     protected int _uly = 1;
02918 
02921     protected int _lrx = 100;
02922 
02925     protected int _lry = 100;
02926 
02930     protected Rectangle _specifiedPlotRectangle = null;
02931 
02935     protected double _yscale = 1.0;
02936 
02940     protected double _xscale = 1.0;
02941 
02943     protected boolean _usecolor = true;
02944 
02945     // Default _colors, by data set.
02946     // There are 11 colors so that combined with the
02947     // 10 marks of the Plot class, we can distinguish 110
02948     // distinct data sets.
02949     static protected Color[] _colors = { new Color(0xff0000), // red
02950             new Color(0x0000ff), // blue
02951             new Color(0x00aaaa), // cyan-ish
02952             new Color(0x000000), // black
02953             new Color(0xffa500), // orange
02954             new Color(0x53868b), // cadetblue4
02955             new Color(0xff7f50), // coral
02956             new Color(0x45ab1f), // dark green-ish
02957             new Color(0x90422d), // sienna-ish
02958             new Color(0xa0a0a0), // grey-ish
02959             new Color(0x14ff14), // green-ish
02960     };
02961 
02963     protected int _width = 500;
02964 
02966     protected int _height = 300;
02967 
02969     protected int _preferredWidth = 500;
02970 
02972     protected int _preferredHeight = 300;
02973 
02976     //protected boolean _sizeHasBeenSet = false;
02983     public URL _documentBase = null;
02984 
02987 
02988     /*
02989      * Draw the legend in the upper right corner and return the width
02990      * (in pixels)  used up.  The arguments give the upper right corner
02991      * of the region where the legend should be placed.
02992      */
02993     private int _drawLegend(Graphics graphics, int urx, int ury) {
02994         // Ignore if there is no graphics object to draw on.
02995         if (graphics == null) {
02996             return 0;
02997         }
02998 
02999         // FIXME: consolidate all these for efficiency
03000         Font previousFont = graphics.getFont();
03001         graphics.setFont(_labelFont);
03002 
03003         int spacing = _labelFontMetrics.getHeight();
03004 
03005         Enumeration v = _legendStrings.elements();
03006         Enumeration i = _legendDatasets.elements();
03007         int ypos = ury + spacing;
03008         int maxwidth = 0;
03009 
03010         while (v.hasMoreElements()) {
03011             String legend = (String) v.nextElement();
03012 
03013             // NOTE: relies on _legendDatasets having the same num. of entries.
03014             int dataset = ((Integer) i.nextElement()).intValue();
03015 
03016             if (dataset >= 0) {
03017                 if (_usecolor) {
03018                     // Points are only distinguished up to the number of colors
03019                     int color = dataset % _colors.length;
03020                     graphics.setColor(_colors[color]);
03021                 }
03022 
03023                 _drawPoint(graphics, dataset, urx - 3, ypos - 3, false);
03024 
03025                 graphics.setColor(_foreground);
03026 
03027                 int width = _labelFontMetrics.stringWidth(legend);
03028 
03029                 if (width > maxwidth) {
03030                     maxwidth = width;
03031                 }
03032 
03033                 graphics.drawString(legend, urx - 15 - width, ypos);
03034                 ypos += spacing;
03035             }
03036         }
03037 
03038         graphics.setFont(previousFont);
03039         return 22 + maxwidth; // NOTE: subjective spacing parameter.
03040     }
03041 
03042     // Execute all actions pending on the deferred action list.
03043     // The list is cleared and the _actionsDeferred variable is set
03044     // to false, even if one of the deferred actions fails.
03045     // This method should only be invoked in the event dispatch thread.
03046     // It is synchronized, so the integrity of the deferred actions list
03047     // is ensured, since modifications to that list occur only in other
03048     // synchronized methods.
03049     private synchronized void _executeDeferredActions() {
03050         try {
03051             Iterator actions = _deferredActions.iterator();
03052 
03053             while (actions.hasNext()) {
03054                 Runnable action = (Runnable) actions.next();
03055                 action.run();
03056             }
03057         } finally {
03058             _actionsDeferred = false;
03059             _deferredActions.clear();
03060         }
03061     }
03062 
03063     /*
03064      * Return the number as a String for use as a label on a
03065      * logarithmic axis.
03066      * Since this is a log plot, number passed in will not have too many
03067      * digits to cause problems.
03068      * If the number is an integer, then we print 1e<num>.
03069      * If the number is not an integer, then print only the fractional
03070      * components.
03071      */
03072     private String _formatLogNum(double num, int numfracdigits) {
03073         String results;
03074         int exponent = (int) num;
03075 
03076         // Determine the exponent, prepending 0 or -0 if necessary.
03077         if ((exponent >= 0) && (exponent < 10)) {
03078             results = "0" + exponent;
03079         } else {
03080             if ((exponent < 0) && (exponent > -10)) {
03081                 results = "-0" + (-exponent);
03082             } else {
03083                 results = Integer.toString(exponent);
03084             }
03085         }
03086 
03087         // Handle the mantissa.
03088         if (num >= 0.0) {
03089             if ((num - (int) (num)) < 0.001) {
03090                 results = "1e" + results;
03091             } else {
03092                 results = _formatNum(Math.pow(10.0, (num - (int) num)),
03093                         numfracdigits);
03094             }
03095         } else {
03096             if ((-num - (int) (-num)) < 0.001) {
03097                 results = "1e" + results;
03098             } else {
03099                 results = _formatNum(Math.pow(10.0, (num - (int) num)) * 10,
03100                         numfracdigits);
03101             }
03102         }
03103 
03104         return results;
03105     }
03106 
03107     /*
03108      * Return a string for displaying the specified number
03109      * using the specified number of digits after the decimal point.
03110      * NOTE: java.text.NumberFormat in Netscape 4.61 has a bug
03111      * where it fails to round numbers instead it truncates them.
03112      * As a result, we don't use java.text.NumberFormat, instead
03113      * We use the method from Ptplot1.3
03114      */
03115     private String _formatNum(double num, int numfracdigits) {
03116         // When java.text.NumberFormat works under Netscape,
03117         // uncomment the next block of code and remove
03118         // the code after it.
03119         // Ptplot developers at UCB can access a test case at:
03120         // http://ptolemy.eecs.berkeley.edu/~ptII/ptIItree/ptolemy/plot/adm/trunc/trunc-jdk11.html
03121         // The plot will show two 0.7 values on the x axis if the bug
03122         // continues to exist.
03123         //if (_numberFormat == null) {
03124         //   // Cache the number format so that we don't have to get
03125         //    // info about local language etc. from the OS each time.
03126         //    _numberFormat = NumberFormat.getInstance();
03127         //}
03128         //_numberFormat.setMinimumFractionDigits(numfracdigits);
03129         //_numberFormat.setMaximumFractionDigits(numfracdigits);
03130         //return _numberFormat.format(num);
03131         // The section below is from Ptplot1.3
03132         // First, round the number.
03133         double fudge = 0.5;
03134 
03135         if (num < 0.0) {
03136             fudge = -0.5;
03137         }
03138 
03139         String numString = Double.toString(num
03140                 + (fudge * Math.pow(10.0, -numfracdigits)));
03141 
03142         // Next, find the decimal point.
03143         int dpt = numString.lastIndexOf(".");
03144         StringBuffer result = new StringBuffer();
03145 
03146         if (dpt < 0) {
03147             // The number we are given is an integer.
03148             if (numfracdigits <= 0) {
03149                 // The desired result is an integer.
03150                 result.append(numString);
03151                 return result.toString();
03152             }
03153 
03154             // Append a decimal point and some zeros.
03155             result.append(".");
03156 
03157             for (int i = 0; i < numfracdigits; i++) {
03158                 result.append("0");
03159             }
03160 
03161             return result.toString();
03162         } else {
03163             // There are two cases.  First, there may be enough digits.
03164             int shortby = numfracdigits - (numString.length() - dpt - 1);
03165 
03166             if (shortby <= 0) {
03167                 int numtocopy = dpt + numfracdigits + 1;
03168 
03169                 if (numfracdigits == 0) {
03170                     // Avoid copying over a trailing decimal point.
03171                     numtocopy -= 1;
03172                 }
03173 
03174                 result.append(numString.substring(0, numtocopy));
03175                 return result.toString();
03176             } else {
03177                 result.append(numString);
03178 
03179                 for (int i = 0; i < shortby; i++) {
03180                     result.append("0");
03181                 }
03182 
03183                 return result.toString();
03184             }
03185         }
03186     }
03187 
03188     /*
03189      * Determine what values to use for log axes.
03190      * Based on initGrid() from xgraph.c by David Harrison.
03191      */
03192     private Vector _gridInit(double low, double step, boolean labeled,
03193             Vector oldgrid) {
03194         // How log axes work:
03195         // _gridInit() creates a vector with the values to use for the
03196         // log axes.  For example, the vector might contain
03197         // {0.0 0.301 0.698}, which could correspond to
03198         // axis labels {1 1.2 1.5 10 12 15 100 120 150}
03199         //
03200         // _gridStep() gets the proper value.  _gridInit is cycled through
03201         // for each integer log value.
03202         //
03203         // Bugs in log axes:
03204         // * Sometimes not enough grid lines are displayed because the
03205         // region is small.  This bug is present in the oriignal xgraph
03206         // binary, which is the basis of this code.  The problem is that
03207         // as ratio gets closer to 1.0, we need to add more and more
03208         // grid marks.
03209         Vector grid = new Vector(10);
03210 
03211         //grid.addElement(Double.valueOf(0.0));
03212         double ratio = Math.pow(10.0, step);
03213         int ngrid = 1;
03214 
03215         if (labeled) {
03216             // Set up the number of grid lines that will be labeled
03217             if (ratio <= 3.5) {
03218                 if (ratio > 2.0) {
03219                     ngrid = 2;
03220                 } else if (ratio > 1.26) {
03221                     ngrid = 5;
03222                 } else if (ratio > 1.125) {
03223                     ngrid = 10;
03224                 } else {
03225                     ngrid = (int) Math.rint(1.0 / step);
03226                 }
03227             }
03228         } else {
03229             // Set up the number of grid lines that will not be labeled
03230             if (ratio > 10.0) {
03231                 ngrid = 1;
03232             } else if (ratio > 3.0) {
03233                 ngrid = 2;
03234             } else if (ratio > 2.0) {
03235                 ngrid = 5;
03236             } else if (ratio > 1.125) {
03237                 ngrid = 10;
03238             } else {
03239                 ngrid = 100;
03240             }
03241 
03242             // Note: we should keep going here, but this increases the
03243             // size of the grid array and slows everything down.
03244         }
03245 
03246         int oldgridi = 0;
03247 
03248         for (int i = 0; i < ngrid; i++) {
03249             double gridval = (i * 1.0) / ngrid * 10;
03250             double logval = _LOG10SCALE * Math.log(gridval);
03251 
03252             if (logval == Double.NEGATIVE_INFINITY) {
03253                 logval = 0.0;
03254             }
03255 
03256             // If oldgrid is not null, then do not draw lines that
03257             // were already drawn in oldgrid.  This is necessary
03258             // so we avoid obliterating the tick marks on the plot borders.
03259             if ((oldgrid != null) && (oldgridi < oldgrid.size())) {
03260                 // Cycle through the oldgrid until we find an element
03261                 // that is equal to or greater than the element we are
03262                 // trying to add.
03263                 while ((oldgridi < oldgrid.size())
03264                         && (((Double) oldgrid.elementAt(oldgridi))
03265                                 .doubleValue() < logval)) {
03266                     oldgridi++;
03267                 }
03268 
03269                 if (oldgridi < oldgrid.size()) {
03270                     // Using == on doubles is bad if the numbers are close,
03271                     // but not exactly equal.
03272                     if (Math.abs(((Double) oldgrid.elementAt(oldgridi))
03273                             .doubleValue()
03274                             - logval) > 0.00001) {
03275                         grid.addElement(Double.valueOf(logval));
03276                     }
03277                 } else {
03278                     grid.addElement(Double.valueOf(logval));
03279                 }
03280             } else {
03281                 grid.addElement(Double.valueOf(logval));
03282             }
03283         }
03284 
03285         // _gridCurJuke and _gridBase are used in _gridStep();
03286         _gridCurJuke = 0;
03287 
03288         if (low == -0.0) {
03289             low = 0.0;
03290         }
03291 
03292         _gridBase = Math.floor(low);
03293 
03294         double x = low - _gridBase;
03295 
03296         // Set gridCurJuke so that the value in grid is greater than
03297         // or equal to x.  This sets us up to process the first point.
03298         for (_gridCurJuke = -1; ((_gridCurJuke + 1) < grid.size())
03299                 && (x >= ((Double) grid.elementAt(_gridCurJuke + 1))
03300                         .doubleValue()); _gridCurJuke++) {
03301         }
03302 
03303         return grid;
03304     }
03305 
03306     /*
03307      * Round pos up to the nearest value in the grid.
03308      */
03309     private double _gridRoundUp(Vector grid, double pos) {
03310         double x = pos - Math.floor(pos);
03311         int i;
03312 
03313         for (i = 0; (i < grid.size())
03314                 && (x >= ((Double) grid.elementAt(i)).doubleValue()); i++) {
03315         }
03316 
03317         if (i >= grid.size()) {
03318             return pos;
03319         } else {
03320             return Math.floor(pos) + ((Double) grid.elementAt(i)).doubleValue();
03321         }
03322     }
03323 
03324     /*
03325      * Used to find the next value for the axis label.
03326      * For non-log axes, we just return pos + step.
03327      * For log axes, we read the appropriate value in the grid Vector,
03328      * add it to _gridBase and return the sum.  We also take care
03329      * to reset _gridCurJuke if necessary.
03330      * Note that for log axes, _gridInit() must be called before
03331      * calling _gridStep().
03332      * Based on stepGrid() from xgraph.c by David Harrison.
03333      */
03334     private double _gridStep(Vector grid, double pos, double step,
03335             boolean logflag) {
03336         if (logflag) {
03337             if (++_gridCurJuke >= grid.size()) {
03338                 _gridCurJuke = 0;
03339                 _gridBase += Math.ceil(step);
03340             }
03341 
03342             if (_gridCurJuke >= grid.size()) {
03343                 return pos + step;
03344             }
03345 
03346             return _gridBase
03347                     + ((Double) grid.elementAt(_gridCurJuke)).doubleValue();
03348         } else {
03349             return pos + step;
03350         }
03351     }
03352 
03353     /*
03354      * Measure the various fonts.  You only want to call this once.
03355      */
03356     private void _measureFonts() {
03357         // We only measure the fonts once, and we do it from addNotify().
03358         if (_labelFont == null) {
03359             _labelFont = new Font("Helvetica", Font.PLAIN, 12);
03360         }
03361 
03362         if (_superscriptFont == null) {
03363             _superscriptFont = new Font("Helvetica", Font.PLAIN, 9);
03364         }
03365 
03366         if (_titleFont == null) {
03367             _titleFont = new Font("Helvetica", Font.BOLD, 14);
03368         }
03369 
03370         _labelFontMetrics = getFontMetrics(_labelFont);
03371         _superscriptFontMetrics = getFontMetrics(_superscriptFont);
03372         _titleFontMetrics = getFontMetrics(_titleFont);
03373     }
03374 
03375     /*
03376      * Return the number of fractional digits required to display the
03377      * given number.  No number larger than 15 is returned (if
03378      * more than 15 digits are required, 15 is returned).
03379      */
03380     private int _numFracDigits(double num) {
03381         int numdigits = 0;
03382 
03383         while ((numdigits <= 15) && (num != Math.floor(num))) {
03384             num *= 10.0;
03385             numdigits += 1;
03386         }
03387 
03388         return numdigits;
03389     }
03390 
03391     /*
03392      * Return the number of integer digits required to display the
03393      * given number.  No number larger than 15 is returned (if
03394      * more than 15 digits are required, 15 is returned).
03395      */
03396     private int _numIntDigits(double num) {
03397         int numdigits = 0;
03398 
03399         while ((numdigits <= 15) && ((int) num != 0.0)) {
03400             num /= 10.0;
03401             numdigits += 1;
03402         }
03403 
03404         return numdigits;
03405     }
03406 
03407     /*
03408      * Parse a string of the form: "word num, word num, word num, ..."
03409      * where the word must be enclosed in quotes if it contains spaces,
03410      * and the number is interpreted as a floating point number.  Ignore
03411      * any incorrectly formatted fields.  I <i>xtick</i> is true, then
03412      * interpret the parsed string to specify the tick labels on the x axis.
03413      * Otherwise, do the y axis.
03414      */
03415     private void _parsePairs(String line, boolean xtick) {
03416         // Clear current ticks first.
03417         if (xtick) {
03418             _xticks = null;
03419             _xticklabels = null;
03420         } else {
03421             _yticks = null;
03422             _yticklabels = null;
03423         }
03424 
03425         int start = 0;
03426         boolean cont = true;
03427 
03428         while (cont) {
03429             int comma = line.indexOf(",", start);
03430             String pair = null;
03431 
03432             if (comma > start) {
03433                 pair = (line.substring(start, comma)).trim();
03434             } else {
03435                 pair = (line.substring(start)).trim();
03436                 cont = false;
03437             }
03438 
03439             int close = -1;
03440             int open = 0;
03441 
03442             if (pair.startsWith("\"")) {
03443                 close = pair.indexOf("\"", 1);
03444                 open = 1;
03445             } else {
03446                 close = pair.indexOf(" ");
03447             }
03448 
03449             if (close > 0) {
03450                 String label = pair.substring(open, close);
03451                 String index = (pair.substring(close + 1)).trim();
03452 
03453                 try {
03454                     double idx = (Double.valueOf(index)).doubleValue();
03455 
03456                     if (xtick) {
03457                         addXTick(label, idx);
03458                     } else {
03459                         addYTick(label, idx);
03460                     }
03461                 } catch (NumberFormatException e) {
03462                     System.err.println("Warning from PlotBox: "
03463                             + "Unable to parse ticks: " + e.getMessage());
03464 
03465                     // ignore if format is bogus.
03466                 }
03467             }
03468 
03469             start = comma + 1;
03470             comma = line.indexOf(",", start);
03471         }
03472     }
03473 
03477     private RenderingHints _defaultImageRenderingHints() {
03478         RenderingHints hints = new RenderingHints(null);
03479         hints.put(RenderingHints.KEY_ANTIALIASING,
03480                 RenderingHints.VALUE_ANTIALIAS_ON);
03481         return hints;
03482     }
03483 
03484     /*
03485      * Given a number, round up to the nearest power of ten
03486      * times 1, 2, or 5.
03487      *
03488      * Note: The argument must be strictly positive.
03489      */
03490     private double _roundUp(double val) {
03491         int exponent = (int) Math.floor(Math.log(val) * _LOG10SCALE);
03492         val *= Math.pow(10, -exponent);
03493 
03494         if (val > 5.0) {
03495             val = 10.0;
03496         } else if (val > 2.0) {
03497             val = 5.0;
03498         } else if (val > 1.0) {
03499             val = 2.0;
03500         }
03501 
03502         val *= Math.pow(10, exponent);
03503         return val;
03504     }
03505 
03506     /*
03507      * Internal implementation of setXRange, so that it can be called when
03508      * autoranging.
03509      */
03510     private void _setXRange(double min, double max) {
03511         // We check to see if the original range has been given here
03512         // because if we check in setXRange(), then we will not catch
03513         // the case where we have a simple plot file that consists of just
03514         // data points
03515         //
03516         // 1. Create a file that consists of two data points
03517         //   1 1
03518         //   2 3
03519         // 2. Start up plot on it
03520         // $PTII/bin/ptplot foo.plt
03521         // 3. Zoom in
03522         // 4. Hit reset axes
03523         // 5. The bug is that the axes do not reset to the initial settings
03524         // Changing the range means we have to replot.
03525         _plotImage = null;
03526 
03527         if (!_originalXRangeGiven) {
03528             _originalXlow = min;
03529             _originalXhigh = max;
03530             _originalXRangeGiven = true;
03531         }
03532 
03533         // If values are invalid, try for something reasonable.
03534         if (min > max) {
03535             min = -1.0;
03536             max = 1.0;
03537         } else if (min == max) {
03538             min -= 1.0;
03539             max += 1.0;
03540         }
03541 
03542         //if (_xRangeGiven) {
03543         // The user specified the range, so don't pad.
03544         //    _xMin = min;
03545         //    _xMax = max;
03546         //} else {
03547         // Pad slightly so that we don't plot points on the axes.
03548         _xMin = min - ((max - min) * _padding);
03549         _xMax = max + ((max - min) * _padding);
03550 
03551         //}
03552         // Find the exponent.
03553         double largest = Math.max(Math.abs(_xMin), Math.abs(_xMax));
03554         _xExp = (int) Math.floor(Math.log(largest) * _LOG10SCALE);
03555 
03556         // Use the exponent only if it's larger than 2 in magnitude.
03557         if ((_xExp > 2) || (_xExp < -2)) {
03558             double xs = 1.0 / Math.pow(10.0, _xExp);
03559             _xtickMin = _xMin * xs;
03560             _xtickMax = _xMax * xs;
03561         } else {
03562             _xtickMin = _xMin;
03563             _xtickMax = _xMax;
03564             _xExp = 0;
03565         }
03566     }
03567 
03568     /*
03569      * Internal implementation of setYRange, so that it can be called when
03570      * autoranging.
03571      */
03572     private void _setYRange(double min, double max) {
03573         // See comment in _setXRange() about why this is necessary.
03574         // Changing the range means we have to replot.
03575         _plotImage = null;
03576 
03577         if (!_originalYRangeGiven) {
03578             _originalYlow = min;
03579             _originalYhigh = max;
03580             _originalYRangeGiven = true;
03581         }
03582 
03583         // If values are invalid, try for something reasonable.
03584         if (min > max) {
03585             min = -1.0;
03586             max = 1.0;
03587         } else if (min == max) {
03588             min -= 0.1;
03589             max += 0.1;
03590         }
03591 
03592         //if (_yRangeGiven) {
03593         // The user specified the range, so don't pad.
03594         //    _yMin = min;
03595         //    _yMax = max;
03596         //} else {
03597         // Pad slightly so that we don't plot points on the axes.
03598         _yMin = min - ((max - min) * _padding);
03599         _yMax = max + ((max - min) * _padding);
03600 
03601         //}
03602         // Find the exponent.
03603         double largest = Math.max(Math.abs(_yMin), Math.abs(_yMax));
03604         _yExp = (int) Math.floor(Math.log(largest) * _LOG10SCALE);
03605 
03606         // Use the exponent only if it's larger than 2 in magnitude.
03607         if ((_yExp > 2) || (_yExp < -2)) {
03608             double ys = 1.0 / Math.pow(10.0, _yExp);
03609             _ytickMin = _yMin * ys;
03610             _ytickMax = _yMax * ys;
03611         } else {
03612             _ytickMin = _yMin;
03613             _ytickMax = _yMax;
03614             _yExp = 0;
03615         }
03616     }
03617 
03618     /*
03619      *  Zoom in or out based on the box that has been drawn.
03620      *  The argument gives the lower right corner of the box.
03621      *  This method is not synchronized because it is called within
03622      *  the UI thread, and making it synchronized causes a deadlock.
03623      *  @param x The final x position.
03624      *  @param y The final y position.
03625      */
03626     void _zoom(int x, int y) {
03627         // FIXME: This is friendly because Netscape 4.0.3 cannot access it if
03628         // it is private!
03629         // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does
03630         // not work on mouse drags, thus we have to use this variable
03631         // to determine whether we are actually zooming. It is used only
03632         // in _zoomBox, since calling this method is properly masked.
03633         _zooming = false;
03634 
03635         Graphics graphics = getGraphics();
03636 
03637         // Ignore if there is no graphics object to draw on.
03638         if (graphics == null) {
03639             return;
03640         }
03641 
03642         if ((_zoomin == true) && (_drawn == true)) {
03643             if ((_zoomxn != -1) || (_zoomyn != -1)) {
03644                 // erase previous rectangle.
03645                 int minx = Math.min(_zoomx, _zoomxn);
03646                 int maxx = Math.max(_zoomx, _zoomxn);
03647                 int miny = Math.min(_zoomy, _zoomyn);
03648                 int maxy = Math.max(_zoomy, _zoomyn);
03649                 graphics.setXORMode(_boxColor);
03650                 graphics.drawRect(minx, miny, maxx - minx, maxy - miny);
03651                 graphics.setPaintMode();
03652 
03653                 // constrain to be in range
03654                 if (y > _lry) {
03655                     y = _lry;
03656                 }
03657 
03658                 if (y < _uly) {
03659                     y = _uly;
03660                 }
03661 
03662                 if (x > _lrx) {
03663                     x = _lrx;
03664                 }
03665 
03666                 if (x < _ulx) {
03667                     x = _ulx;
03668                 }
03669 
03670                 // NOTE: ignore if total drag less than 5 pixels.
03671                 if ((Math.abs(_zoomx - x) > 5) && (Math.abs(_zoomy - y) > 5)) {
03672                     double a = _xMin + ((_zoomx - _ulx) / _xscale);
03673                     double b = _xMin + ((x - _ulx) / _xscale);
03674 
03675                     // NOTE: It used to be that it was problematic to set
03676                     // the X range here because it conflicted with the wrap
03677                     // mechanism.  But now the wrap mechanism saves the state
03678                     // of the X range when the setWrap() method is called,
03679                     // so this is safe.
03680                     // EAL 6/12/00.
03681                     if (a < b) {
03682                         setXRange(a, b);
03683                     } else {
03684                         setXRange(b, a);
03685                     }
03686 
03687                     a = _yMax - ((_zoomy - _uly) / _yscale);
03688                     b = _yMax - ((y - _uly) / _yscale);
03689 
03690                     if (a < b) {
03691                         setYRange(a, b);
03692                     } else {
03693                         setYRange(b, a);
03694                     }
03695                 }
03696 
03697                 repaint();
03698             }
03699         } else if ((_zoomout == true) && (_drawn == true)) {
03700             // Erase previous rectangle.
03701             graphics.setXORMode(_boxColor);
03702 
03703             int x_diff = Math.abs(_zoomx - _zoomxn);
03704             int y_diff = Math.abs(_zoomy - _zoomyn);
03705             graphics.drawRect(_zoomx - 15 - x_diff, _zoomy - 15 - y_diff,
03706                     30 + (x_diff * 2), 30 + (y_diff * 2));
03707             graphics.setPaintMode();
03708 
03709             // Calculate zoom factor.
03710             double a = (Math.abs(_zoomx - x)) / 30.0;
03711             double b = (Math.abs(_zoomy - y)) / 30.0;
03712             double newx1 = _xMax + ((_xMax - _xMin) * a);
03713             double newx2 = _xMin - ((_xMax - _xMin) * a);
03714 
03715             // NOTE: To limit zooming out to the fill area, uncomment this...
03716             // if (newx1 > _xTop) newx1 = _xTop;
03717             // if (newx2 < _xBottom) newx2 = _xBottom;
03718             double newy1 = _yMax + ((_yMax - _yMin) * b);
03719             double newy2 = _yMin - ((_yMax - _yMin) * b);
03720 
03721             // NOTE: To limit zooming out to the fill area, uncomment this...
03722             // if (newy1 > _yTop) newy1 = _yTop;
03723             // if (newy2 < _yBottom) newy2 = _yBottom;
03724             zoom(newx2, newy2, newx1, newy1);
03725             repaint();
03726         } else if (_drawn == false) {
03727             repaint();
03728         }
03729 
03730         _drawn = false;
03731         _zoomin = _zoomout = false;
03732         _zoomxn = _zoomyn = _zoomx = _zoomy = -1;
03733     }
03734 
03735     /*
03736      *  Draw a box for an interactive zoom box.  The starting point (the
03737      *  upper left corner of the box) is taken
03738      *  to be that specified by the startZoom() method.  The argument gives
03739      *  the lower right corner of the box.  If a previous box
03740      *  has been drawn, erase it first.
03741      *  This method is not synchronized because it is called within
03742      *  the UI thread, and making it synchronized causes a deadlock.
03743      *  @param x The x position.
03744      *  @param y The y position.
03745      */
03746     void _zoomBox(int x, int y) {
03747         // FIXME: This is friendly because Netscape 4.0.3 cannot access it if
03748         // it is private!
03749         // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does
03750         // not work on mouse drags, thus we have to use this variable
03751         // to determine whether we are actually zooming.
03752         if (!_zooming) {
03753             return;
03754         }
03755 
03756         Graphics graphics = getGraphics();
03757 
03758         // Ignore if there is no graphics object to draw on.
03759         if (graphics == null) {
03760             return;
03761         }
03762 
03763         // Bound the rectangle so it doesn't go outside the box.
03764         if (y > _lry) {
03765             y = _lry;
03766         }
03767 
03768         if (y < _uly) {
03769             y = _uly;
03770         }
03771 
03772         if (x > _lrx) {
03773             x = _lrx;
03774         }
03775 
03776         if (x < _ulx) {
03777             x = _ulx;
03778         }
03779 
03780         // erase previous rectangle, if there was one.
03781         if (((_zoomx != -1) || (_zoomy != -1))) {
03782             // Ability to zoom out added by William Wu.
03783             // If we are not already zooming, figure out whether we
03784             // are zooming in or out.
03785             if ((_zoomin == false) && (_zoomout == false)) {
03786                 if (y < _zoomy) {
03787                     _zoomout = true;
03788 
03789                     // Draw reference box.
03790                     graphics.setXORMode(_boxColor);
03791                     graphics.drawRect(_zoomx - 15, _zoomy - 15, 30, 30);
03792                 } else if (y > _zoomy) {
03793                     _zoomin = true;
03794                 }
03795             }
03796 
03797             if (_zoomin == true) {
03798                 // Erase the previous box if necessary.
03799                 if (((_zoomxn != -1) || (_zoomyn != -1)) && (_drawn == true)) {
03800                     int minx = Math.min(_zoomx, _zoomxn);
03801                     int maxx = Math.max(_zoomx, _zoomxn);
03802                     int miny = Math.min(_zoomy, _zoomyn);
03803                     int maxy = Math.max(_zoomy, _zoomyn);
03804                     graphics.setXORMode(_boxColor);
03805                     graphics.drawRect(minx, miny, maxx - minx, maxy - miny);
03806                 }
03807 
03808                 // Draw a new box if necessary.
03809                 if (y > _zoomy) {
03810                     _zoomxn = x;
03811                     _zoomyn = y;
03812 
03813                     int minx = Math.min(_zoomx, _zoomxn);
03814                     int maxx = Math.max(_zoomx, _zoomxn);
03815                     int miny = Math.min(_zoomy, _zoomyn);
03816                     int maxy = Math.max(_zoomy, _zoomyn);
03817                     graphics.setXORMode(_boxColor);
03818                     graphics.drawRect(minx, miny, maxx - minx, maxy - miny);
03819                     _drawn = true;
03820                     return;
03821                 } else {
03822                     _drawn = false;
03823                 }
03824             } else if (_zoomout == true) {
03825                 // Erase previous box if necessary.
03826                 if (((_zoomxn != -1) || (_zoomyn != -1)) && (_drawn == true)) {
03827                     int x_diff = Math.abs(_zoomx - _zoomxn);
03828                     int y_diff = Math.abs(_zoomy - _zoomyn);
03829                     graphics.setXORMode(_boxColor);
03830                     graphics.drawRect(_zoomx - 15 - x_diff, _zoomy - 15
03831                             - y_diff, 30 + (x_diff * 2), 30 + (y_diff * 2));
03832                 }
03833 
03834                 if (y < _zoomy) {
03835                     _zoomxn = x;
03836                     _zoomyn = y;
03837 
03838                     int x_diff = Math.abs(_zoomx - _zoomxn);
03839                     int y_diff = Math.abs(_zoomy - _zoomyn);
03840                     graphics.setXORMode(_boxColor);
03841                     graphics.drawRect(_zoomx - 15 - x_diff, _zoomy - 15
03842                             - y_diff, 30 + (x_diff * 2), 30 + (y_diff * 2));
03843                     _drawn = true;
03844                     return;
03845                 } else {
03846                     _drawn = false;
03847                 }
03848             }
03849         }
03850 
03851         graphics.setPaintMode();
03852     }
03853 
03854     /*
03855      *  Set the starting point for an interactive zoom box (the upper left
03856      *  corner).
03857      *  This method is not synchronized because it is called within
03858      *  the UI thread, and making it synchronized causes a deadlock.
03859      *  @param x The x position.
03860      *  @param y The y position.
03861      */
03862     void _zoomStart(int x, int y) {
03863         // FIXME: This is friendly because Netscape 4.0.3 cannot access it if
03864         // it is private!
03865         // constrain to be in range
03866         if (y > _lry) {
03867             y = _lry;
03868         }
03869 
03870         if (y < _uly) {
03871             y = _uly;
03872         }
03873 
03874         if (x > _lrx) {
03875             x = _lrx;
03876         }
03877 
03878         if (x < _ulx) {
03879             x = _ulx;
03880         }
03881 
03882         _zoomx = x;
03883         _zoomy = y;
03884         _zooming = true;
03885     }
03886 
03889 
03891     private boolean _actionsDeferred = false;
03892 
03894     private List _deferredActions;
03895 
03897     private String _filespec = null;
03898 
03899     // Call setXORMode with a hardwired color because
03900     // _background does not work in an application,
03901     // and _foreground does not work in an applet.
03902     // NOTE: For some reason, this comes out blue, which is fine...
03903     private static final Color _boxColor = Color.orange;
03904 
03908     private double _ytickMax = 0.0;
03909 
03913     private double _ytickMin = 0.0;
03914 
03918     private double _xtickMax = 0.0;
03919 
03923     private double _xtickMin = 0.0;
03924 
03928     private int _yExp = 0;
03929 
03933     private int _xExp = 0;
03934 
03936     private double _ytickscale = 0.0;
03937 
03939     private double _xtickscale = 0.0;
03940 
03942     private Font _labelFont = null;
03943 
03945     private Font _superscriptFont = null;
03946 
03948     private Font _titleFont = null;
03949 
03951     private FontMetrics _labelFontMetrics = null;
03952 
03954     private FontMetrics _superscriptFontMetrics = null;
03955 
03957     private FontMetrics _titleFontMetrics = null;
03958 
03959     // Number format cache used by _formatNum.
03960     // See the comment in _formatNum for more information.
03961     // private transient NumberFormat _numberFormat = null;
03962     // Used for log axes. Index into vector of axis labels.
03963     private transient int _gridCurJuke = 0;
03964 
03965     // Used for log axes.  Base of the grid.
03966     private transient double _gridBase = 0.0;
03967 
03968     // An array of strings for reporting errors.
03969     private transient String[] _errorMsg;
03970 
03972     private String _xlabel;
03973 
03975     private String _ylabel;
03976 
03978     private String _title;
03979 
03981     private Vector _legendStrings = new Vector();
03982 
03984     private Vector _legendDatasets = new Vector();
03985 
03987     private Vector _xticks = null;
03988 
03990     private Vector _xticklabels = null;
03991 
03993     private Vector _yticks = null;
03994 
03996     private Vector _yticklabels = null;
03997 
03998     // A button for filling the plot
03999     private transient JButton _fillButton = null;
04000 
04001     // A button for formatting the plot
04002     private transient JButton _formatButton = null;
04003 
04004     // Indicator of whether X and Y range has been first specified.
04005     boolean _originalXRangeGiven = false;
04006 
04007     // Indicator of whether X and Y range has been first specified.
04008     boolean _originalYRangeGiven = false;
04009 
04010     // First values specified to setXRange() and setYRange().
04011     double _originalXlow = 0.0;
04012 
04013     // First values specified to setXRange() and setYRange().
04014     double _originalXhigh = 0.0;
04015 
04016     // First values specified to setXRange() and setYRange().
04017     double _originalYlow = 0.0;
04018 
04019     // First values specified to setXRange() and setYRange().
04020     double _originalYhigh = 0.0;
04021 
04022     // An offscreen buffer for improving plot performance.
04023     protected transient BufferedImage _plotImage = null;
04024 
04025     // A button for printing the plot
04026     private transient JButton _printButton = null;
04027 
04028     // A button for filling the plot
04029     private transient JButton _resetButton = null;
04030 
04031     // Variables keeping track of the interactive zoom box.
04032     // Initialize to impossible values.
04033     private transient int _zoomx = -1;
04034 
04035     private transient int _zoomy = -1;
04036 
04037     private transient int _zoomxn = -1;
04038 
04039     private transient int _zoomyn = -1;
04040 
04041     // Control whether we are zooming in or out.
04042     private transient boolean _zoomin = false;
04043 
04044     private transient boolean _zoomout = false;
04045 
04046     private transient boolean _drawn = false;
04047 
04048     private transient boolean _zooming = false;
04049 
04050     // NOTE: It is unfortunate to have to include the DTD here, but there
04051     // seems to be no other way to ensure that the generated data exactly
04052     // matches the DTD.
04053     //    private static final String _DTD =
04054     //    "<!-- PlotML DTD, created by Edward A. Lee.\n"
04055     //    + "   See http://ptolemy.eecs.berkeley.edu/java/ptplot -->\n"
04056     //    + "<!ELEMENT plot (barGraph | bin | dataset | default | noColor | \n"
04057     //    + "        noGrid | title | wrap | xLabel | xLog | xRange | xTicks | yLabel | \n"
04058     //    + " yLog | yRange | yTicks)*>\n"
04059     //    + "  <!ELEMENT barGraph EMPTY>\n"
04060     //    + "    <!ATTLIST barGraph width CDATA #IMPLIED>\n"
04061     //    + "    <!ATTLIST barGraph offset CDATA #IMPLIED>\n"
04062     //    + "  <!ELEMENT bin EMPTY>\n"
04063     //    + "    <!ATTLIST bin width CDATA #IMPLIED>\n"
04064     //    + "    <!ATTLIST bin offset CDATA #IMPLIED>\n"
04065     //    + "  <!ELEMENT dataset (m | move | p | point)*>\n"
04066     //    + "    <!ATTLIST dataset connected (yes | no) #IMPLIED>\n"
04067     //    + "    <!ATTLIST dataset marks (none | dots | points | various) #IMPLIED>\n"
04068     //    + "    <!ATTLIST dataset name CDATA #IMPLIED>\n"
04069     //    + "    <!ATTLIST dataset stems (yes | no) #IMPLIED>\n"
04070     //    + "  <!ELEMENT default EMPTY>\n"
04071     //    + "    <!ATTLIST default connected (yes | no) \"yes\">\n"
04072     //    + "    <!ATTLIST default marks (none | dots | points | various) \"none\">\n"
04073     //    + "    <!ATTLIST default stems (yes | no) \"no\">\n"
04074     //    + "  <!ELEMENT noColor EMPTY>\n"
04075     //    + "  <!ELEMENT noGrid EMPTY>\n"
04076     //    + "  <!ELEMENT title (#PCDATA)>\n"
04077     //    + "  <!ELEMENT wrap EMPTY>\n"
04078     //    + "  <!ELEMENT xLabel (#PCDATA)>\n"
04079     //    + "  <!ELEMENT xLog EMPTY>\n"
04080     //    + "  <!ELEMENT xRange EMPTY>\n"
04081     //    + "    <!ATTLIST xRange min CDATA #REQUIRED>\n"
04082     //    + "    <!ATTLIST xRange max CDATA #REQUIRED>\n"
04083     //    + "  <!ELEMENT xTicks (tick)+>\n"
04084     //    + "  <!ELEMENT yLabel (#PCDATA)>\n"
04085     //    + "  <!ELEMENT yLog EMPTY>\n"
04086     //    + "  <!ELEMENT yRange EMPTY>\n"
04087     //    + "    <!ATTLIST yRange min CDATA #REQUIRED>\n"
04088     //    + "    <!ATTLIST yRange max CDATA #REQUIRED>\n"
04089     //    + "  <!ELEMENT yTicks (tick)+>\n"
04090     //    + "    <!ELEMENT tick EMPTY>\n"
04091     //    + "      <!ATTLIST tick label CDATA #REQUIRED>\n"
04092     //    + "      <!ATTLIST tick position CDATA #REQUIRED>\n"
04093     //    + "    <!ELEMENT m EMPTY>\n"
04094     //    + "      <!ATTLIST m x CDATA #IMPLIED>\n"
04095     //    + "      <!ATTLIST m x CDATA #REQUIRED>\n"
04096     //    + "      <!ATTLIST m lowErrorBar CDATA #IMPLIED>\n"
04097     //    + "      <!ATTLIST m highErrorBar CDATA #IMPLIED>\n"
04098     //    + "    <!ELEMENT move EMPTY>\n"
04099     //    + "      <!ATTLIST move x CDATA #IMPLIED>\n"
04100     //    + "      <!ATTLIST move x CDATA #REQUIRED>\n"
04101     //    + "      <!ATTLIST move lowErrorBar CDATA #IMPLIED>\n"
04102     //    + "      <!ATTLIST move highErrorBar CDATA #IMPLIED>\n"
04103     //    + "    <!ELEMENT p EMPTY>\n"
04104     //    + "      <!ATTLIST p x CDATA #IMPLIED>\n"
04105     //    + "      <!ATTLIST p x CDATA #REQUIRED>\n"
04106     //    + "      <!ATTLIST p lowErrorBar CDATA #IMPLIED>\n"
04107     //    + "      <!ATTLIST p highErrorBar CDATA #IMPLIED>\n"
04108     //    + "    <!ELEMENT point EMPTY>\n"
04109     //    + "      <!ATTLIST point x CDATA #IMPLIED>\n"
04110     //    + "      <!ATTLIST point x CDATA #REQUIRED>\n"
04111     //    + "      <!ATTLIST point lowErrorBar CDATA #IMPLIED>\n"
04112     //    + "      <!ATTLIST point highErrorBar CDATA #IMPLIED>";
04115     class ButtonListener implements ActionListener {
04116         public void actionPerformed(ActionEvent event) {
04117             if (event.getSource() == _fillButton) {
04118                 fillPlot();
04119             } else if (event.getSource() == _printButton) {
04120                 // FIXME:  Code duplication with PlotFrame._printCrossPlatform
04121                 PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
04122                 PrinterJob job = PrinterJob.getPrinterJob();
04123 
04124                 // rbeyer@LPL.Arizona.EDU: Get the Page Format and use it.
04125                 //PageFormat format = job.pageDialog(job.defaultPage());
04126                 //job.setPrintable(PlotBox.this, format);
04127                 job.setPrintable(PlotBox.this);
04128 
04129                 if (job.printDialog(aset)) {
04130                     try {
04131                         job.print(aset);
04132                     } catch (Exception ex) {
04133                         Component ancestor = getTopLevelAncestor();
04134                         JOptionPane.showMessageDialog(ancestor,
04135                                 "Printing failed:\n" + ex.toString(),
04136                                 "Print Error", JOptionPane.WARNING_MESSAGE);
04137                     }
04138                 }
04139             } else if (event.getSource() == _resetButton) {
04140                 resetAxes();
04141             } else if (event.getSource() == _formatButton) {
04142                 PlotFormatter fmt = new PlotFormatter(PlotBox.this);
04143                 fmt.openModal();
04144             }
04145         }
04146     }
04147 
04148     public class ZoomListener implements MouseListener {
04149         public void mouseClicked(MouseEvent event) {
04150             requestFocus();
04151         }
04152 
04153         public void mouseEntered(MouseEvent event) {
04154         }
04155 
04156         public void mouseExited(MouseEvent event) {
04157         }
04158 
04159         public void mousePressed(MouseEvent event) {
04160             // http://developer.java.sun.com/developer/bugParade/bugs/4072703.html
04161             // BUTTON1_MASK still not set for MOUSE_PRESSED events
04162             // suggests:
04163             // Workaround
04164             //   Assume that a press event with no modifiers must be button 1.
04165             //   This has the serious drawback that it is impossible to be sure
04166             //   that button 1 hasn't been pressed along with one of the other
04167             //   buttons.
04168             // This problem affects Netscape 4.61 under Digital Unix and
04169             // 4.51 under Solaris
04170             if (((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0)
04171                     || (event.getModifiers() == 0)) {
04172                 PlotBox.this._zoomStart(event.getX(), event.getY());
04173             }
04174         }
04175 
04176         public void mouseReleased(MouseEvent event) {
04177             if (((event.getModifiers() & InputEvent.BUTTON1_MASK) != 0)
04178                     || (event.getModifiers() == 0)) {
04179                 PlotBox.this._zoom(event.getX(), event.getY());
04180             }
04181         }
04182     }
04183 
04184     public class DragListener implements MouseMotionListener {
04185         public void mouseDragged(MouseEvent event) {
04186             // NOTE: Due to a bug in JDK 1.1.7B, the BUTTON1_MASK does
04187             // not work on mouse drags.  It does work on MouseListener
04188             // methods, so those methods set a variable _zooming that
04189             // is used by _zoomBox to determine whether to draw a box.
04190             // if ((event.getModifiers() & event.BUTTON1_MASK)!= 0) {
04191             PlotBox.this._zoomBox(event.getX(), event.getY());
04192 
04193             // }
04194         }
04195 
04196         public void mouseMoved(MouseEvent event) {
04197         }
04198     }
04199 
04200     class CommandListener implements KeyListener {
04201         public void keyPressed(KeyEvent e) {
04202             int keycode = e.getKeyCode();
04203 
04204             switch (keycode) {
04205             case KeyEvent.VK_CONTROL:
04206                 _control = true;
04207                 break;
04208 
04209             case KeyEvent.VK_SHIFT:
04210                 _shift = true;
04211                 break;
04212 
04213             case KeyEvent.VK_C:
04214 
04215                 if (_control) {
04216                     // The "null" sends the output to the clipboard.
04217                     export(null);
04218 
04219                     String message = "Encapsulated PostScript (EPS) "
04220                             + "exported to clipboard.";
04221                     JOptionPane.showMessageDialog(PlotBox.this, message,
04222                             "Ptolemy Plot Message",
04223                             JOptionPane.INFORMATION_MESSAGE);
04224                 }
04225 
04226                 break;
04227 
04228             case KeyEvent.VK_D:
04229 
04230                 if (!_control && _shift) {
04231                     write(System.out);
04232 
04233                     String message = "Plot data sent to standard out.";
04234                     JOptionPane.showMessageDialog(PlotBox.this, message,
04235                             "Ptolemy Plot Message",
04236                             JOptionPane.INFORMATION_MESSAGE);
04237                 }
04238 
04239                 if (_control) {
04240                     // xgraph and many other Unix apps use Control-D to exit
04241                     StringUtilities.exit(1);
04242                 }
04243 
04244                 break;
04245 
04246             case KeyEvent.VK_E:
04247 
04248                 if (!_control && _shift) {
04249                     export(System.out);
04250 
04251                     String message = "Encapsulated PostScript (EPS) "
04252                             + "exported to standard out.";
04253                     JOptionPane.showMessageDialog(PlotBox.this, message,
04254                             "Ptolemy Plot Message",
04255                             JOptionPane.INFORMATION_MESSAGE);
04256                 }
04257 
04258                 break;
04259 
04260             case KeyEvent.VK_F:
04261 
04262                 if (!_control && _shift) {
04263                     fillPlot();
04264                 }
04265 
04266                 break;
04267 
04268             case KeyEvent.VK_H:
04269 
04270                 if (!_control && _shift) {
04271                     _help();
04272                 }
04273 
04274                 break;
04275 
04276             case KeyEvent.VK_Q:
04277 
04278                 if (!_control) {
04279                     // xv uses q to quit.
04280                     StringUtilities.exit(1);
04281                 }
04282 
04283                 break;
04284 
04285             case KeyEvent.VK_SLASH:
04286 
04287                 if (_shift) {
04288                     // Question mark is SHIFT-SLASH
04289                     _help();
04290                 }
04291 
04292                 break;
04293 
04294             default:
04295                 // None
04296             }
04297         }
04298 
04299         public void keyReleased(KeyEvent e) {
04300             int keycode = e.getKeyCode();
04301 
04302             switch (keycode) {
04303             case KeyEvent.VK_CONTROL:
04304                 _control = false;
04305                 break;
04306 
04307             case KeyEvent.VK_SHIFT:
04308                 _shift = false;
04309                 break;
04310 
04311             default:
04312                 // None
04313             }
04314         }
04315 
04316         // The keyTyped method is broken in jdk 1.1.4.
04317         // It always gets "unknown key code".
04318         public void keyTyped(KeyEvent e) {
04319         }
04320 
04321         private boolean _control = false;
04322 
04323         private boolean _shift = false;
04324     }
04325 }