Mega Code Archive

 
Categories / Java / Advanced Graphics
 

Spline Editor

/**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ import java.awt.*; import java.awt.datatransfer.*; import java.awt.event.*; import java.awt.geom.*; import java.beans.*; import java.io.*; import java.text.*; import java.util.*; import javax.imageio.*; import javax.swing.*; import javax.swing.event.*; import java.awt.image.*; import org.jdesktop.animation.timing.interpolation.*; import org.jdesktop.animation.timing.*; import org.jdesktop.animation.timing.Animator.*; import org.jdesktop.animation.timing.interpolation.*; import java.awt.image.*; import javax.swing.border.*; import java.net.*; public class SplineEditor extends JFrame {     public SplineEditor() throws HeadlessException {         super("Spline Editor");         add(buildHeader(), BorderLayout.NORTH);         add(buildControlPanel(), BorderLayout.CENTER);         pack();         setLocationRelativeTo(null);         setResizable(false);         setDefaultCloseOperation(EXIT_ON_CLOSE);     }     private Component buildHeader() {         ImageIcon icon = new ImageIcon(getClass().getResource("simulator.png"));         HeaderPanel header = new HeaderPanel(icon,                                              "Timing Framework Spline Editor",                                              "Drag control points in the display to change the shape of the spline.",                                              "Click the Copy Code button to generate the corresponding Java code.");         return header;     }     private Component buildControlPanel() {         return new SplineControlPanel();     }          public static void main(String[] args) {         SwingUtilities.invokeLater(new Runnable() {             public void run() {                 try {                     UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());                 } catch (ClassNotFoundException e) {                 } catch (InstantiationException e) {                 } catch (IllegalAccessException e) {                 } catch (UnsupportedLookAndFeelException e) {                 }                                  new SplineEditor().setVisible(true);             }         });     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class EquationDisplay extends JComponent implements PropertyChangeListener {     private static final Color COLOR_BACKGROUND = Color.WHITE;     private static final Color COLOR_MAJOR_GRID = Color.GRAY.brighter();     private static final Color COLOR_MINOR_GRID = new Color(220, 220, 220);     private static final Color COLOR_AXIS = Color.BLACK;          private static final float STROKE_AXIS = 1.2f;     private static final float STROKE_GRID = 1.0f;          private static final float COEFF_ZOOM = 1.1f;          private java.util.List<DrawableEquation> equations;     protected double minX;     protected double maxX;     protected double minY;     protected double maxY;     private double originX;     private double originY;     private double majorX;     private int minorX;     private double majorY;     private int minorY;          private boolean drawText = true;          private Point dragStart;          private NumberFormat formatter;     private ZoomHandler zoomHandler;     private PanMotionHandler panMotionHandler;     private PanHandler panHandler;     public EquationDisplay(double originX, double originY,                            double minX, double maxX,                            double minY, double maxY,                            double majorX, int minorX,                            double majorY, int minorY) {         if (minX >= maxX) {             throw new IllegalArgumentException("minX must be < to maxX");         }                  if (originX < minX || originX > maxX) {             throw new IllegalArgumentException("originX must be between minX and maxX");         }         if (minY >= maxY) {             throw new IllegalArgumentException("minY must be < to maxY");         }                  if (originY < minY || originY > maxY) {             throw new IllegalArgumentException("originY must be between minY and maxY");         }                  if (minorX <= 0) {             throw new IllegalArgumentException("minorX must be > 0");         }                  if (minorY <= 0) {             throw new IllegalArgumentException("minorY must be > 0");         }                  if (majorX <= 0.0) {             throw new IllegalArgumentException("majorX must be > 0.0");         }                  if (majorY <= 0.0) {             throw new IllegalArgumentException("majorY must be > 0.0");         }                  this.originX = originX;         this.originY = originY;         this.minX = minX;         this.maxX = maxX;         this.minY = minY;         this.maxY = maxY;                  this.majorX = majorX;         this.minorX = minorX;         this.majorY = majorY;         this.minorY = minorY;                  this.equations = new LinkedList<DrawableEquation>();                  this.formatter = NumberFormat.getInstance();         this.formatter.setMaximumFractionDigits(2);                  panHandler = new PanHandler();         addMouseListener(panHandler);         panMotionHandler = new PanMotionHandler();         addMouseMotionListener(panMotionHandler);         zoomHandler = new ZoomHandler();         addMouseWheelListener(zoomHandler);     }          @Override     public void setEnabled(boolean enabled) {         if (isEnabled() != enabled) {             //super.setEnabled(enabled);             if (enabled) {                 addMouseListener(panHandler);                 addMouseMotionListener(panMotionHandler);                 addMouseWheelListener(zoomHandler);             } else {                 removeMouseListener(panHandler);                 removeMouseMotionListener(panMotionHandler);                 removeMouseWheelListener(zoomHandler);             }         }     }          public boolean isDrawText() {         return drawText;     }     public void setDrawText(boolean drawText) {         this.drawText = drawText;     }     public void addEquation(AbstractEquation equation, Color color) {         if (equation != null && !equations.contains(equation)) {             equation.addPropertyChangeListener(this);             equations.add(new DrawableEquation(equation, color));             repaint();         }     }          public void removeEquation(AbstractEquation equation) {         if (equation != null) {             DrawableEquation toRemove = null;             for (DrawableEquation drawable: equations) {                 if (drawable.getEquation() == equation) {                     toRemove = drawable;                     break;                 }             }                          if (toRemove != null) {                 equation.removePropertyChangeListener(this);                 equations.remove(toRemove);                 repaint();             }         }     }     @Override     public Dimension getPreferredSize() {         return new Dimension(400, 400);     }     public void propertyChange(PropertyChangeEvent evt) {         repaint();     }          protected double yPositionToPixel(double position) {         double height = (double) getHeight();         return height - ((position - minY) * height / (maxY - minY));     }     protected double xPositionToPixel(double position) {         return (position - minX) * (double) getWidth() / (maxX - minX);     }          protected double xPixelToPosition(double pixel) {         double axisV = xPositionToPixel(originX);         return (pixel - axisV) * (maxX - minX) / (double) getWidth();     }          protected double yPixelToPosition(double pixel) {         double axisH = yPositionToPixel(originY);         return (getHeight() - pixel - axisH) * (maxY - minY) / (double) getHeight();     }     @Override     protected void paintComponent(Graphics g) {         if (!isVisible()) {             return;         }                  Graphics2D g2 = (Graphics2D) g;         setupGraphics(g2);         paintBackground(g2);         drawGrid(g2);         drawAxis(g2);                  drawEquations(g2);                  paintInformation(g2);     }          protected void paintInformation(Graphics2D g2) {     }     private void drawEquations(Graphics2D g2) {         for (DrawableEquation drawable: equations) {             g2.setColor(drawable.getColor());             drawEquation(g2, drawable.getEquation());         }     }     private void drawEquation(Graphics2D g2, AbstractEquation equation) {         float x = 0.0f;         float y = (float) yPositionToPixel(equation.compute(xPixelToPosition(0.0)));                  GeneralPath path = new GeneralPath();         path.moveTo(x, y);                  for (x = 0.0f; x < getWidth(); x += 1.0f) {             double position = xPixelToPosition(x);             y = (float) yPositionToPixel(equation.compute(position));             path.lineTo(x, y);         }                  g2.draw(path);     }     private void drawGrid(Graphics2D g2) {         Stroke stroke = g2.getStroke();         drawVerticalGrid(g2);         drawHorizontalGrid(g2);         if (drawText) {             drawVerticalLabels(g2);             drawHorizontalLabels(g2);         }                  g2.setStroke(stroke);     }          private void drawHorizontalLabels(Graphics2D g2) {         double axisV = xPositionToPixel(originX);         g2.setColor(COLOR_AXIS);         for (double y = originY + majorY; y < maxY + majorY; y += majorY) {             int position = (int) yPositionToPixel(y);             g2.drawString(formatter.format(y), (int) axisV + 5, position);         }                  for (double y = originY - majorY; y > minY - majorY; y -= majorY) {             int position = (int) yPositionToPixel(y);             g2.drawString(formatter.format(y), (int) axisV + 5, position);         }     }          private void drawHorizontalGrid(Graphics2D g2) {         double minorSpacing = majorY / minorY;         double axisV = xPositionToPixel(originX);                  Stroke gridStroke = new BasicStroke(STROKE_GRID);         Stroke axisStroke = new BasicStroke(STROKE_AXIS);                  for (double y = originY + majorY; y < maxY + majorY; y += majorY) {             g2.setStroke(gridStroke);             g2.setColor(COLOR_MINOR_GRID);             for (int i = 0; i < minorY; i++) {                 int position = (int) yPositionToPixel(y - i * minorSpacing);                 g2.drawLine(0, position, getWidth(), position);                 }             int position = (int) yPositionToPixel(y);             g2.setColor(COLOR_MAJOR_GRID);             g2.drawLine(0, position, getWidth(), position);                          g2.setStroke(axisStroke);             g2.setColor(COLOR_AXIS);             g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);         }         for (double y = originY - majorY; y > minY - majorY; y -= majorY) {             g2.setStroke(gridStroke);             g2.setColor(COLOR_MINOR_GRID);             for (int i = 0; i < minorY; i++) {                 int position = (int) yPositionToPixel(y + i * minorSpacing);                 g2.drawLine(0, position, getWidth(), position);                 }             int position = (int) yPositionToPixel(y);             g2.setColor(COLOR_MAJOR_GRID);             g2.drawLine(0, position, getWidth(), position);                          g2.setStroke(axisStroke);             g2.setColor(COLOR_AXIS);             g2.drawLine((int) axisV - 3, position, (int) axisV + 3, position);         }     }     private void drawVerticalLabels(Graphics2D g2) {         double axisH = yPositionToPixel(originY);         FontMetrics metrics = g2.getFontMetrics();                  g2.setColor(COLOR_AXIS);         for (double x = originX + majorX; x < maxX + majorX; x += majorX) {             int position = (int) xPositionToPixel(x);             g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());         }         for (double x = originX - majorX; x > minX - majorX; x -= majorX) {             int position = (int) xPositionToPixel(x);             g2.drawString(formatter.format(x), position, (int) axisH + metrics.getHeight());         }     }          private void drawVerticalGrid(Graphics2D g2) {         double minorSpacing = majorX / minorX;         double axisH = yPositionToPixel(originY);                  Stroke gridStroke = new BasicStroke(STROKE_GRID);         Stroke axisStroke = new BasicStroke(STROKE_AXIS);                  for (double x = originX + majorX; x < maxX + majorX; x += majorX) {             g2.setStroke(gridStroke);             g2.setColor(COLOR_MINOR_GRID);             for (int i = 0; i < minorX; i++) {                 int position = (int) xPositionToPixel(x - i * minorSpacing);                 g2.drawLine(position, 0, position, getHeight());                 }             int position = (int) xPositionToPixel(x);             g2.setColor(COLOR_MAJOR_GRID);             g2.drawLine(position, 0, position, getHeight());             g2.setStroke(axisStroke);             g2.setColor(COLOR_AXIS);             g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);         }         for (double x = originX - majorX; x > minX - majorX; x -= majorX) {             g2.setStroke(gridStroke);             g2.setColor(COLOR_MINOR_GRID);             for (int i = 0; i < minorX; i++) {                 int position = (int) xPositionToPixel(x + i * minorSpacing);                 g2.drawLine(position, 0, position, getHeight());                 }             int position = (int) xPositionToPixel(x);             g2.setColor(COLOR_MAJOR_GRID);             g2.drawLine(position, 0, position, getHeight());                          g2.setStroke(axisStroke);             g2.setColor(COLOR_AXIS);             g2.drawLine(position, (int) axisH - 3, position, (int) axisH + 3);         }     }     private void drawAxis(Graphics2D g2) {         double axisH = yPositionToPixel(originY);         double axisV = xPositionToPixel(originX);                  g2.setColor(COLOR_AXIS);         Stroke stroke = g2.getStroke();         g2.setStroke(new BasicStroke(STROKE_AXIS));                  g2.drawLine(0, (int) axisH, getWidth(), (int) axisH);         g2.drawLine((int) axisV, 0, (int) axisV, getHeight());                  FontMetrics metrics = g2.getFontMetrics();         g2.drawString(formatter.format(0.0), (int) axisV + 5, (int) axisH + metrics.getHeight());                  g2.setStroke(stroke);     }     protected void setupGraphics(Graphics2D g2) {         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                             RenderingHints.VALUE_ANTIALIAS_ON);     }     protected void paintBackground(Graphics2D g2) {         g2.setColor(COLOR_BACKGROUND);         g2.fill(g2.getClipBounds());     }     private class DrawableEquation {         private AbstractEquation equation;         private Color color;         DrawableEquation(AbstractEquation equation, Color color) {             this.equation = equation;             this.color = color;         }                  AbstractEquation getEquation() {             return equation;         }                  Color getColor() {             return color;         }     }          private class ZoomHandler implements MouseWheelListener {         public void mouseWheelMoved(MouseWheelEvent e) {             double distanceX = maxX - minX;             double distanceY = maxY - minY;                          double cursorX = minX + distanceX / 2.0;             double cursorY = minY + distanceY / 2.0;                          int rotation = e.getWheelRotation();             if (rotation < 0) {                 distanceX /= COEFF_ZOOM;                 distanceY /= COEFF_ZOOM;             } else {                 distanceX *= COEFF_ZOOM;                 distanceY *= COEFF_ZOOM;             }                          minX = cursorX - distanceX / 2.0;             maxX = cursorX + distanceX / 2.0;             minY = cursorY - distanceY / 2.0;             maxY = cursorY + distanceY / 2.0;             repaint();         }     }          private class PanHandler extends MouseAdapter {         @Override         public void mousePressed(MouseEvent e) {             dragStart = e.getPoint();         }     }     private class PanMotionHandler extends MouseMotionAdapter {         @Override         public void mouseDragged(MouseEvent e) {             Point dragEnd = e.getPoint();             double distance = xPixelToPosition(dragEnd.getX()) -                               xPixelToPosition(dragStart.getX());             minX -= distance;             maxX -= distance;             distance = yPixelToPosition(dragEnd.getY()) -                        yPixelToPosition(dragStart.getY());             minY -= distance;             maxY -= distance;                          repaint();             dragStart = dragEnd;         }     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ interface Equation {     public double compute(double variable); } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ abstract class AbstractEquation implements Equation {     protected java.util.List<PropertyChangeListener> listeners;     protected AbstractEquation() {         this.listeners = new LinkedList<PropertyChangeListener>();     }          public void addPropertyChangeListener(PropertyChangeListener listener) {         if (listener != null && !listeners.contains(listener)) {             listeners.add(listener);         }     }          public void removePropertyChangeListener(PropertyChangeListener listener) {         if (listener != null) {             listeners.remove(listener);         }     }          protected void firePropertyChange(String propertyName,                                       double oldValue,                                       double newValue) {         PropertyChangeEvent changeEvent = new PropertyChangeEvent(this,                                                                   propertyName,                                                                   oldValue,                                                                   newValue);         for (PropertyChangeListener listener: listeners) {             listener.propertyChange(changeEvent);         }     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class AbstractSimulator extends JComponent {     protected double time;          public AbstractSimulator() {         this.time = 0.0f;     }     public void setTime(double time) {         this.time = time;         repaint();     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class BouncerSimulator extends AbstractSimulator {     private static final Color COLOR_BACKGROUND = Color.WHITE;     private BufferedImage image;     public BouncerSimulator() {         try {             image = ImageIO.read(BouncerSimulator.class.getResource("item.png"));         } catch (Exception e) { }     }     @Override     protected void paintComponent(Graphics g) {         if (!isVisible()) {             return;         }                  Graphics2D g2 = (Graphics2D) g;         setupGraphics(g2);         drawBackground(g2);         drawItem(g2);     }     private void drawItem(Graphics2D g2) {         double position = time;         double xPos = position * getWidth() / 2;         int width = getWidth() * 2 / 3;         int x = (getWidth() - width) / 2;         x += xPos;         int y = getHeight() / 2;         y -= image.getHeight() / 2;         g2.drawImage(image, null, x, y);     }     private void setupGraphics(Graphics2D g2) {         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                             RenderingHints.VALUE_ANTIALIAS_ON);     }     private void drawBackground(Graphics2D g2) {         g2.setColor(COLOR_BACKGROUND);         g2.fill(g2.getClipBounds());     }              @Override     public Dimension getPreferredSize() {         return new Dimension(150, 100);     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class DropSimulator extends AbstractSimulator {     private static final Color COLOR_BACKGROUND = Color.WHITE;     private BufferedImage image;     private BufferedImage shadow;          private float angle = 90;     private int distance = 20;     // cached values for fast painting     private int distance_x = 0;     private int distance_y = 0;          public DropSimulator() {         try {             image = ImageIO.read(BouncerSimulator.class.getResource("icon.png"));             ShadowFactory factory = new ShadowFactory(5, 0.5f, Color.BLACK);             shadow = factory.createShadow(image);         } catch (Exception e) { }     }     @Override     protected void paintComponent(Graphics g) {         if (!isVisible()) {             return;         }                  Graphics2D g2 = (Graphics2D) g;         setupGraphics(g2);         drawBackground(g2);         drawItem(g2);     }     private void drawItem(Graphics2D g2) {         double position = time;         int width = (int) (shadow.getWidth() / 2 * (1.0 + position));         int height = (int) (shadow.getHeight() / 2 * (1.0 + position));         int x = (getWidth() - width) / 2;         int y = (getHeight() - height) / 2;         Composite composite = g2.getComposite();         g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,                                                    1.0f - (0.5f * (float) position)));         computeShadowPosition((position * distance) + 1.0);         g2.drawImage(shadow, x + distance_x, y + distance_y, width, height, null);                  g2.setComposite(composite);                  width = (int) (image.getWidth() / 2 * (1.0 + position));         height = (int) (image.getHeight() / 2 * (1.0 + position));         x = (getWidth() - width) / 2;         y = (getHeight() - height) / 2;         g2.drawImage(image, x, y, width, height, null);     }     private void setupGraphics(Graphics2D g2) {         g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,                             RenderingHints.VALUE_ANTIALIAS_ON);         g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,                             RenderingHints.VALUE_INTERPOLATION_BICUBIC);     }     private void drawBackground(Graphics2D g2) {         g2.setColor(COLOR_BACKGROUND);         g2.fill(g2.getClipBounds());     }     @Override     public Dimension getPreferredSize() {         return new Dimension(150, 100);     }     private void computeShadowPosition(double distance) {         double angleRadians = Math.toRadians(angle);         distance_x = (int) (Math.cos(angleRadians) * distance);         distance_y = (int) (Math.sin(angleRadians) * distance);     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class HeaderPanel extends JPanel {     private ImageIcon icon;     HeaderPanel(ImageIcon icon,                 String title,                 String help1,                 String help2) {         super(new BorderLayout());         this.icon = icon;         JPanel titlesPanel = new JPanel(new GridLayout(3, 1));         titlesPanel.setOpaque(false);         titlesPanel.setBorder(new EmptyBorder(12, 0, 12, 0));         JLabel headerTitle = new JLabel(title);         Font police = headerTitle.getFont().deriveFont(Font.BOLD);         headerTitle.setFont(police);         headerTitle.setBorder(new EmptyBorder(0, 12, 0, 0));         titlesPanel.add(headerTitle);         JLabel message;         titlesPanel.add(message = new JLabel(help1));         police = headerTitle.getFont().deriveFont(Font.PLAIN);         message.setFont(police);         message.setBorder(new EmptyBorder(0, 24, 0, 0));         titlesPanel.add(message = new JLabel(help2));         police = headerTitle.getFont().deriveFont(Font.PLAIN);         message.setFont(police);         message.setBorder(new EmptyBorder(0, 24, 0, 0));         message = new JLabel(this.icon);         message.setBorder(new EmptyBorder(0, 0, 0, 12));         add(BorderLayout.WEST, titlesPanel);         add(BorderLayout.EAST, message);         add(BorderLayout.SOUTH, new JSeparator(JSeparator.HORIZONTAL));         setPreferredSize(new Dimension(500, this.icon.getIconHeight() + 24));     }     public void paintComponent(Graphics g) {         super.paintComponent(g);         if (!isOpaque()) {             return;         }                  Rectangle bounds = g.getClipBounds();         Color control = UIManager.getColor("control");         int width = getWidth();         Graphics2D g2 = (Graphics2D) g;         Paint storedPaint = g2.getPaint();         g2.setPaint(new GradientPaint(this.icon.getIconWidth(), 0, Color.white, width, 0, control));         g2.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);         g2.setPaint(storedPaint);     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class Java2dHelper {     public static BufferedImage createCompatibleImage(int width, int height) {         GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();         GraphicsDevice screenDevice = environment.getDefaultScreenDevice();         GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();         return configuration.createCompatibleImage(width, height);     }          public static BufferedImage loadCompatibleImage(URL resource) throws IOException {         BufferedImage image = ImageIO.read(resource);         BufferedImage compatibleImage = createCompatibleImage(image.getWidth(), image.getHeight());         Graphics g = compatibleImage.getGraphics();         g.drawImage(image, 0, 0, null);         g.dispose();         image = null;         return compatibleImage;     }          public static BufferedImage createThumbnail(BufferedImage image, int requestedThumbSize) {         float ratio = (float) image.getWidth() / (float) image.getHeight();         int width = image.getWidth();         BufferedImage thumb = image;                  do {             width /= 2;             if (width < requestedThumbSize) {                 width = requestedThumbSize;             }                          BufferedImage temp = new BufferedImage(width, (int) (width / ratio), BufferedImage.TYPE_INT_ARGB);             Graphics2D g2 = temp.createGraphics();             g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,                                 RenderingHints.VALUE_INTERPOLATION_BILINEAR);             g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);             g2.dispose();             thumb = temp;         } while (width != requestedThumbSize);                  return thumb;     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ /**  * <p>A shadow factory generates a drop shadow for any given picture, respecting  * the transparency channel if present. The resulting picture contains the  * shadow only and to create a drop shadow effect you will need to stack the  * original picture and the shadow generated by the factory. If you are using  * Swing you can get this done very easily with the layout  * {@link org.jdesktop.swingx.StackLayout}.</p>  * <h2>Shadow Properties</h2>  * <p>A shadow is defined by three properties:  * <ul>  *   <li><i>size</i>: The size, in pixels, of the shadow. This property also  *   defines the fuzzyness.</li>  *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>  *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be  *   black only.</li>  * </ul>  * You can set these properties using the provided mutaters or the appropriate  * constructor. Here are two ways of creating a green shadow of size 10 and  * with an opacity of 50%:  * <pre>  * ShadowFactory factory = new ShadowFactory(10, 0.5f, Color.GREEN);  * // ..  * factory = new ShadowFactory();  * factory.setSize(10);  * factory.setOpacity(0.5f);  * factory.setColor(Color.GREEN);  * </pre>  * The default constructor provides the following default values:  * <ul>  *   <li><i>size</i>: 5 pixels</li>  *   <li><i>opacity</i>: 50%</li>  *   <li><i>color</i>: Black</li>  * </ul></p>  * <h2>Shadow Quality</h2>  * <p>The factory provides two shadow generation algorithms: <i>fast quality blur</i>  * and <i>high quality blur</i>. You can select your preferred algorithm by  * setting the appropriate rendering hint:  * <pre>  * ShadowFactory factory = new ShadowFactory();  * factory.setRenderingHint(ShadowFactory.KEY_BLUR_QUALITY,  *                          ShadowFactory.VALUE_BLUR_QUALITY_HIGH);  * </pre>  * The default rendering algorihtm is <code>VALUE_BLUR_QUALITY_FAST</code>.</p>  * <p>The current implementation should provide the same quality with both  * algorithms but performances are guaranteed to be better (about 30 times  * faster) with the <i>fast quality blur</i>.</p>  * <h2>Generating a Shadow</h2>  * <p>A shadow is generated as a <code>BufferedImage</code> from another  * <code>BufferedImage</code>. Once the factory is set up, you must call  * {@link #createShadow} to actually generate the shadow:  * <pre>  * ShadowFactory factory = new ShadowFactory();  * // factory setup  * BufferedImage shadow = factory.createShadow(bufferedImage);   * </pre>  * The resulting image is of type <code>BufferedImage.TYPE_INT_ARGB</code>.  * Both dimensions of this image are larger than original image's:  * <ul>  *   <li>new width = original width + 2 * shadow size</li>  *   <li>new height = original height + 2 * shadow size</li>  * </ul>  * This must be taken into account when you need to create a drop shadow effect.</p>  * <h2>Properties Changes</h2>  * <p>This factory allows to register property change listeners with  * {@link #addPropertyChangeListener}. Listening to properties changes is very  * useful when you emebed the factory in a graphical component and give the API  * user the ability to access the factory. By listening to properties changes,  * you can easily repaint the component when needed.</p>  * <h2>Threading Issues</h2>  * <p><code>ShadowFactory</code> is not guaranteed to be thread-safe.</p>  *   * @author Romain Guy <romain.guy@mac.com>  * @author Sébastien Petrucci <sebastien_petrucci@yahoo.fr>  */ class ShadowFactory {     /**      * <p>Key for the blur quality rendering hint.</p>      */     public static final String KEY_BLUR_QUALITY = "blur_quality";     /**      * <p>Selects the fast rendering algorithm. This is the default rendering      * hint for <code>KEY_BLUR_QUALITY</code>.</p>      */     public static final String VALUE_BLUR_QUALITY_FAST = "fast";          /**      * <p>Selects the high quality rendering algorithm. With current implementation,      * This algorithm does not guarantee a better rendering quality and should      * not be used.</p>      */     public static final String VALUE_BLUR_QUALITY_HIGH = "high";     /**      * <p>Identifies a change to the size used to render the shadow.</p>      * <p>When the property change event is fired, the old value and the new      * value are provided as <code>Integer</code> instances.</p>      */     public static final String SIZE_CHANGED_PROPERTY = "shadow_size";          /**      * <p>Identifies a change to the opacity used to render the shadow.</p>      * <p>When the property change event is fired, the old value and the new      * value are provided as <code>Float</code> instances.</p>      */     public static final String OPACITY_CHANGED_PROPERTY = "shadow_opacity";          /**      * <p>Identifies a change to the color used to render the shadow.</p>      */     public static final String COLOR_CHANGED_PROPERTY = "shadow_color";     // size of the shadow in pixels (defines the fuzziness)     private int size = 5;          // opacity of the shadow     private float opacity = 0.5f;          // color of the shadow     private Color color = Color.BLACK;     // rendering hints map     private HashMap<Object, Object> hints;          // notifies listeners of properties changes     private PropertyChangeSupport changeSupport;     /**      * <p>Creates a default good looking shadow generator.      * The default shadow factory provides the following default values:      * <ul>      *   <li><i>size</i>: 5 pixels</li>      *   <li><i>opacity</i>: 50%</li>      *   <li><i>color</i>: Black</li>      *   <li><i>rendering quality</i>: VALUE_BLUR_QUALITY_FAST</li>      * </ul></p>      * <p>These properties provide a regular, good looking shadow.</p>      */     public ShadowFactory() {         this(5, 0.5f, Color.BLACK);     }          /**      * <p>A shadow factory needs three properties to generate shadows.      * These properties are:</p>       * <ul>      *   <li><i>size</i>: The size, in pixels, of the shadow. This property also      *   defines the fuzzyness.</li>      *   <li><i>opacity</i>: The opacity, between 0.0 and 1.0, of the shadow.</li>      *   <li><i>color</i>: The color of the shadow. Shadows are not meant to be      *   black only.</li>      * </ul></p>      * <p>Besides these properties you can set rendering hints to control the      * rendering process. The default rendering hints let the factory use the      * fastest shadow generation algorithm.</p>      * @param size The size of the shadow in pixels. Defines the fuzziness.      * @param opacity The opacity of the shadow.      * @param color The color of the shadow.      * @see #setRenderingHint(Object, Object)      */     public ShadowFactory(final int size, final float opacity, final Color color) {         hints = new HashMap<Object, Object>();         hints.put(KEY_BLUR_QUALITY, VALUE_BLUR_QUALITY_FAST);                  changeSupport = new PropertyChangeSupport(this);         setSize(size);         setOpacity(opacity);         setColor(color);     }     /**      * <p>Add a PropertyChangeListener to the listener list. The listener is      * registered for all properties. The same listener object may be added      * more than once, and will be called as many times as it is added. If      * <code>listener</code> is null, no exception is thrown and no action      * is taken.</p>       * @param listener the PropertyChangeListener to be added      */     public void addPropertyChangeListener(PropertyChangeListener listener) {         changeSupport.addPropertyChangeListener(listener);     }     /**      * <p>Remove a PropertyChangeListener from the listener list. This removes      * a PropertyChangeListener that was registered for all properties. If      * <code>listener</code> was added more than once to the same event source,      * it will be notified one less time after being removed. If      * <code>listener</code> is null, or was never added, no exception is thrown      * and no action is taken.</p>      * @param listener      */     public void removePropertyChangeListener(PropertyChangeListener listener) {         changeSupport.removePropertyChangeListener(listener);     }     /**      * <p>Maps the specified rendering hint <code>key</code> to the specified      * <code>value</code> in this <code>SahdowFactory</code> object.</p>      * @param key The rendering hint key      * @param value The rendering hint value      */     public void setRenderingHint(final Object key, final Object value) {         hints.put(key, value);     }     /**      * <p>Gets the color used by the factory to generate shadows.</p>      * @return this factory's shadow color      */     public Color getColor() {         return color;     }     /**      * <p>Sets the color used by the factory to generate shadows.</p>      * <p>Consecutive calls to {@link #createShadow} will all use this color      * until it is set again.</p>      * <p>If the color provided is null, the previous color will be retained.</p>      * @param shadowColor the generated shadows color      */     public void setColor(final Color shadowColor) {         if (shadowColor != null) {             Color oldColor = this.color;             this.color = shadowColor;             changeSupport.firePropertyChange(COLOR_CHANGED_PROPERTY,                                              oldColor,                                              this.color);         }     }     /**      * <p>Gets the opacity used by the factory to generate shadows.</p>      * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully      * transparent and 1.0f fully opaque.</p>      * @return this factory's shadow opacity      */     public float getOpacity() {         return opacity;     }     /**      * <p>Sets the opacity used by the factory to generate shadows.</p>      * <p>Consecutive calls to {@link #createShadow} will all use this color      * until it is set again.</p>      * <p>The opacity is comprised between 0.0f and 1.0f; 0.0f being fully      * transparent and 1.0f fully opaque. If you provide a value out of these      * boundaries, it will be restrained to the closest boundary.</p>      * @param shadowOpacity the generated shadows opacity      */     public void setOpacity(final float shadowOpacity) {         float oldOpacity = this.opacity;                  if (shadowOpacity < 0.0) {             this.opacity = 0.0f;         } else if (shadowOpacity > 1.0f) {             this.opacity = 1.0f;         } else {             this.opacity = shadowOpacity;         }                  changeSupport.firePropertyChange(OPACITY_CHANGED_PROPERTY,                                          new Float(oldOpacity),                                          new Float(this.opacity));     }     /**      * <p>Gets the size in pixel used by the factory to generate shadows.</p>      * @return this factory's shadow size      */     public int getSize() {         return size;     }     /**      * <p>Sets the size, in pixels, used by the factory to generate shadows.</p>      * <p>The size defines the blur radius applied to the shadow to create the      * fuzziness.</p>      * <p>There is virtually no limit to the size but it has an impact on shadow      * generation performances. The greater this value, the longer it will take       * to generate the shadow. Remember the generated shadow image dimensions       * are computed as follow:      * <ul>      *   <li>new width = original width + 2 * shadow size</li>      *   <li>new height = original height + 2 * shadow size</li>      * </ul>      * The size cannot be negative. If you provide a negative value, the size      * will be 0 instead.</p>      * @param shadowSize the generated shadows size in pixels (fuzziness)      */     public void setSize(final int shadowSize) {         int oldSize = this.size;                  if (shadowSize < 0) {             this.size = 0;         } else {             this.size = shadowSize;         }                  changeSupport.firePropertyChange(SIZE_CHANGED_PROPERTY,                                          new Integer(oldSize),                                          new Integer(this.size));     }     /**      * <p>Generates the shadow for a given picture and the current properties      * of the factory.</p>      * <p>The generated shadow image dimensions are computed as follow:      *  <ul>      *  <li>new width = original width + 2 * shadow size</li>      *  <li>new height = original height + 2 * shadow size</li>      * </ul></p>      * <p>The time taken by a call to this method depends on the size of the      * shadow, the larger the longer it takes, and on the selected rendering      * algorithm.</p>      * @param image the picture from which the shadow must be cast      * @return the picture containing the shadow of <code>image</code>       */     public BufferedImage createShadow(final BufferedImage image) {         if (hints.get(KEY_BLUR_QUALITY) == VALUE_BLUR_QUALITY_HIGH) {             // the high quality algorithm is a 3-pass algorithm             // it goes through all the pixels of the original picture at least             // three times to generate the shadow             // it is easy to understand but very slow             BufferedImage subject = prepareImage(image);             BufferedImage shadow = new BufferedImage(subject.getWidth(),                                                      subject.getHeight(),                                                      BufferedImage.TYPE_INT_ARGB);             BufferedImage shadowMask = createShadowMask(subject);             getLinearBlurOp(size).filter(shadowMask, shadow);             return shadow;         }         // call the fast rendering algorithm         return createShadowFast(image);     }          // prepares the picture for the high quality rendering algorithm     private BufferedImage prepareImage(final BufferedImage image) {         BufferedImage subject = new BufferedImage(image.getWidth() + size * 2,                                                   image.getHeight() + size * 2,                                                   BufferedImage.TYPE_INT_ARGB);         Graphics2D g2 = subject.createGraphics();         g2.drawImage(image, null, size, size);         g2.dispose();         return subject;     }     // fast rendering algorithm     // basically applies duplicates the picture and applies a size*size kernel     // in only one pass.     // the kernel is simulated by an horizontal and a vertical pass     // implemented by Sébastien Petrucci     private BufferedImage createShadowFast(final BufferedImage src) {         int shadowSize = this.size;         int srcWidth = src.getWidth();         int srcHeight = src.getHeight();         int dstWidth = srcWidth + size;         int dstHeight = srcHeight + size;         int left = (shadowSize - 1) >> 1;         int right = shadowSize - left;         int yStop = dstHeight - right;         BufferedImage dst = new BufferedImage(dstWidth, dstHeight,                                               BufferedImage.TYPE_INT_ARGB);         int shadowRgb = color.getRGB() & 0x00FFFFFF;         int[] aHistory = new int[shadowSize];         int historyIdx;         int aSum;         ColorModel srcColorModel = src.getColorModel();         WritableRaster srcRaster = src.getRaster();         int[] dstBuffer = ((DataBufferInt) dst.getRaster().getDataBuffer()).getData();         int lastPixelOffset = right * dstWidth;         float hSumDivider = 1.0f / size;         float vSumDivider = opacity / size;         // horizontal pass : extract the alpha mask from the source picture and         // blur it into the destination picture         for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) {             // first pixels are empty             for (historyIdx = 0; historyIdx < shadowSize; ) {                 aHistory[historyIdx++] = 0;             }             aSum = 0;             historyIdx = 0;             // compute the blur average with pixels from the source image             for (int srcX = 0; srcX < srcWidth; srcX++) {                 int a = (int) (aSum * hSumDivider); // calculate alpha value                 dstBuffer[dstOffset++] = a << 24;   // store the alpha value only                                                     // the shadow color will be added in the next pass                 aSum -= aHistory[historyIdx]; // substract the oldest pixel from the sum                 // extract the new pixel ...                 a = srcColorModel.getAlpha(srcRaster.getDataElements(srcX, srcY, null));                 aHistory[historyIdx] = a;   // ... and store its value into history                 aSum += a;                  // ... and add its value to the sum                 if (++historyIdx >= shadowSize) {                     historyIdx -= shadowSize;                 }             }             // blur the end of the row - no new pixels to grab             for (int i = 0; i < shadowSize; i++) {                 int a = (int) (aSum * hSumDivider);                 dstBuffer[dstOffset++] = a << 24;                 // substract the oldest pixel from the sum ... and nothing new to add !                 aSum -= aHistory[historyIdx];                 if (++historyIdx >= shadowSize) {                     historyIdx -= shadowSize;                 }             }         }         // vertical pass         for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) {             aSum = 0;             // first pixels are empty             for (historyIdx = 0; historyIdx < left;) {                 aHistory[historyIdx++] = 0;             }             // and then they come from the dstBuffer             for (int y = 0; y < right; y++, bufferOffset += dstWidth) {                 int a = dstBuffer[bufferOffset] >>> 24;         // extract alpha                 aHistory[historyIdx++] = a;                     // store into history                 aSum += a;                                      // and add to sum             }             bufferOffset = x;             historyIdx = 0;             // compute the blur average with pixels from the previous pass             for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) {                 int a = (int) (aSum * vSumDivider);             // calculate alpha value                 dstBuffer[bufferOffset] = a << 24 | shadowRgb;  // store alpha value + shadow color                 aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum                 a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24;   // extract the new pixel ...                 aHistory[historyIdx] = a;                               // ... and store its value into history                 aSum += a;                                              // ... and add its value to the sum                 if (++historyIdx >= shadowSize) {                     historyIdx -= shadowSize;                 }             }             // blur the end of the column - no pixels to grab anymore             for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) {                 int a = (int) (aSum * vSumDivider);                 dstBuffer[bufferOffset] = a << 24 | shadowRgb;                 aSum -= aHistory[historyIdx];   // substract the oldest pixel from the sum                 if (++historyIdx >= shadowSize) {                     historyIdx -= shadowSize;                 }             }         }         return dst;     }     // creates the shadow mask for the original picture     // it colorize all the pixels with the shadow color according to their     // original transparency     private BufferedImage createShadowMask(final BufferedImage image) {         BufferedImage mask = new BufferedImage(image.getWidth(),                                                image.getHeight(),                                                BufferedImage.TYPE_INT_ARGB);         Graphics2D g2d = mask.createGraphics();         g2d.drawImage(image, 0, 0, null);         g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,                                                     opacity));         g2d.setColor(color);         g2d.fillRect(0, 0, image.getWidth(), image.getHeight());         g2d.dispose();         return mask;     }     // creates a blur convolve operation by generating a kernel of     // dimensions (size, size).     private ConvolveOp getLinearBlurOp(final int size) {         float[] data = new float[size * size];         float value = 1.0f / (float) (size * size);         for (int i = 0; i < data.length; i++) {             data[i] = value;         }         return new ConvolveOp(new Kernel(size, size, data));     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class SplineControlPanel extends JPanel {     private SplineDisplay display;     private DropSimulator dropSimulator = new DropSimulator();     private BouncerSimulator bounceSimulator = new BouncerSimulator();          private int linesCount = 0;          private JLabel labelControl1;     private JLabel labelControl2;     private Animator controller;          SplineControlPanel() {         super(new BorderLayout());                  add(buildEquationDisplay(), BorderLayout.CENTER);         add(buildDebugControls(), BorderLayout.EAST);     }          private Component buildDebugControls() {         JButton button;         JPanel debugPanel = new JPanel(new GridBagLayout());                  debugPanel.add(Box.createHorizontalStrut(150),                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.NONE,                 new Insets(0, 0, 0, 0),                 0, 0));          //        button = addButton(debugPanel, "Create"); //        button.addActionListener(new ActionListener() { //            public void actionPerformed(ActionEvent e) { //                JFileChooser chooser = new JFileChooser("."); //                int choice = chooser.showSaveDialog(SplineControlPanel.this); //                if (choice == JFileChooser.CANCEL_OPTION) { //                    return; //                } //                File file = chooser.getSelectedFile(); //                try { //                    OutputStream out = new FileOutputStream(file); //                    display.saveAsTemplate(out); //                    out.close(); //                } catch (FileNotFoundException e1) { //                } catch (IOException e1) { //                } //            } //        });                  addSeparator(debugPanel, "Control Points");         labelControl1 = addDebugLabel(debugPanel, "Point 1:", formatPoint(display.getControl1()));         labelControl2 = addDebugLabel(debugPanel, "Point 2:", formatPoint(display.getControl2()));         button = addButton(debugPanel, "Copy Code");         button.addActionListener(new ActionListener() {             public void actionPerformed(ActionEvent e) {                 NumberFormat formatter = getNumberFormatter();                 Point2D c1 = display.getControl1();                 Point2D c2 = display.getControl2();                                  StringBuilder code = new StringBuilder();                 code.append("Spline spline = new Spline(");                 code.append(formatter.format(c1.getX())).append("f, ");                 code.append(formatter.format(c1.getY())).append("f, ");                 code.append(formatter.format(c2.getX())).append("f, ");                 code.append(formatter.format(c2.getY())).append("f);");                                  Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();                 clipboard.setContents(new StringSelection(code.toString()), null);             }         });                  addEmptySpace(debugPanel, 6);         addSeparator(debugPanel, "Animation");                  button = addButton(debugPanel, "Play Sample");         button.addActionListener(new ActionListener() {             public void actionPerformed(ActionEvent e) {                 startSampleAnimation();             }         });                  addEmptySpace(debugPanel, 6);         addSeparator(debugPanel, "Templates");         debugPanel.add(createTemplates(),                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.CENTER,                 GridBagConstraints.NONE,                 new Insets(0, 0, 0, 0),                 0, 0));                  addEmptySpace(debugPanel, 6);                  debugPanel.add(Box.createVerticalGlue(),                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 1.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.NONE,                 new Insets(0, 0, 0, 0),                 0, 0));                  JPanel wrapper = new JPanel(new BorderLayout());         wrapper.add(new JSeparator(JSeparator.VERTICAL), BorderLayout.WEST);         wrapper.add(debugPanel);         wrapper.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 6));                  return wrapper;     }          private Component createTemplates() {         DefaultListModel model = new DefaultListModel();         model.addElement(createTemplate(0.0, 0.0, 1.0, 1.0));         model.addElement(createTemplate(0.0, 1.0, 0.0, 1.0));         model.addElement(createTemplate(0.0, 1.0, 1.0, 1.0));         model.addElement(createTemplate(0.0, 1.0, 1.0, 0.0));         model.addElement(createTemplate(1.0, 0.0, 0.0, 1.0));         model.addElement(createTemplate(1.0, 0.0, 1.0, 1.0));         model.addElement(createTemplate(1.0, 0.0, 1.0, 0.0));                  JList list = new JList(model);         list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);         list.setCellRenderer(new TemplateCellRenderer());         list.addListSelectionListener(new TemplateSelectionHandler());                  JScrollPane pane = new JScrollPane(list);         pane.getViewport().setPreferredSize(new Dimension(98, 97 * 3));         return pane;     }          private JButton addButton(JPanel debugPanel, String label) {         JButton button;         debugPanel.add(button = new JButton(label),                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.CENTER,                 GridBagConstraints.NONE,                 new Insets(3, 0, 0, 0),                 0, 0));         return button;     }          private String formatPoint(Point2D p) {         NumberFormat formatter = getNumberFormatter();         return "" + formatter.format(p.getX()) + ", " + formatter.format(p.getY());     }          private Component buildEquationDisplay() {         JPanel panel = new JPanel(new BorderLayout());                  display = new SplineDisplay();         display.addPropertyChangeListener("control1", new PropertyChangeListener() {             public void propertyChange(PropertyChangeEvent evt) {                 labelControl1.setText(formatPoint(display.getControl1()));             }         });         display.addPropertyChangeListener("control2", new PropertyChangeListener() {             public void propertyChange(PropertyChangeEvent evt) {                 labelControl2.setText(formatPoint(display.getControl2()));             }         });                  panel.add(display, BorderLayout.NORTH);                  JPanel wrapper = new JPanel(new GridBagLayout());         wrapper.add(new JSeparator(),                 new GridBagConstraints(0, 0,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.HORIZONTAL,                 new Insets(0, 0, 0, 0),                 0, 0));         wrapper.add(bounceSimulator,                 new GridBagConstraints(0, 1,                 1, 1,                 1.0, 1.0,                 GridBagConstraints.CENTER,                 GridBagConstraints.BOTH,                 new Insets(0, 0, 0, 0),                 0, 0));         wrapper.add(dropSimulator,                 new GridBagConstraints(1, 1,                 1, 1,                 1.0, 1.0,                 GridBagConstraints.CENTER,                 GridBagConstraints.BOTH,                 new Insets(0, 0, 0, 0),                 0, 0));         panel.add(wrapper, BorderLayout.CENTER);                  return panel;     }          private JLabel addDebugLabel(JPanel panel, String label, String value) {         JLabel labelComponent = new JLabel(label);         panel.add(labelComponent,                 new GridBagConstraints(0, linesCount,                 1, 1,                 0.5, 0.0,                 GridBagConstraints.LINE_END,                 GridBagConstraints.NONE,                 new Insets(0, 6, 0, 0),                 0, 0));         labelComponent = new JLabel(value);         panel.add(labelComponent,                 new GridBagConstraints(1, linesCount++,                 1, 1,                 0.5, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.NONE,                 new Insets(0, 6, 0, 0),                 0, 0));         return labelComponent;     }          private void addEmptySpace(JPanel panel, int size) {         panel.add(Box.createVerticalStrut(size),                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.CENTER,                 GridBagConstraints.VERTICAL,                 new Insets(6, 0, 0, 0),                 0, 0));     }          private void addSeparator(JPanel panel, String label) {         JPanel innerPanel = new JPanel(new GridBagLayout());         innerPanel.add(new JLabel(label),                 new GridBagConstraints(0, 0,                 1, 1,                 0.0, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.NONE,                 new Insets(0, 0, 0, 0),                 0, 0));         innerPanel.add(new JSeparator(),                 new GridBagConstraints(1, 0,                 1, 1,                 0.9, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.HORIZONTAL,                 new Insets(0, 6, 0, 6),                 0, 0));         panel.add(innerPanel,                 new GridBagConstraints(0, linesCount++,                 2, 1,                 1.0, 0.0,                 GridBagConstraints.LINE_START,                 GridBagConstraints.HORIZONTAL,                 new Insets(6, 6, 6, 0),                 0, 0));     }          private void startSampleAnimation() {         if (controller != null && controller.isRunning()) {             controller.stop();         }                  Point2D control1 = display.getControl1();         Point2D control2 = display.getControl2();         Interpolator splines = new SplineInterpolator((float) control1.getX(),                  (float) control1.getY(),                 (float) control2.getX(), (float) control2.getY());         KeyTimes times = new KeyTimes(0.0f, 1.0f);         KeyValues values = KeyValues.create(0.0, 1.0);         KeyFrames frames = new KeyFrames(values,times, splines);                  PropertySetter dropModifier = new PropertySetter(dropSimulator,                 "time", frames);         PropertySetter bounceModifier = new PropertySetter(bounceSimulator,                 "time", frames);                  controller = new Animator(1000, 4, RepeatBehavior.REVERSE, dropModifier);         controller.setResolution(10);         controller.addTarget(bounceModifier);                  controller.start();     }          private Evaluator point2dInterpolator = new Point2DNonLinearInterpolator();          private class Point2DNonLinearInterpolator extends Evaluator<Point2D> {         private Point2D value;         public Point2D evaluate(Point2D v0, Point2D v1,                 float fraction) {             Point2D value = (Point2D)v0.clone();             if (v0 != v1) {                 double x = value.getX();                 x += (v1.getX() - v0.getX()) * fraction;                 double y = value.getY();                 y += (v1.getY() - v0.getY()) * fraction;                 value.setLocation(x, y);             } else {                 value.setLocation(v0.getX(), v0.getY());             }             return value;         }     }          private class TemplateSelectionHandler implements ListSelectionListener {         public void valueChanged(ListSelectionEvent e) {             if (e.getValueIsAdjusting()) {                 return;             }                          JList list = (JList) e.getSource();             Template template = (Template) list.getSelectedValue();             if (template != null) {                 if (controller != null && controller.isRunning()) {                     controller.stop();                 }                                  controller = new Animator(300,                         new PropertySetter(display, "control1",                         point2dInterpolator, display.getControl1(),                         template.getControl1()));                 controller.setResolution(10);                 controller.addTarget(new PropertySetter(display, "control2",                         point2dInterpolator, display.getControl2(),                         template.getControl2()));                                  controller.start();             }         }     }          private static NumberFormat getNumberFormatter() {         NumberFormat formatter = NumberFormat.getInstance(Locale.ENGLISH);         formatter.setMinimumFractionDigits(2);         formatter.setMaximumFractionDigits(2);         return formatter;     }          private static Template createTemplate(double x1, double y1, double x2, double y2) {         return new Template(new Point2D.Double(x1, y1),                 new Point2D.Double(x2, y2));     }          private static class TemplateCellRenderer extends DefaultListCellRenderer {         private boolean isSelected;                  @Override         public Component getListCellRendererComponent(JList list, Object value, int index,                 boolean isSelected, boolean cellHasFocus) {             Template template = (Template) value;             this.setBackground(Color.WHITE);             this.setIcon(new ImageIcon(template.getImage()));             this.isSelected = isSelected;             return this;         }                  @Override         protected void paintComponent(Graphics g) {             super.paintComponent(g);                          if (isSelected) {                 g.setColor(new Color(0.0f, 0.0f, 0.7f, 0.1f));                 g.fillRect(0, 0, getWidth(), getHeight());             }         }     }          private static class Template {         private Point2D control1;         private Point2D control2;         private Image image;                  public Template(Point2D control1, Point2D control2) {             this.control1 = control1;             this.control2 = control2;         }                  public Point2D getControl1() {             return control1;         }                  public Point2D getControl2() {             return control2;         }                  public Image getImage() {             if (image == null) {                 NumberFormat formatter = getNumberFormatter();                                  String name = "";                 name += formatter.format(control1.getX()) + '-' + formatter.format(control1.getY());                 name += '-';                 name += formatter.format(control2.getX()) + '-' + formatter.format(control2.getY());                                  try {                     image = ImageIO.read(getClass().getResourceAsStream(name + ".png"));                 } catch (IOException e) {                 }             }                          return image;         }     } } /**  * Copyright (c) 2006, Sun Microsystems, Inc  * All rights reserved.  *  * Redistribution and use in source and binary forms, with or without  * modification, are permitted provided that the following conditions  * are met:  *  *   * Redistributions of source code must retain the above copyright  *     notice, this list of conditions and the following disclaimer.  *   * Redistributions in binary form must reproduce the above  *     copyright notice, this list of conditions and the following  *     disclaimer in the documentation and/or other materials provided  *     with the distribution.  *   * Neither the name of the TimingFramework project nor the names of its  *     contributors may be used to endorse or promote products derived  *     from this software without specific prior written permission.  *  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  */ class SplineDisplay extends EquationDisplay {     private static final double CONTROL_POINT_SIZE = 12.0;     private Point2D control1 = new Point2D.Double(0.25, 0.75);     private Point2D control2 = new Point2D.Double(0.75, 0.25);          private Point2D selected = null;     private Point dragStart = null;          private boolean isSaving = false;          private PropertyChangeSupport support;          SplineDisplay() {         super(0.0, 0.0,               -0.1, 1.1, -0.1, 1.1,               0.2, 6,               0.2, 6);                  setEnabled(false);                  addMouseMotionListener(new ControlPointsHandler());         addMouseListener(new SelectionHandler());                  support = new PropertyChangeSupport(this);     }          public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {         support.addPropertyChangeListener(propertyName, listener);     }     public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {         support.removePropertyChangeListener(propertyName, listener);     }     public Point2D getControl1() {         return (Point2D) control1.clone();     }     public Point2D getControl2() {         return (Point2D) control2.clone();     }          public void setControl1(Point2D control1) {         support.firePropertyChange("control1",                                    (Point2D) this.control1.clone(),                                    (Point2D) control1.clone());         this.control1 = (Point2D) control1.clone();         repaint();     }     public void setControl2(Point2D control2) {         support.firePropertyChange("control2",                                    (Point2D) this.control2.clone(),                                    (Point2D) control2.clone());         this.control2 = (Point2D) control2.clone();         repaint();     }          synchronized void saveAsTemplate(OutputStream out) {         BufferedImage image = Java2dHelper.createCompatibleImage(getWidth(), getHeight());         Graphics g = image.getGraphics();         isSaving = true;         setDrawText(false);         paint(g);         setDrawText(true);         isSaving = false;         g.dispose();                  BufferedImage subImage = image.getSubimage((int) xPositionToPixel(0.0),                                                    (int) yPositionToPixel(1.0),                                                    (int) (xPositionToPixel(1.0) - xPositionToPixel(0.0)) + 1,                                                    (int) (yPositionToPixel(0.0) - yPositionToPixel(1.0)) + 1);                  try {             ImageIO.write(subImage, "PNG", out);         } catch (IOException e) {         }                  image.flush();         subImage = null;         image = null;     }     @Override     protected void paintInformation(Graphics2D g2) {         if (!isSaving) {             paintControlPoints(g2);         }         paintSpline(g2);     }     private void paintControlPoints(Graphics2D g2) {         paintControlPoint(g2, control1);         paintControlPoint(g2, control2);     }              private void paintControlPoint(Graphics2D g2, Point2D control) {         double origin_x = xPositionToPixel(control.getX());         double origin_y = yPositionToPixel(control.getY());         double pos = control == control1 ? 0.0 : 1.0;                  Ellipse2D outer = getDraggableArea(control);         Ellipse2D inner = new Ellipse2D.Double(origin_x + 2.0 - CONTROL_POINT_SIZE / 2.0,                                                origin_y + 2.0 - CONTROL_POINT_SIZE / 2.0,                                                8.0, 8.0);                  Area circle = new Area(outer);         circle.subtract(new Area(inner));                  Stroke stroke = g2.getStroke();         g2.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,                                      5, new float[] { 5, 5 }, 0));         g2.setColor(new Color(1.0f, 0.0f, 0.0f, 0.4f));         g2.drawLine(0, (int) origin_y, (int) origin_x, (int) origin_y);         g2.drawLine((int) origin_x, (int) origin_y, (int) origin_x, getHeight());         g2.setStroke(stroke);                  if (selected == control) {             g2.setColor(new Color(1.0f, 1.0f, 1.0f, 1.0f));         } else {             g2.setColor(new Color(0.8f, 0.8f, 0.8f, 0.6f));         }         g2.fill(inner);                  g2.setColor(new Color(0.0f, 0.0f, 0.5f, 0.5f));         g2.fill(circle);                  g2.drawLine((int) origin_x, (int) origin_y,                     (int) xPositionToPixel(pos), (int) yPositionToPixel(pos));     }     private Ellipse2D getDraggableArea(Point2D control) {         Ellipse2D outer = new Ellipse2D.Double(xPositionToPixel(control.getX()) - CONTROL_POINT_SIZE / 2.0,                                                yPositionToPixel(control.getY()) - CONTROL_POINT_SIZE / 2.0,                                                CONTROL_POINT_SIZE, CONTROL_POINT_SIZE);         return outer;     }     private void paintSpline(Graphics2D g2) {         CubicCurve2D spline = new CubicCurve2D.Double(xPositionToPixel(0.0), yPositionToPixel(0.0),                                                       xPositionToPixel(control1.getX()),                                                       yPositionToPixel(control1.getY()),                                                       xPositionToPixel(control2.getX()),                                                       yPositionToPixel(control2.getY()),                                                       xPositionToPixel(1.0), yPositionToPixel(1.0));         g2.setColor(new Color(0.0f, 0.3f, 0.0f, 1.0f));         g2.draw(spline);     }          private void resetSelection() {         Point2D oldSelected = selected;         selected = null;                  if (oldSelected != null) {             Rectangle bounds = getDraggableArea(oldSelected).getBounds();             repaint(bounds.x, bounds.y, bounds.width, bounds.height);         }     }          private class ControlPointsHandler extends MouseMotionAdapter {         @Override         public void mouseMoved(MouseEvent e) {             Ellipse2D area1 = getDraggableArea(control1);             Ellipse2D area2 = getDraggableArea(control2);                          if (area1.contains(e.getPoint()) || area2.contains(e.getPoint())) {                 setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));             } else {                 setCursor(Cursor.getDefaultCursor());             }         }         @Override         public void mouseDragged(MouseEvent e) {             if (selected == null) {                 return;             }                          Point dragEnd = e.getPoint();             double distance = xPixelToPosition(dragEnd.getX()) -                               xPixelToPosition(dragStart.getX());             double x = selected.getX() + distance;             if (x < 0.0) {                 x = 0.0;             } else if (x > 1.0) {                 x = 1.0;             }                          distance = yPixelToPosition(dragEnd.getY()) -                        yPixelToPosition(dragStart.getY());             double y = selected.getY() + distance;             if (y < 0.0) {                 y = 0.0;             } else if (y > 1.0) {                 y = 1.0;             }             Point2D selectedCopy = (Point2D) selected.clone();             selected.setLocation(x, y);             support.firePropertyChange("control" + (selected == control1 ? "1" : "2"),                                        selectedCopy, (Point2D) selected.clone());                          repaint();             double xPos = xPixelToPosition(dragEnd.getX());             double yPos = -yPixelToPosition(dragEnd.getY());                          if (xPos >= 0.0 && xPos <= 1.0) {                 dragStart.setLocation(dragEnd.getX(), dragStart.getY());             }             if (yPos >= 0.0 && yPos <= 1.0) {                 dragStart.setLocation(dragStart.getX(), dragEnd.getY());             }         }     }          private class SelectionHandler extends MouseAdapter {         @Override         public void mousePressed(MouseEvent e) {             Ellipse2D area1 = getDraggableArea(control1);             Ellipse2D area2 = getDraggableArea(control2);                          if (area1.contains(e.getPoint())) {                 selected = control1;                 dragStart = e.getPoint();                                  Rectangle bounds = area1.getBounds();                 repaint(bounds.x, bounds.y, bounds.width, bounds.height);             } else if (area2.contains(e.getPoint())) {                 selected = control2;                 dragStart = e.getPoint();                                  Rectangle bounds = area2.getBounds();                 repaint(bounds.x, bounds.y, bounds.width, bounds.height);             } else {                 resetSelection();             }         }         @Override         public void mouseReleased(MouseEvent e) {             resetSelection();         }     } }                     Filthy-Rich-Clients-SplineEditor.zip( 318 k)