JXCollapsiblePane.java
上传用户:zhengdagz
上传日期:2014-03-06
资源大小:1956k
文件大小:20k
源码类别:

xml/soap/webservice

开发平台:

Java

  1. /*
  2.  * $Id: JXCollapsiblePane.java,v 1.5 2005/10/10 18:02:04 rbair Exp $
  3.  *
  4.  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
  5.  * Santa Clara, California 95054, U.S.A. All rights reserved.
  6.  *
  7.  * This library is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  * 
  12.  * This library is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  * 
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with this library; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  20.  */
  21. package org.jdesktop.swingx;
  22. import java.awt.AlphaComposite;
  23. import java.awt.BorderLayout;
  24. import java.awt.Component;
  25. import java.awt.Composite;
  26. import java.awt.Container;
  27. import java.awt.Dimension;
  28. import java.awt.Graphics;
  29. import java.awt.Graphics2D;
  30. import java.awt.LayoutManager;
  31. import java.awt.Rectangle;
  32. import java.awt.event.ActionEvent;
  33. import java.awt.event.ActionListener;
  34. import java.awt.image.BufferedImage;
  35. import javax.swing.AbstractAction;
  36. import javax.swing.JComponent;
  37. import javax.swing.JPanel;
  38. import javax.swing.SwingUtilities;
  39. import javax.swing.Timer;
  40. /**
  41.  * <code>JXCollapsiblePane</code> provides a component which can collapse or
  42.  * expand its content area with animation and fade in/fade out effects.
  43.  * It also acts as a standard container for other Swing components.
  44.  * 
  45.  * <p>
  46.  * In this example, the <code>JXCollapsiblePane</code> is used to build
  47.  * a Search pane which can be shown and hidden on demand.
  48.  * 
  49.  * <pre>
  50.  * <code>
  51.  * JXCollapsiblePane cp = new JXCollapsiblePane();
  52.  *
  53.  * // JXCollapsiblePane can be used like any other container
  54.  * cp.setLayout(new BorderLayout());
  55.  * 
  56.  * // the Controls panel with a textfield to filter the tree
  57.  * JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
  58.  * controls.add(new JLabel("Search:"));
  59.  * controls.add(new JTextField(10));    
  60.  * controls.add(new JButton("Refresh"));
  61.  * controls.setBorder(new TitledBorder("Filters"));
  62.  * cp.add("Center", controls);
  63.  *   
  64.  * JXFrame frame = new JXFrame();
  65.  * frame.setLayout(new BorderLayout());
  66.  *  
  67.  * // Put the "Controls" first
  68.  * frame.add("North", cp);
  69.  *    
  70.  * // Then the tree - we assume the Controls would somehow filter the tree
  71.  * JScrollPane scroll = new JScrollPane(new JTree());
  72.  * frame.add("Center", scroll);
  73.  *
  74.  * // Show/hide the "Controls"
  75.  * JButton toggle = new JButton(cp.getActionMap().get("toggle"));
  76.  * frame.add("South", toggle);
  77.  *
  78.  * frame.pack();
  79.  * frame.setVisible(true);
  80.  * </code>
  81.  * </pre>
  82.  * 
  83.  * <p>
  84.  * Note: <code>JXCollapsiblePane</code> requires its parent container to have a
  85.  * {@link java.awt.LayoutManager} using {@link #getPreferredSize()} when
  86.  * calculating its layout (example {@link org.jdesktop.swingx.VerticalLayout},
  87.  * {@link java.awt.BorderLayout}). 
  88.  * 
  89.  * @javabean.attribute
  90.  *          name="isContainer"
  91.  *          value="Boolean.TRUE"
  92.  *          rtexpr="true"
  93.  * 
  94.  * @javabean.attribute
  95.  *          name="containerDelegate"
  96.  *          value="getContentPane"
  97.  *          
  98.  * @javabean.class
  99.  *          name="JXCollapsiblePane"
  100.  *          shortDescription="A pane which hides its content with an animation."
  101.  *          stopClass="java.awt.Component"
  102.  *          
  103.  * @author rbair (from the JDNC project)
  104.  * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
  105.  */
  106. public class JXCollapsiblePane extends JPanel {
  107.   /**
  108.    * Used when generating PropertyChangeEvents for the "animationState" property
  109.    */
  110.   public final static String ANIMATION_STATE_KEY = "animationState";
  111.   
  112.   /**
  113.    * Indicates whether the component is collapsed or expanded
  114.    */
  115.   private boolean collapsed = false;
  116.   /**
  117.    * Timer used for doing the transparency animation (fade-in)
  118.    */
  119.   private Timer animateTimer;
  120.   private AnimationListener animator;
  121.   private int currentHeight = -1;
  122.   private WrapperContainer wrapper;
  123.   private boolean useAnimation = true;
  124.   private AnimationParams animationParams;
  125.   /**
  126.    * Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane
  127.    * and a vertical {@link VerticalLayout} with a gap of 2 pixels as layout
  128.    * manager.
  129.    */
  130.   public JXCollapsiblePane() {
  131.     super.setLayout(new BorderLayout(0, 0));
  132.     JPanel panel = new JPanel();
  133.     panel.setLayout(new VerticalLayout(2));
  134.     setContentPane(panel);
  135.     animator = new AnimationListener();
  136.     setAnimationParams(new AnimationParams(30, 8, 0.01f, 1.0f));
  137.     
  138.     // add an action to automatically toggle the state of the pane
  139.     getActionMap().put("toggle", new AbstractAction("Toggle") {
  140.       public void actionPerformed(ActionEvent e) {
  141.         setCollapsed(!isCollapsed());
  142.       }
  143.     });
  144.   }
  145.   /**
  146.    * Sets the content pane of this JXCollapsiblePane. Components must be added
  147.    * to this content pane, not to the JXCollapsiblePane.
  148.    * 
  149.    * @param contentPanel
  150.    * @throws IllegalArgumentException
  151.    *           if contentPanel is null
  152.    */
  153.   public void setContentPane(Container contentPanel) {
  154.     if (contentPanel == null) {
  155.       throw new IllegalArgumentException("Content pane can't be null");
  156.     }
  157.     
  158.     if (wrapper != null) {
  159.       super.remove(wrapper);
  160.     }
  161.     wrapper = new WrapperContainer(contentPanel);
  162.     super.addImpl(wrapper, BorderLayout.CENTER, -1);
  163.   }
  164.   /**
  165.    * @return the content pane
  166.    */
  167.   public Container getContentPane() {
  168.     return wrapper.c;
  169.   }
  170.   /**
  171.    * Overriden to redirect call to the content pane.
  172.    */
  173.   public void setLayout(LayoutManager mgr) {
  174.     // wrapper can be null when setLayout is called by "super()" constructor
  175.     if (wrapper != null) {
  176.       getContentPane().setLayout(mgr);
  177.     }
  178.   }
  179.   /**
  180.    * Overriden to redirect call to the content pane.
  181.    */
  182.   protected void addImpl(Component comp, Object constraints, int index) {
  183.     getContentPane().add(comp, constraints, index);
  184.   }
  185.   /**
  186.    * Overriden to redirect call to the content pane
  187.    */
  188.   public void remove(Component comp) {
  189.     getContentPane().remove(comp);
  190.   }
  191.   /**
  192.    * Overriden to redirect call to the content pane.
  193.    */
  194.   public void remove(int index) {
  195.     getContentPane().remove(index);
  196.   }
  197.   
  198.   /**
  199.    * Overriden to redirect call to the content pane.
  200.    */
  201.   public void removeAll() {
  202.     getContentPane().removeAll();
  203.   }
  204.   
  205.   /**
  206.    * If true, enables the animation when pane is collapsed/expanded. If false,
  207.    * animation is turned off.
  208.    * 
  209.    * <p>
  210.    * When animated, the <code>JXCollapsiblePane</code> will progressively
  211.    * reduce (when collapsing) or enlarge (when expanding) the height of its
  212.    * content area until it becomes 0 or until it reaches the preferred height of
  213.    * the components it contains. The transparency of the content area will also
  214.    * change during the animation.
  215.    * 
  216.    * <p>
  217.    * If not animated, the <code>JXCollapsiblePane</code> will simply hide
  218.    * (collapsing) or show (expanding) its content area.
  219.    * 
  220.    * @param animated
  221.    * @javabean.property bound="true" preferred="true"
  222.    */
  223.   public void setAnimated(boolean animated) {
  224.     if (animated != useAnimation) {
  225.       useAnimation = animated;
  226.       firePropertyChange("animated", !useAnimation, useAnimation);
  227.     }
  228.   }
  229.   /**
  230.    * @return true if the pane is animated, false otherwise
  231.    * @see #setAnimated(boolean)
  232.    */
  233.   public boolean isAnimated() {
  234.     return useAnimation;
  235.   }
  236.   /**
  237.    * @return true if the pane is collapsed, false if expanded
  238.    */
  239.   public boolean isCollapsed() {
  240.     return collapsed;
  241.   }
  242.   /**
  243.    * Expands or collapses this <code>JXCollapsiblePane</code>.
  244.    * 
  245.    * <p>
  246.    * If the component is collapsed and <code>val</code> is false, then this
  247.    * call expands the JXCollapsiblePane, such that the entire JXCollapsiblePane
  248.    * will be visible. If {@link #isAnimated()} returns true, the expansion will
  249.    * be accompanied by an animation.
  250.    * 
  251.    * <p>
  252.    * However, if the component is expanded and <code>val</code> is true, then
  253.    * this call collapses the JXCollapsiblePane, such that the entire
  254.    * JXCollapsiblePane will be invisible. If {@link #isAnimated()} returns true,
  255.    * the collapse will be accompanied by an animation.
  256.    * 
  257.    * @see #isAnimated()
  258.    * @see #setAnimated(boolean)
  259.    * @javabean.property
  260.    *    bound="true"
  261.    *    preferred="true"
  262.    */
  263.   public void setCollapsed(boolean val) {
  264.     if (collapsed != val) {
  265.       collapsed = val;
  266.       if (isAnimated()) {
  267.         if (collapsed) {
  268.           setAnimationParams(new AnimationParams(30, Math.max(8, wrapper
  269.             .getHeight() / 10), 1.0f, 0.01f));
  270.           animator.reinit(wrapper.getHeight(), 0);
  271.           animateTimer.start();
  272.         } else {
  273.           setAnimationParams(new AnimationParams(30, Math.max(8,
  274.             getContentPane().getPreferredSize().height / 10), 0.01f, 1.0f));
  275.           animator.reinit(wrapper.getHeight(), getContentPane()
  276.             .getPreferredSize().height);
  277.           animateTimer.start();
  278.         }
  279.       } else {
  280.         wrapper.c.setVisible(!collapsed);
  281.         invalidate();
  282.         doLayout();
  283.       }
  284.       repaint();
  285.       firePropertyChange("collapsed", !collapsed, collapsed);
  286.     }
  287.   }
  288.   public Dimension getMinimumSize() {
  289.     return getPreferredSize();
  290.   }
  291.   /**
  292.    * The critical part of the animation of this <code>JXCollapsiblePane</code>
  293.    * relies on the calculation of its preferred size. During the animation, its
  294.    * preferred size (specially its height) will change, when expanding, from 0
  295.    * to the preferred size of the content pane, and the reverse when collapsing.
  296.    * 
  297.    * @return this component preferred size
  298.    */
  299.   public Dimension getPreferredSize() {
  300.     /*
  301.      * The preferred size is calculated based on the current position of the
  302.      * component in its animation sequence. If the Component is expanded, then
  303.      * the preferred size will be the preferred size of the top component plus
  304.      * the preferred size of the embedded content container. <p>However, if the
  305.      * scroll up is in any state of animation, the height component of the
  306.      * preferred size will be the current height of the component (as contained
  307.      * in the currentHeight variable)
  308.      */
  309.     Dimension dim;
  310.     if (!isAnimated()) {
  311.       if (getContentPane().isVisible()) {
  312.         dim = getContentPane().getPreferredSize();
  313.       } else {
  314.         dim = super.getPreferredSize();
  315.       }
  316.     } else {
  317.       dim = new Dimension(getContentPane().getPreferredSize());
  318.       if (!getContentPane().isVisible() && currentHeight != -1) {
  319.         dim.height = currentHeight;
  320.       }
  321.     }
  322.     return dim;
  323.   }
  324.   /**
  325.    * Sets the parameters controlling the animation
  326.    * 
  327.    * @param params
  328.    * @throws IllegalArgumentException
  329.    *           if params is null
  330.    */
  331.   private void setAnimationParams(AnimationParams params) {
  332.     if (params == null) { throw new IllegalArgumentException(
  333.       "params can't be null"); }
  334.     if (animateTimer != null) {
  335.       animateTimer.stop();
  336.     }
  337.     animationParams = params;
  338.     animateTimer = new Timer(animationParams.waitTime, animator);
  339.     animateTimer.setInitialDelay(0);
  340.   }
  341.   
  342.   /**
  343.    * Tagging interface for containers in a JXCollapsiblePane hierarchy who needs
  344.    * to be revalidated (invalidate/validate/repaint) when the pane is expanding
  345.    * or collapsing. Usually validating only the parent of the JXCollapsiblePane
  346.    * is enough but there might be cases where the parent parent must be
  347.    * validated.
  348.    */
  349.   public static interface JCollapsiblePaneContainer {
  350.     Container getValidatingContainer();
  351.   }
  352.   /**
  353.    * Parameters controlling the animations
  354.    */
  355.   private static class AnimationParams {
  356.     final int waitTime;
  357.     final int deltaY;
  358.     final float alphaStart;
  359.     final float alphaEnd;
  360.     /**
  361.      * @param waitTime
  362.      *          the amount of time in milliseconds to wait between calls to the
  363.      *          animation thread
  364.      * @param deltaY
  365.      *          the delta in the Y direction to inc/dec the size of the scroll
  366.      *          up by
  367.      * @param alphaStart
  368.      *          the starting alpha transparency level
  369.      * @param alphaEnd
  370.      *          the ending alpha transparency level
  371.      */
  372.     public AnimationParams(int waitTime, int deltaY, float alphaStart,
  373.       float alphaEnd) {
  374.       this.waitTime = waitTime;
  375.       this.deltaY = deltaY;
  376.       this.alphaStart = alphaStart;
  377.       this.alphaEnd = alphaEnd;
  378.     }
  379.   }
  380.   /**
  381.    * This class actual provides the animation support for scrolling up/down this
  382.    * component. This listener is called whenever the animateTimer fires off. It
  383.    * fires off in response to scroll up/down requests. This listener is
  384.    * responsible for modifying the size of the content container and causing it
  385.    * to be repainted.
  386.    * 
  387.    * @author Richard Bair
  388.    */
  389.   private final class AnimationListener implements ActionListener {
  390.     /**
  391.      * Mutex used to ensure that the startHeight/finalHeight are not changed
  392.      * during a repaint operation.
  393.      */
  394.     private final Object ANIMATION_MUTEX = "Animation Synchronization Mutex";
  395.     /**
  396.      * This is the starting height when animating. If > finalHeight, then the
  397.      * animation is going to be to scroll up the component. If it is < then
  398.      * finalHeight, then the animation will scroll down the component.
  399.      */
  400.     private int startHeight = 0;
  401.     /**
  402.      * This is the final height that the content container is going to be when
  403.      * scrolling is finished.
  404.      */
  405.     private int finalHeight = 0;
  406.     /**
  407.      * The current alpha setting used during "animation" (fade-in/fade-out)
  408.      */
  409.     private float animateAlpha = 1.0f;
  410.     public void actionPerformed(ActionEvent e) {
  411.       /*
  412.        * Pre-1) If startHeight == finalHeight, then we're done so stop the timer
  413.        * 1) Calculate whether we're contracting or expanding. 2) Calculate the
  414.        * delta (which is either positive or negative, depending on the results
  415.        * of (1)) 3) Calculate the alpha value 4) Resize the ContentContainer 5)
  416.        * Revalidate/Repaint the content container
  417.        */
  418.       synchronized (ANIMATION_MUTEX) {
  419.         if (startHeight == finalHeight) {
  420.           animateTimer.stop();
  421.           animateAlpha = animationParams.alphaEnd;
  422.           // keep the content pane hidden when it is collapsed, other it may
  423.           // still receive focus.
  424.           if (finalHeight > 0) {
  425.             wrapper.showContent();   
  426.             validate();
  427.             JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
  428.               "expanded");
  429.             return;
  430.           }
  431.         }
  432.         final boolean contracting = startHeight > finalHeight;
  433.         final int delta_y = contracting?-1 * animationParams.deltaY
  434.           :animationParams.deltaY;
  435.         int newHeight = wrapper.getHeight() + delta_y;
  436.         if (contracting) {
  437.           if (newHeight < finalHeight) {
  438.             newHeight = finalHeight;
  439.           }
  440.         } else {
  441.           if (newHeight > finalHeight) {
  442.             newHeight = finalHeight;
  443.           }
  444.         }
  445.         animateAlpha = (float)newHeight
  446.           / (float)wrapper.c.getPreferredSize().height;
  447.         Rectangle bounds = wrapper.getBounds();
  448.         int oldHeight = bounds.height;
  449.         bounds.height = newHeight;
  450.         wrapper.setBounds(bounds);
  451.         bounds = getBounds();
  452.         bounds.height = (bounds.height - oldHeight) + newHeight;
  453.         currentHeight = bounds.height;
  454.         setBounds(bounds);
  455.         startHeight = newHeight;
  456.         
  457.         // it happens the animateAlpha goes over the alphaStart/alphaEnd range
  458.         // this code ensures it stays in bounds. This behavior is seen when
  459.         // component such as JTextComponents are used in the container.
  460.         if (contracting) {
  461.           // alphaStart > animateAlpha > alphaEnd
  462.           if (animateAlpha < animationParams.alphaEnd) {
  463.             animateAlpha = animationParams.alphaEnd;
  464.           }
  465.           if (animateAlpha > animationParams.alphaStart) {
  466.             animateAlpha = animationParams.alphaStart;            
  467.           }
  468.         } else {
  469.           // alphaStart < animateAlpha < alphaEnd
  470.           if (animateAlpha > animationParams.alphaEnd) {
  471.             animateAlpha = animationParams.alphaEnd;
  472.           }
  473.           if (animateAlpha < animationParams.alphaStart) {
  474.             animateAlpha = animationParams.alphaStart;
  475.           }
  476.         }
  477.         wrapper.alpha = animateAlpha;
  478.         validate();
  479.       }
  480.     }
  481.       
  482.     void validate() {
  483.       Container parent = SwingUtilities.getAncestorOfClass(
  484.         JCollapsiblePaneContainer.class, JXCollapsiblePane.this);
  485.       if (parent != null) {
  486.         parent = ((JCollapsiblePaneContainer)parent).getValidatingContainer();
  487.       } else {
  488.         parent = getParent();
  489.       }
  490.       if (parent != null) {
  491.         if (parent instanceof JComponent) {
  492.           ((JComponent)parent).revalidate();
  493.         } else {
  494.           parent.invalidate();
  495.         }
  496.         parent.doLayout();
  497.         parent.repaint();
  498.       }        
  499.     }
  500.     /**
  501.      * Reinitializes the timer for scrolling up/down the component. This method
  502.      * is properly synchronized, so you may make this call regardless of whether
  503.      * the timer is currently executing or not.
  504.      * 
  505.      * @param startHeight
  506.      * @param stopHeight
  507.      */
  508.     public void reinit(int startHeight, int stopHeight) {
  509.       synchronized (ANIMATION_MUTEX) {
  510.         JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
  511.           "reinit");
  512.         this.startHeight = startHeight;
  513.         this.finalHeight = stopHeight;
  514.         animateAlpha = animationParams.alphaStart;
  515.         currentHeight = -1;
  516.         wrapper.showImage();
  517.       }
  518.     }
  519.   }
  520.   private final class WrapperContainer extends JPanel {
  521.     private BufferedImage img;
  522.     private Container c;
  523.     float alpha = 1.0f;
  524.     public WrapperContainer(Container c) {
  525.       super(new BorderLayout());
  526.       this.c = c;
  527.       add(c, BorderLayout.CENTER);
  528.       
  529.       // we must ensure the container is opaque. It is not opaque it introduces
  530.       // painting glitches specially on Linux with JDK 1.5 and GTK look and feel.
  531.       // GTK look and feel calls setOpaque(false)
  532.       if (c instanceof JComponent && !((JComponent)c).isOpaque()) {
  533.         ((JComponent)c).setOpaque(true);
  534.       }
  535.     }
  536.     public void showImage() {
  537.       // render c into the img
  538.       makeImage();
  539.       c.setVisible(false);
  540.     }
  541.     public void showContent() {
  542.       currentHeight = -1;
  543.       c.setVisible(true);
  544.     }
  545.     void makeImage() {
  546.       // if we have no image or if the image has changed      
  547.       if (getGraphicsConfiguration() != null && getWidth() > 0) {
  548.         Dimension dim = c.getPreferredSize();
  549.         // width and height must be > 0 to be able to create an image
  550.         if (dim.height > 0) {
  551.           img = getGraphicsConfiguration().createCompatibleImage(getWidth(),
  552.             dim.height);
  553.           c.setSize(getWidth(), dim.height);
  554.           c.paint(img.getGraphics());
  555.         } else {
  556.           img = null;
  557.         }
  558.       }
  559.     }
  560.     
  561.     public void paintComponent(Graphics g) {
  562.       if (!useAnimation || c.isVisible()) {
  563.         super.paintComponent(g);
  564.       } else {
  565.         // within netbeans, it happens we arrive here and the image has not been
  566.         // created yet. We ensure it is.
  567.         if (img == null) {
  568.           makeImage();
  569.         }
  570.         // and we paint it only if it has been created and only if we have a
  571.         // valid graphics
  572.         if (g != null && img != null) {
  573.           // draw the image with y being height - imageHeight
  574.           g.drawImage(img, 0, getHeight() - img.getHeight(), null);
  575.         }
  576.       }
  577.     }
  578.     public void paint(Graphics g) {
  579.       Graphics2D g2d = (Graphics2D)g;
  580.       Composite oldComp = g2d.getComposite();
  581.       Composite alphaComp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
  582.         alpha);
  583.       g2d.setComposite(alphaComp);
  584.       super.paint(g2d);
  585.       g2d.setComposite(oldComp);
  586.     }
  587.   }
  588. }