/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.display.axes; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingConstants; import org.opensourcephysics.display.DrawingPanel; import org.opensourcephysics.display.GUIUtils; import org.opensourcephysics.display.Interactive; import org.opensourcephysics.display.PlottingPanel; import org.opensourcephysics.display.Selectable; import org.opensourcephysics.display.dialogs.DialogsRes; import org.opensourcephysics.media.core.ScientificField; import org.opensourcephysics.tools.FontSizer; import org.opensourcephysics.tools.ResourceLoader; /** * A cartesian axis class that provides interactive scale adjustment with a mouse. */ public class CartesianInteractive extends CartesianType1 implements Selectable { // static constant plot regions public static final int INSIDE = 0, HORZ_MIN = 1, HORZ_MAX = 2, VERT_MIN = 3, VERT_MAX = 4, HORZ_AXIS = 5, HORZ_AXIS_MIN = 6, HORZ_AXIS_MAX = 7, VERT_AXIS = 8, VERT_AXIS_MIN = 9, VERT_AXIS_MAX = 10, HORZ_VAR = 11, VERT_VAR = 12; // instance fields Rectangle hitRect = new Rectangle(); boolean drawHitRect; ScaleSetter scaleSetter; JPanel scaleSetterPanel; AxisMouseListener axisListener; int mouseRegion; Point mouseLoc; double mouseX, mouseY; PlottingPanel plot; boolean enabled = true; boolean altDown; Cursor horzCenter, horzRight, horzLeft, vertCenter, vertUp, vertDown, move; java.util.List<ActionListener> axisListeners = new java.util.ArrayList<ActionListener>(); // Paco /** * Constructs a set of interactive axes for a plotting panel. * * @param panel the PlottingPanel */ public CartesianInteractive(PlottingPanel panel) { super(panel); plot = panel; axisListener = new AxisMouseListener(); panel.addMouseListener(axisListener); panel.addMouseMotionListener(axisListener); panel.addKeyListener(new java.awt.event.KeyAdapter() { public void keyPressed(java.awt.event.KeyEvent e) { if (!enabled) return; if((mouseRegion==INSIDE)&&!drawingPanel.isFixedScale()&&(e.getKeyCode()==java.awt.event.KeyEvent.VK_ALT)) { altDown = true; plot.setMouseCursor(getPreferredCursor()); } } public void keyReleased(java.awt.event.KeyEvent e) { if (!enabled) return; if(e.getKeyCode()==java.awt.event.KeyEvent.VK_ALT) { altDown = false; plot.setMouseCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); } } }); scaleSetter = new ScaleSetter(); // create transparent scaleSetterPanel with no LayoutManager scaleSetterPanel = new javax.swing.JPanel(null); scaleSetterPanel.setOpaque(false); scaleSetterPanel.add(scaleSetter); plot.getGlassPanel().add(scaleSetterPanel, BorderLayout.CENTER); } /** * Gets the current plot region containing the mouse. * * @return one of the static plot regions defined by CartesianInteractive, or -1 */ public int getMouseRegion() { return mouseRegion; } /** * Draws the axes. * * @param panel the drawing panel * @param g the graphics context */ public void draw(DrawingPanel panel, Graphics g) { super.draw(panel, g); if(drawHitRect) { g.drawRect(hitRect.x, hitRect.y, hitRect.width, hitRect.height); } if(!panel.isFixedScale()&&scaleSetter.isVisible()&&(scaleSetter.scaleField.getBackground()!=Color.yellow)) { switch(scaleSetter.region) { case HORZ_MIN : scaleSetter.scaleField.setValue(drawingPanel.getXMin()); scaleSetter.autoscaleCheckbox.setSelected(drawingPanel.isAutoscaleXMin()); break; case HORZ_MAX : scaleSetter.scaleField.setValue(drawingPanel.getXMax()); scaleSetter.autoscaleCheckbox.setSelected(drawingPanel.isAutoscaleXMax()); break; case VERT_MIN : scaleSetter.scaleField.setValue(drawingPanel.getYMin()); scaleSetter.autoscaleCheckbox.setSelected(drawingPanel.isAutoscaleYMin()); break; case VERT_MAX : scaleSetter.scaleField.setValue(drawingPanel.getYMax()); scaleSetter.autoscaleCheckbox.setSelected(drawingPanel.isAutoscaleYMax()); } } } // overrides CartesianType1 method public double getX() { return Double.isNaN(mouseX) ? plot.pixToX(plot.getMouseIntX()) : mouseX; } // overrides CartesianType1 method public double getY() { return Double.isNaN(mouseY) ? plot.pixToY(plot.getMouseIntY()) : mouseY; } // implements Selectable public void setSelected(boolean selectable) {} // implements Selectable public boolean isSelected() { return false; } // implements Selectable public void toggleSelected() {} // implements Selectable public Cursor getPreferredCursor() { switch(mouseRegion) { case HORZ_AXIS_MIN : if(horzLeft==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/horzleft.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); horzLeft = GUIUtils.createCustomCursor(im, new Point(16, 16), "Horizontal Left", Cursor.W_RESIZE_CURSOR); //$NON-NLS-1$ } return horzLeft; case HORZ_AXIS_MAX : if(horzRight==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/horzright.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); horzRight = GUIUtils.createCustomCursor(im, new Point(16, 16), "Horizontal Right", Cursor.E_RESIZE_CURSOR); //$NON-NLS-1$ } return horzRight; case HORZ_AXIS : if(horzCenter==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/horzcenter.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); horzCenter = GUIUtils.createCustomCursor(im, new Point(16, 16), "Horizontal Center", Cursor.MOVE_CURSOR); //$NON-NLS-1$ } return horzCenter; case VERT_AXIS_MIN : if(vertDown==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/vertdown.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); vertDown = GUIUtils.createCustomCursor(im, new Point(16, 16), "Vertical Down", Cursor.S_RESIZE_CURSOR); //$NON-NLS-1$ } return vertDown; case VERT_AXIS_MAX : if(vertUp==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/vertup.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); vertUp = GUIUtils.createCustomCursor(im, new Point(16, 16), "Vertical Up", Cursor.N_RESIZE_CURSOR); //$NON-NLS-1$ } return vertUp; case VERT_AXIS : if(vertCenter==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/vertcenter.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); vertCenter = GUIUtils.createCustomCursor(im, new Point(16, 16), "Vertical Center", Cursor.MOVE_CURSOR); //$NON-NLS-1$ } return vertCenter; case INSIDE : if(move==null) { // create cursor String imageFile = "/org/opensourcephysics/resources/tools/images/movecursor.gif"; //$NON-NLS-1$ Image im = ResourceLoader.getImage(imageFile); move = GUIUtils.createCustomCursor(im, new Point(16, 16), "Move All Ways", Cursor.MOVE_CURSOR); //$NON-NLS-1$ } return move; case HORZ_VAR : case VERT_VAR : return Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } return Cursor.getDefaultCursor(); } // implements Interactive public boolean isEnabled() { return enabled; } // implements Interactive public void setEnabled(boolean enable) { enabled = enable; } public void addAxisListener(ActionListener listener) { axisListeners.add(listener); } // Paco // implements Interactive public Interactive findInteractive(DrawingPanel panel, int xpix, int ypix) { if(drawingPanel.isFixedScale()) { return null; } if(mouseRegion>=HORZ_MIN) { return this; } if(mouseRegion==-1) { return this; } if((mouseRegion==INSIDE)&&altDown) { return this; } return null; } // implements Interactive public void setXY(double x, double y) {} // implements Measurable public boolean isMeasured() { return true; } // implements Measurable public double getXMin() { return drawingPanel.getXMin(); } // implements Measurable public double getXMax() { return drawingPanel.getXMax(); } // implements Measurable public double getYMin() { return drawingPanel.getYMin(); } // implements Measurable public double getYMax() { return drawingPanel.getYMax(); } /** * Hides the scale setter. */ public void hideScaleSetter() { if(scaleSetter!=null) { scaleSetter.autoscaleCheckbox.requestFocusInWindow(); scaleSetter.setVisible(false); } } /** * Resizes fonts by the specified factor. * * @param factor the factor * @param panel the drawing panel on which these axes are drawn */ public void resizeFonts(double factor, DrawingPanel panel) { super.resizeFonts(factor, panel); if(scaleSetter!=null) { scaleSetter.scaleField.setFont(FontSizer.getResizedFont(scaleSetter.scaleField.getFont(), factor)); scaleSetter.autoscaleCheckbox.setFont(FontSizer.getResizedFont(scaleSetter.autoscaleCheckbox.getFont(), factor)); } } /** * Reports whether this provides a popup menu for setting the horizontal axis variable. * * @return true if this has a popup menu with horizontal axis variables */ protected boolean hasHorzVariablesPopup() { return false; } /** * Gets a popup menu with horizontal axis variables. * This default method returns null; subclasses should override to return * a popup with associated action for setting horizontal axis variable. * * @return the popup menu */ protected JPopupMenu getHorzVariablesPopup() { return null; } /** * Reports whether this provides a popup menu for setting the vertical axis variable. * * @return true if this has a popup menu with vertical axis variables */ protected boolean hasVertVariablesPopup() { return false; } /** * Gets a popup menu with vertical axis variables. * This default method returns null; subclasses should override to return * a popup with associated action for setting vertical axis variable. * * @return the popup menu */ protected JPopupMenu getVertVariablesPopup() { return null; } /** * Finds the plot region containing the specified point. * * @param p the point * @return one of the static regions defined by CartesianInteractive */ protected int findRegion(Point p) { int l = drawingPanel.getLeftGutter(); int r = drawingPanel.getRightGutter(); int t = drawingPanel.getTopGutter(); int b = drawingPanel.getBottomGutter(); Dimension plotDim = drawingPanel.getSize(); // horizontal axis int axisLen = plotDim.width-r-l; hitRect.setSize(axisLen/4, 12); hitRect.setLocation(l+axisLen/2-hitRect.width/2, plotDim.height-b-hitRect.height/2); if(hitRect.contains(p)) { return HORZ_AXIS; } hitRect.setLocation(l+4, plotDim.height-b-hitRect.height/2); if(hitRect.contains(p)) { return HORZ_AXIS_MIN; } hitRect.setLocation(l+axisLen-hitRect.width-4, plotDim.height-b-hitRect.height/2); if(hitRect.contains(p)) { return HORZ_AXIS_MAX; } // vertical axis axisLen = plotDim.height-t-b; hitRect.setSize(12, axisLen/4); hitRect.setLocation(l-hitRect.width/2, t+axisLen/2-hitRect.height/2); if(hitRect.contains(p)) { return VERT_AXIS; } hitRect.setLocation(l-hitRect.width/2, t+4); if(hitRect.contains(p)) { return VERT_AXIS_MAX; } hitRect.setLocation(l-hitRect.width/2, t+axisLen-hitRect.height-4); if(hitRect.contains(p)) { return VERT_AXIS_MIN; } // horizontal variable Graphics g = drawingPanel.getGraphics(); int w = xLine.getWidth(g)+8; int h = xLine.getHeight(g); hitRect.setSize(w, h); int x = (int) (xLine.getX()-w/2); int y = (int) (xLine.getY()-h/2-xLine.getFontSize()/3); hitRect.setLocation(x, y); if(hitRect.contains(p)&&hasHorzVariablesPopup()) { return HORZ_VAR; } // vertical variable: drawn sideways, so width<->height reversed w = yLine.getHeight(g); h = yLine.getWidth(g)+8; hitRect.setSize(w, h); x = (int) (yLine.getX()-w/2-yLine.getFontSize()/3); y = (int) (yLine.getY()-h/2-1); hitRect.setLocation(x, y); if(hitRect.contains(p)&&hasVertVariablesPopup()) { return VERT_VAR; } // inside if(!((p.x<l)||(p.y<t)||(p.x>plotDim.width-r)||(p.y>plotDim.height-b))) { return INSIDE; } // scale setter regions ScientificField field = scaleSetter.scaleField; Dimension fieldDim = field.getPreferredSize(); hitRect.setSize(fieldDim); double xmin = drawingPanel.getXMin(); double xmax = drawingPanel.getXMax(); double ymin = drawingPanel.getYMin(); double ymax = drawingPanel.getYMax(); int offset = 8; // approx distance from axis to hitRect for scale setter // horizontal min hitRect.setLocation(l-12, plotDim.height-b+6+offset); if(hitRect.contains(p)) { Point hitLoc = hitRect.getLocation(); // relative to plotPanel scaleSetter.add(scaleSetter.autoscaleCheckbox, BorderLayout.NORTH); scaleSetter.validate(); Point fieldLoc = field.getLocation(); // relative to scaleSetter Dimension size = scaleSetter.getPreferredSize(); scaleSetter.setBounds(hitLoc.x-fieldLoc.x, hitLoc.y-fieldLoc.y-offset, size.width, size.height); return HORZ_MIN; } // horizontal max hitRect.setLocation(plotDim.width-r-fieldDim.width+12, plotDim.height-b+6+offset); if(hitRect.contains(p)) { field.setExpectedRange(xmin, xmax); Point hitLoc = hitRect.getLocation(); // relative to plotPanel scaleSetter.add(scaleSetter.autoscaleCheckbox, BorderLayout.NORTH); scaleSetter.validate(); Point fieldLoc = field.getLocation(); // relative to scaleSetter Dimension size = scaleSetter.getPreferredSize(); scaleSetter.setBounds(hitLoc.x-fieldLoc.x, hitLoc.y-fieldLoc.y-offset, size.width, size.height); return HORZ_MAX; } // vertical min hitRect.setLocation(l-fieldDim.width-1-offset, plotDim.height-b-fieldDim.height+8); if(hitRect.contains(p)) { field.setExpectedRange(ymin, ymax); Point hitLoc = hitRect.getLocation(); // relative to plotPanel scaleSetter.add(scaleSetter.autoscaleCheckbox, BorderLayout.EAST); scaleSetter.validate(); Point fieldLoc = field.getLocation(); // relative to scaleSetter int minLoc = hitLoc.x-fieldLoc.x; Dimension size = scaleSetter.getPreferredSize(); scaleSetter.setBounds(Math.max(minLoc, 1-fieldLoc.x), hitLoc.y-fieldLoc.y, size.width, size.height); return VERT_MIN; } // vertical max hitRect.setLocation(l-fieldDim.width-1-offset, t-8); if(hitRect.contains(p)) { field.setExpectedRange(ymin, ymax); Point hitLoc = hitRect.getLocation(); // relative to plotPanel scaleSetter.add(scaleSetter.autoscaleCheckbox, BorderLayout.EAST); scaleSetter.validate(); Point fieldLoc = field.getLocation(); // relative to scaleSetter int minLoc = hitLoc.x-fieldLoc.x; Dimension size = scaleSetter.getPreferredSize(); scaleSetter.setBounds(Math.max(minLoc, 1-fieldLoc.x), hitLoc.y-fieldLoc.y, size.width, size.height); return VERT_MAX; } return -1; } /** * Gets the scale setter. * * @return the ScaleSetter dialog */ public ScaleSetter getScaleSetter() { // refresh autoscale checkbox text if needed (eg, new Locale) String s = DialogsRes.SCALE_AUTO; if(!s.equals(scaleSetter.autoscaleCheckbox.getText())) { scaleSetter.autoscaleCheckbox.setText(s); } return scaleSetter; } /** * A dialog with value field and autoscale checkbox. */ public class ScaleSetter extends JPanel { Action scaleAction; JCheckBox autoscaleCheckbox; ScientificField scaleField; int region; // determines which axis and end are active boolean pinned = false; // prevents hiding this when true private ScaleSetter() { super(new BorderLayout()); scaleAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { scaleField.setBackground(Color.white); pinned = false; boolean auto = autoscaleCheckbox.isSelected(); boolean horzAxis = true; double min = auto ? Double.NaN : scaleField.getValue(); double max = min; switch(region) { case HORZ_MIN : max = drawingPanel.isAutoscaleXMax()? Double.NaN: drawingPanel.getXMax(); break; case HORZ_MAX : min = drawingPanel.isAutoscaleXMin()? Double.NaN: drawingPanel.getXMin(); break; case VERT_MIN : horzAxis = false; max = drawingPanel.isAutoscaleYMax()? Double.NaN: drawingPanel.getYMax(); break; case VERT_MAX : horzAxis = false; min = drawingPanel.isAutoscaleYMin()? Double.NaN: drawingPanel.getYMin(); } if(horzAxis) { drawingPanel.setPreferredMinMaxX(min, max); } else { drawingPanel.setPreferredMinMaxY(min, max); } Rectangle bounds = drawingPanel.getBounds(); bounds.setLocation(0, 0); drawingPanel.paintImmediately(bounds); } }; autoscaleCheckbox = new JCheckBox(); autoscaleCheckbox.setBorder(BorderFactory.createEmptyBorder(1, 2, 2, 1)); autoscaleCheckbox.setBackground(drawingPanel.getBackground()); autoscaleCheckbox.setHorizontalTextPosition(SwingConstants.RIGHT); autoscaleCheckbox.addActionListener(scaleAction); scaleField = new ScientificField(6, 3) { public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); dim.width -= 4; return dim; } }; scaleField.addActionListener(new AbstractAction() { public void actionPerformed(ActionEvent e) { autoscaleCheckbox.setSelected(false); scaleAction.actionPerformed(null); } }); scaleField.addFocusListener(new FocusAdapter() { public void focusLost(FocusEvent e) { if(scaleField.getBackground()==Color.yellow) { autoscaleCheckbox.setSelected(false); scaleAction.actionPerformed(null); } } }); scaleField.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { pinned = true; if(e.getClickCount()==2) { scaleField.selectAll(); } } }); add(scaleField, BorderLayout.CENTER); } void hideIfInactive() { if((scaleField.getBackground()!=Color.yellow)&&(scaleField.getSelectedText()==null)&&!pinned) { hideScaleSetter(); } } void setRegion(int mouseRegion) { if(region!=mouseRegion) { autoscaleCheckbox.requestFocusInWindow(); if(scaleField.getBackground()==Color.yellow) { autoscaleCheckbox.setSelected(false); scaleAction.actionPerformed(null); } region = mouseRegion; pinned = false; scaleField.select(20, 20); // clears selection and places caret at end scaleField.requestFocusInWindow(); } } } /** * A mouse listener for handling interactivity */ class AxisMouseListener extends javax.swing.event.MouseInputAdapter { public void mouseMoved(MouseEvent e) { if (!enabled) return; // Paco altDown = e.isAltDown(); Point p = e.getPoint(); mouseRegion = findRegion(p); if((mouseRegion>INSIDE)&&(mouseRegion<HORZ_AXIS)&&!drawingPanel.isFixedScale()) { getScaleSetter().setRegion(mouseRegion); scaleSetter.validate(); scaleSetter.setVisible(true); } else { scaleSetter.hideIfInactive(); } drawHitRect = ((mouseRegion==HORZ_VAR)||(mouseRegion==VERT_VAR)); plot.repaint(); } public void mouseDragged(MouseEvent e) { if (!enabled) return; // Paco double dx = 0, dy = 0, min = 0, max = 0; switch(mouseRegion) { case INSIDE : if(!altDown||drawingPanel.isFixedScale()) { return; } dx = (mouseLoc.x-e.getX())/plot.getXPixPerUnit(); min = plot.getXMin()+dx; max = plot.getXMax()+dx; dx = 0; plot.setPreferredMinMaxX(min, max); dy = (e.getY()-mouseLoc.y)/plot.getYPixPerUnit(); min = plot.getYMin()+dy; max = plot.getYMax()+dy; break; case HORZ_AXIS : dx = (mouseLoc.x-e.getX())/plot.getXPixPerUnit(); min = plot.getXMin()+dx; max = plot.getXMax()+dx; break; case HORZ_AXIS_MIN : dx = 2*(mouseLoc.x-e.getX())/plot.getXPixPerUnit(); min = plot.getXMin()+dx; max = plot.isAutoscaleXMax() ? Double.NaN : plot.getXMax(); break; case HORZ_AXIS_MAX : dx = 2*(mouseLoc.x-e.getX())/plot.getXPixPerUnit(); min = plot.isAutoscaleXMin() ? Double.NaN : plot.getXMin(); max = plot.getXMax()+dx; break; case VERT_AXIS : dy = (e.getY()-mouseLoc.y)/plot.getYPixPerUnit(); min = plot.getYMin()+dy; max = plot.getYMax()+dy; break; case VERT_AXIS_MIN : dy = 2*(e.getY()-mouseLoc.y)/plot.getYPixPerUnit(); min = plot.getYMin()+dy; max = plot.isAutoscaleYMax() ? Double.NaN : plot.getYMax(); break; case VERT_AXIS_MAX : dy = 2*(e.getY()-mouseLoc.y)/plot.getYPixPerUnit(); min = plot.isAutoscaleYMin() ? Double.NaN : plot.getYMin(); max = plot.getYMax()+dy; break; } if(dx!=0) { plot.setPreferredMinMaxX(min, max); } else if(dy!=0) { plot.setPreferredMinMaxY(min, max); } // Call any registered listener: Paco for (ActionListener listener : axisListeners) listener.actionPerformed(new ActionEvent(CartesianInteractive.this, e.getID(), "axis dragged")); //$NON-NLS-1$ plot.invalidateImage(); plot.repaint(); mouseLoc = e.getPoint(); } public void mousePressed(MouseEvent e) { if (!enabled) return; // Paco plot.requestFocusInWindow(); altDown = e.isAltDown(); mouseLoc = e.getPoint(); mouseX = plot.pixToX(plot.getMouseIntX()); mouseY = plot.pixToY(plot.getMouseIntY()); mouseRegion = findRegion(mouseLoc); if(scaleSetter==null) { return; } if((mouseRegion>INSIDE)&&(mouseRegion<HORZ_AXIS)&&!drawingPanel.isFixedScale()) { scaleSetter.setVisible(true); return; } hideScaleSetter(); if(mouseRegion==HORZ_VAR) { drawHitRect = false; getHorzVariablesPopup().show(plot, mouseLoc.x-20, mouseLoc.y-12); } else if(mouseRegion==VERT_VAR) { drawHitRect = false; getVertVariablesPopup().show(plot, mouseLoc.x-20, mouseLoc.y-12); } plot.repaint(); } public void mouseReleased(MouseEvent e) { if (!enabled) return; // Paco mouseX = Double.NaN; mouseY = Double.NaN; } public void mouseExited(MouseEvent e) { if (!enabled) return; // Paco Point p = e.getPoint(); if(!new Rectangle(plot.getSize()).contains(p)&&(scaleSetter!=null)&&"".equals(InputEvent.getModifiersExText(e.getModifiersEx()))) { //$NON-NLS-1$ hideScaleSetter(); } } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */