All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.l2fprod.common.swing.JCollapsiblePane Maven / Gradle / Ivy

Go to download

Orbit, a versatile image analysis software for biological image-based quantification

There is a newer version: 3.15
Show newest version
/**
 * $ $ License.
 *
 * Copyright $ L2FProd.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.l2fprod.common.swing;

import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Composite;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

/**
 * JCollapsiblePane provides a component which can collapse or
 * expand its content area with animation and fade in/fade out effects.
 * It also acts as a standard container for other Swing components.
 * 
 * 

* In this example, the JCollapsiblePane is used to build * a Search pane which can be shown and hidden on demand. * *

 * 
 * JCollapsiblePane cp = new JCollapsiblePane();
 *
 * // JCollapsiblePane can be used like any other container
 * cp.setLayout(new BorderLayout());
 * 
 * // the Controls panel with a textfield to filter the tree
 * JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
 * controls.add(new JLabel("Search:"));
 * controls.add(new JTextField(10));    
 * controls.add(new JButton("Refresh"));
 * controls.setBorder(new TitledBorder("Filters"));
 * cp.add("Center", controls);
 *   
 * JFrame frame = new JFrame();
 * frame.setLayout(new BorderLayout());
 *  
 * // Put the "Controls" first
 * frame.add("North", cp);
 *    
 * // Then the tree - we assume the Controls would somehow filter the tree
 * JScrollPane scroll = new JScrollPane(new JTree());
 * frame.add("Center", scroll);
 *
 * // Show/hide the "Controls"
 * JButton toggle = new JButton(cp.getActionMap().get("toggle"));
 * frame.add("South", toggle);
 *
 * frame.pack();
 * frame.setVisible(true);
 * 
 * 
* *

* Note: JCollapsiblePane requires its parent container to have a * {@link java.awt.LayoutManager} using {@link #getPreferredSize()} when * calculating its layout (example {@link com.l2fprod.common.swing.PercentLayout}, * {@link java.awt.BorderLayout}). * * @javabean.attribute * name="isContainer" * value="Boolean.TRUE" * rtexpr="true" * * @javabean.attribute * name="containerDelegate" * value="getContentPane" * * @javabean.class * name="JCollapsiblePane" * shortDescription="A pane which hides its content with an animation." * stopClass="java.awt.Component" * * @author rbair (from the JDNC project) * @author Frederic Lavigne */ public class JCollapsiblePane extends JPanel { /** * Used when generating PropertyChangeEvents for the "animationState" property */ public final static String ANIMATION_STATE_KEY = "animationState"; /** * Indicates whether the component is collapsed or expanded */ private boolean collapsed = false; /** * Timer used for doing the transparency animation (fade-in) */ private Timer animateTimer; private AnimationListener animator; private int currentHeight = -1; private WrapperContainer wrapper; private boolean useAnimation = true; private AnimationParams animationParams; /** * Constructs a new JCollapsiblePane with a {@link JPanel} as content pane and * a vertical {@link PercentLayout} with a gap of 2 pixels as layout manager. */ public JCollapsiblePane() { super.setLayout(new BorderLayout(0, 0)); JPanel panel = new JPanel(); panel.setLayout(new PercentLayout(PercentLayout.VERTICAL, 2)); setContentPane(panel); animator = new AnimationListener(); setAnimationParams(new AnimationParams(30, 8, 0.01f, 1.0f)); // add an action to automatically toggle the state of the pane getActionMap().put("toggle", new AbstractAction("Toggle") { public void actionPerformed(ActionEvent e) { setCollapsed(!isCollapsed()); } }); } /** * Sets the content pane of this JCollapsiblePane. Components must be added * to this content pane, not to the JCollapsiblePane. * * @param contentPanel * @throws IllegalArgumentException * if contentPanel is null */ public void setContentPane(Container contentPanel) { if (contentPanel == null) { throw new IllegalArgumentException("Content pane can't be null"); } if (wrapper != null) { super.remove(wrapper); } wrapper = new WrapperContainer(contentPanel); super.addImpl(wrapper, BorderLayout.CENTER, -1); } /** * @return the content pane */ public Container getContentPane() { return wrapper.c; } /** * Overriden to redirect call to the content pane. */ public void setLayout(LayoutManager mgr) { // wrapper can be null when setLayout is called by "super()" constructor if (wrapper != null) { getContentPane().setLayout(mgr); } } /** * Overriden to redirect call to the content pane. */ protected void addImpl(Component comp, Object constraints, int index) { getContentPane().add(comp, constraints, index); } /** * Overriden to redirect call to the content pane */ public void remove(Component comp) { getContentPane().remove(comp); } /** * Overriden to redirect call to the content pane. */ public void remove(int index) { getContentPane().remove(index); } /** * Overriden to redirect call to the content pane. */ public void removeAll() { getContentPane().removeAll(); } /** * If true, enables the animation when pane is collapsed/expanded. If false, * animation is turned off. * *

* When animated, the JCollapsiblePane will progressively * reduce (when collapsing) or enlarge (when expanding) the height of its * content area until it becomes 0 or until it reaches the preferred height of * the components it contains. The transparency of the content area will also * change during the animation. * *

* If not animated, the JCollapsiblePane will simply hide * (collapsing) or show (expanding) its content area. * * @param animated * @javabean.property bound="true" preferred="true" */ public void setAnimated(boolean animated) { if (animated != useAnimation) { useAnimation = animated; firePropertyChange("animated", !useAnimation, useAnimation); } } /** * @return true if the pane is animated, false otherwise * @see #setAnimated(boolean) */ public boolean isAnimated() { return useAnimation; } /** * @return true if the pane is collapsed, false if expanded */ public boolean isCollapsed() { return collapsed; } /** * Expands or collapses this JCollapsiblePane. * *

* If the component is collapsed and val is false, then this * call expands the JCollapsiblePane, such that the entire JCollapsiblePane * will be visible. If {@link #isAnimated()} returns true, the expansion will * be accompanied by an animation. * *

* However, if the component is expanded and val is true, then * this call collapses the JCollapsiblePane, such that the entire * JCollapsiblePane will be invisible. If {@link #isAnimated()} returns true, * the collapse will be accompanied by an animation. * * @see #isAnimated() * @see #setAnimated(boolean) * @javabean.property * bound="true" * preferred="true" */ public void setCollapsed(boolean val) { if (collapsed != val) { collapsed = val; if (isAnimated()) { if (collapsed) { setAnimationParams(new AnimationParams(30, Math.max(8, wrapper .getHeight() / 10), 1.0f, 0.01f)); animator.reinit(wrapper.getHeight(), 0); animateTimer.start(); } else { setAnimationParams(new AnimationParams(30, Math.max(8, getContentPane().getPreferredSize().height / 10), 0.01f, 1.0f)); animator.reinit(wrapper.getHeight(), getContentPane() .getPreferredSize().height); animateTimer.start(); } } else { wrapper.c.setVisible(!collapsed); invalidate(); doLayout(); } repaint(); firePropertyChange("collapsed", !collapsed, collapsed); } } public Dimension getMinimumSize() { return getPreferredSize(); } /** * The critical part of the animation of this JCollapsiblePane * relies on the calculation of its preferred size. During the animation, its * preferred size (specially its height) will change, when expanding, from 0 * to the preferred size of the content pane, and the reverse when collapsing. * * @return this component preferred size */ public Dimension getPreferredSize() { /* * The preferred size is calculated based on the current position of the * component in its animation sequence. If the Component is expanded, then * the preferred size will be the preferred size of the top component plus * the preferred size of the embedded content container.

However, if the * scroll up is in any state of animation, the height component of the * preferred size will be the current height of the component (as contained * in the currentHeight variable) */ Dimension dim; if (!isAnimated()) { if (getContentPane().isVisible()) { dim = getContentPane().getPreferredSize(); } else { dim = super.getPreferredSize(); } } else { dim = new Dimension(getContentPane().getPreferredSize()); if (!getContentPane().isVisible() && currentHeight != -1) { dim.height = currentHeight; } } return dim; } /** * Sets the parameters controlling the animation * * @param params * @throws IllegalArgumentException * if params is null */ private void setAnimationParams(AnimationParams params) { if (params == null) { throw new IllegalArgumentException( "params can't be null"); } if (animateTimer != null) { animateTimer.stop(); } animationParams = params; animateTimer = new Timer(animationParams.waitTime, animator); animateTimer.setInitialDelay(0); } /** * Tagging interface for containers in a JCollapsiblePane hierarchy who needs * to be revalidated (invalidate/validate/repaint) when the pane is expanding * or collapsing. Usually validating only the parent of the JCollapsiblePane * is enough but there might be cases where the parent parent must be * validated. */ public static interface JCollapsiblePaneContainer { Container getValidatingContainer(); } /** * Parameters controlling the animations */ private static class AnimationParams { final int waitTime; final int deltaY; final float alphaStart; final float alphaEnd; /** * @param waitTime * the amount of time in milliseconds to wait between calls to the * animation thread * @param deltaY * the delta in the Y direction to inc/dec the size of the scroll * up by * @param alphaStart * the starting alpha transparency level * @param alphaEnd * the ending alpha transparency level */ public AnimationParams(int waitTime, int deltaY, float alphaStart, float alphaEnd) { this.waitTime = waitTime; this.deltaY = deltaY; this.alphaStart = alphaStart; this.alphaEnd = alphaEnd; } } /** * This class actual provides the animation support for scrolling up/down this * component. This listener is called whenever the animateTimer fires off. It * fires off in response to scroll up/down requests. This listener is * responsible for modifying the size of the content container and causing it * to be repainted. * * @author Richard Bair */ private final class AnimationListener implements ActionListener { /** * Mutex used to ensure that the startHeight/finalHeight are not changed * during a repaint operation. */ private final Object ANIMATION_MUTEX = "Animation Synchronization Mutex"; /** * This is the starting height when animating. If > finalHeight, then the * animation is going to be to scroll up the component. If it is < then * finalHeight, then the animation will scroll down the component. */ private int startHeight = 0; /** * This is the final height that the content container is going to be when * scrolling is finished. */ private int finalHeight = 0; /** * The current alpha setting used during "animation" (fade-in/fade-out) */ private float animateAlpha = 1.0f; public void actionPerformed(ActionEvent e) { /* * Pre-1) If startHeight == finalHeight, then we're done so stop the timer * 1) Calculate whether we're contracting or expanding. 2) Calculate the * delta (which is either positive or negative, depending on the results * of (1)) 3) Calculate the alpha value 4) Resize the ContentContainer 5) * Revalidate/Repaint the content container */ synchronized (ANIMATION_MUTEX) { if (startHeight == finalHeight) { animateTimer.stop(); animateAlpha = animationParams.alphaEnd; // keep the content pane hidden when it is collapsed, other it may // still receive focus. if (finalHeight > 0) { wrapper.showContent(); validate(); JCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null, "expanded"); return; } } final boolean contracting = startHeight > finalHeight; final int delta_y = contracting?-1 * animationParams.deltaY :animationParams.deltaY; int newHeight = wrapper.getHeight() + delta_y; if (contracting) { if (newHeight < finalHeight) { newHeight = finalHeight; } } else { if (newHeight > finalHeight) { newHeight = finalHeight; } } animateAlpha = (float)newHeight / (float)wrapper.c.getPreferredSize().height; Rectangle bounds = wrapper.getBounds(); int oldHeight = bounds.height; bounds.height = newHeight; wrapper.setBounds(bounds); bounds = getBounds(); bounds.height = (bounds.height - oldHeight) + newHeight; currentHeight = bounds.height; setBounds(bounds); startHeight = newHeight; // it happens the animateAlpha goes over the alphaStart/alphaEnd range // this code ensures it stays in bounds. This behavior is seen when // component such as JTextComponents are used in the container. if (contracting) { // alphaStart > animateAlpha > alphaEnd if (animateAlpha < animationParams.alphaEnd) { animateAlpha = animationParams.alphaEnd; } if (animateAlpha > animationParams.alphaStart) { animateAlpha = animationParams.alphaStart; } } else { // alphaStart < animateAlpha < alphaEnd if (animateAlpha > animationParams.alphaEnd) { animateAlpha = animationParams.alphaEnd; } if (animateAlpha < animationParams.alphaStart) { animateAlpha = animationParams.alphaStart; } } wrapper.alpha = animateAlpha; validate(); } } void validate() { Container parent = SwingUtilities.getAncestorOfClass( JCollapsiblePaneContainer.class, JCollapsiblePane.this); if (parent != null) { parent = ((JCollapsiblePaneContainer)parent).getValidatingContainer(); } else { parent = getParent(); } if (parent != null) { if (parent instanceof JComponent) { ((JComponent)parent).revalidate(); } else { parent.invalidate(); } parent.doLayout(); parent.repaint(); } } /** * Reinitializes the timer for scrolling up/down the component. This method * is properly synchronized, so you may make this call regardless of whether * the timer is currently executing or not. * * @param startHeight * @param stopHeight */ public void reinit(int startHeight, int stopHeight) { synchronized (ANIMATION_MUTEX) { JCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null, "reinit"); this.startHeight = startHeight; this.finalHeight = stopHeight; animateAlpha = animationParams.alphaStart; currentHeight = -1; wrapper.showImage(); } } } private final class WrapperContainer extends JPanel { private BufferedImage img; private Container c; float alpha = 1.0f; public WrapperContainer(Container c) { super(new BorderLayout()); this.c = c; add(c, BorderLayout.CENTER); // we must ensure the container is opaque. It is not opaque it introduces // painting glitches specially on Linux with JDK 1.5 and GTK look and feel. // GTK look and feel calls setOpaque(false) if (c instanceof JComponent && !((JComponent)c).isOpaque()) { ((JComponent)c).setOpaque(true); } } public void showImage() { // render c into the img makeImage(); c.setVisible(false); } public void showContent() { currentHeight = -1; c.setVisible(true); } void makeImage() { // if we have no image or if the image has changed if (getGraphicsConfiguration() != null && getWidth() > 0) { Dimension dim = c.getPreferredSize(); // width and height must be > 0 to be able to create an image if (dim.height > 0) { img = getGraphicsConfiguration().createCompatibleImage(getWidth(), dim.height); c.setSize(getWidth(), dim.height); c.paint(img.getGraphics()); } else { img = null; } } } public void paintComponent(Graphics g) { if (!useAnimation || c.isVisible()) { super.paintComponent(g); } else { // within netbeans, it happens we arrive here and the image has not been // created yet. We ensure it is. if (img == null) { makeImage(); } // and we paint it only if it has been created and only if we have a // valid graphics if (g != null && img != null) { // draw the image with y being height - imageHeight g.drawImage(img, 0, getHeight() - img.getHeight(), null); } } } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; Composite oldComp = g2d.getComposite(); Composite alphaComp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha); g2d.setComposite(alphaComp); super.paint(g2d); g2d.setComposite(oldComp); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy