001 /* 002 * $Id: JXCollapsiblePane.java,v 1.13 2006/04/21 05:48:39 gfx Exp $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 package org.jdesktop.swingx; 022 023 import java.awt.AlphaComposite; 024 import java.awt.BorderLayout; 025 import java.awt.Component; 026 import java.awt.Composite; 027 import java.awt.Container; 028 import java.awt.Dimension; 029 import java.awt.Graphics; 030 import java.awt.Graphics2D; 031 import java.awt.LayoutManager; 032 import java.awt.Rectangle; 033 import java.awt.event.ActionEvent; 034 import java.awt.event.ActionListener; 035 import java.awt.image.BufferedImage; 036 import java.beans.PropertyChangeEvent; 037 import java.beans.PropertyChangeListener; 038 import javax.swing.AbstractAction; 039 import javax.swing.JComponent; 040 import javax.swing.JPanel; 041 import javax.swing.SwingUtilities; 042 import javax.swing.Timer; 043 044 /** 045 * <code>JXCollapsiblePane</code> provides a component which can collapse or 046 * expand its content area with animation and fade in/fade out effects. 047 * It also acts as a standard container for other Swing components. 048 * 049 * <p> 050 * In this example, the <code>JXCollapsiblePane</code> is used to build 051 * a Search pane which can be shown and hidden on demand. 052 * 053 * <pre> 054 * <code> 055 * JXCollapsiblePane cp = new JXCollapsiblePane(); 056 * 057 * // JXCollapsiblePane can be used like any other container 058 * cp.setLayout(new BorderLayout()); 059 * 060 * // the Controls panel with a textfield to filter the tree 061 * JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0)); 062 * controls.add(new JLabel("Search:")); 063 * controls.add(new JTextField(10)); 064 * controls.add(new JButton("Refresh")); 065 * controls.setBorder(new TitledBorder("Filters")); 066 * cp.add("Center", controls); 067 * 068 * JXFrame frame = new JXFrame(); 069 * frame.setLayout(new BorderLayout()); 070 * 071 * // Put the "Controls" first 072 * frame.add("North", cp); 073 * 074 * // Then the tree - we assume the Controls would somehow filter the tree 075 * JScrollPane scroll = new JScrollPane(new JTree()); 076 * frame.add("Center", scroll); 077 * 078 * // Show/hide the "Controls" 079 * JButton toggle = new JButton(cp.getActionMap().get(JXCollapsiblePane.TOGGLE_ACTION)); 080 * toggle.setText("Show/Hide Search Panel"); 081 * frame.add("South", toggle); 082 * 083 * frame.pack(); 084 * frame.setVisible(true); 085 * </code> 086 * </pre> 087 * 088 * <p> 089 * The <code>JXCollapsiblePane</code> has a default toggle action registered 090 * under the name {@link #TOGGLE_ACTION}. Bind this action to a button and 091 * pressing the button will automatically toggle the pane between expanded 092 * and collapsed states. Additionally, you can define the icons to use through 093 * the {@link #EXPAND_ICON} and {@link #COLLAPSE_ICON} properties on the action. 094 * Example 095 * <pre> 096 * <code> 097 * // get the built-in toggle action 098 * Action toggleAction = collapsible.getActionMap(). 099 * get(JXCollapsiblePane.TOGGLE_ACTION); 100 * 101 * // use the collapse/expand icons from the JTree UI 102 * toggleAction.putValue(JXCollapsiblePane.COLLAPSE_ICON, 103 * UIManager.getIcon("Tree.expandedIcon")); 104 * toggleAction.putValue(JXCollapsiblePane.EXPAND_ICON, 105 * UIManager.getIcon("Tree.collapsedIcon")); 106 * </code> 107 * </pre> 108 * 109 * <p> 110 * Note: <code>JXCollapsiblePane</code> requires its parent container to have a 111 * {@link java.awt.LayoutManager} using {@link #getPreferredSize()} when 112 * calculating its layout (example {@link org.jdesktop.swingx.VerticalLayout}, 113 * {@link java.awt.BorderLayout}). 114 * 115 * @javabean.attribute 116 * name="isContainer" 117 * value="Boolean.TRUE" 118 * rtexpr="true" 119 * 120 * @javabean.attribute 121 * name="containerDelegate" 122 * value="getContentPane" 123 * 124 * @javabean.class 125 * name="JXCollapsiblePane" 126 * shortDescription="A pane which hides its content with an animation." 127 * stopClass="java.awt.Component" 128 * 129 * @author rbair (from the JDNC project) 130 * @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a> 131 */ 132 public class JXCollapsiblePane extends JXPanel { 133 /** 134 * The orientation defines in which direction the collapsible pane will 135 * expand. 136 */ 137 public enum Orientation { 138 /** 139 * The horizontal orientation makes the collapsible pane 140 * expand horizontally 141 */ 142 HORIZONTAL, 143 /** 144 * The horizontal orientation makes the collapsible pane 145 * expand vertically 146 */ 147 VERTICAL 148 } 149 150 /** 151 * Used when generating PropertyChangeEvents for the "animationState" 152 * property. The PropertyChangeEvent will takes the following different values 153 * for {@link PropertyChangeEvent#getNewValue()}: 154 * <ul> 155 * <li><code>reinit</code> every time the animation starts 156 * <li><code>expanded</code> when the animation ends and the pane is expanded 157 * <li><code>collapsed</code> when the animation ends and the pane is collapsed 158 * </ul> 159 */ 160 public final static String ANIMATION_STATE_KEY = "animationState"; 161 162 /** 163 * JXCollapsible has a built-in toggle action which can be bound to buttons. 164 * Accesses the action through 165 * <code>collapsiblePane.getActionMap().get(JXCollapsiblePane.TOGGLE_ACTION)</code>. 166 */ 167 public final static String TOGGLE_ACTION = "toggle"; 168 169 /** 170 * The icon used by the "toggle" action when the JXCollapsiblePane is 171 * expanded, i.e the icon which indicates the pane can be collapsed. 172 */ 173 public final static String COLLAPSE_ICON = "collapseIcon"; 174 175 /** 176 * The icon used by the "toggle" action when the JXCollapsiblePane is 177 * collapsed, i.e the icon which indicates the pane can be expanded. 178 */ 179 public final static String EXPAND_ICON = "expandIcon"; 180 181 /** 182 * Indicates whether the component is collapsed or expanded 183 */ 184 private boolean collapsed = false; 185 186 /** 187 * Defines the orientation of the component. 188 */ 189 private Orientation orientation = Orientation.VERTICAL; 190 191 /** 192 * Timer used for doing the transparency animation (fade-in) 193 */ 194 private Timer animateTimer; 195 private AnimationListener animator; 196 private int currentDimension = -1; 197 private WrapperContainer wrapper; 198 private boolean useAnimation = true; 199 private AnimationParams animationParams; 200 201 /** 202 * Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane 203 * and a vertical {@link VerticalLayout} with a gap of 2 pixels as layout 204 * manager and a vertical orientation. 205 */ 206 public JXCollapsiblePane() { 207 this(Orientation.VERTICAL, new BorderLayout(0, 0)); 208 } 209 210 /** 211 * Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane 212 * and the specified orientation. 213 */ 214 public JXCollapsiblePane(Orientation orientation) { 215 this(orientation, new BorderLayout(0, 0)); 216 } 217 218 /** 219 * Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane 220 * and the given LayoutManager and a vertical orientation 221 */ 222 public JXCollapsiblePane(LayoutManager layout) { 223 this(Orientation.VERTICAL, layout); 224 } 225 226 /** 227 * Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane 228 * and the given LayoutManager and orientation. A vertical orientation enables 229 * a vertical {@link VerticalLayout} with a gap of 2 pixels as layout 230 * manager. A horizontal orientation enables a horizontal 231 * {@link HorizontalLayout} with a gap of 2 pixels as layout manager 232 */ 233 public JXCollapsiblePane(Orientation orientation, LayoutManager layout) { 234 super.setLayout(layout); 235 236 this.orientation = orientation; 237 238 JPanel panel = new JPanel(); 239 if (orientation == Orientation.VERTICAL) { 240 panel.setLayout(new VerticalLayout(2)); 241 } else { 242 panel.setLayout(new HorizontalLayout(2)); 243 } 244 setContentPane(panel); 245 246 animator = new AnimationListener(); 247 setAnimationParams(new AnimationParams(30, 8, 0.01f, 1.0f)); 248 249 // add an action to automatically toggle the state of the pane 250 getActionMap().put(TOGGLE_ACTION, new ToggleAction()); 251 } 252 253 /** 254 * Toggles the JXCollapsiblePane state and updates its icon based on the 255 * JXCollapsiblePane "collapsed" status. 256 */ 257 private class ToggleAction extends AbstractAction implements 258 PropertyChangeListener { 259 public ToggleAction() { 260 super(TOGGLE_ACTION); 261 // the action must track the collapsed status of the pane to update its 262 // icon 263 JXCollapsiblePane.this.addPropertyChangeListener("collapsed", this); 264 } 265 266 @Override 267 public void putValue(String key, Object newValue) { 268 super.putValue(key, newValue); 269 if (EXPAND_ICON.equals(key) || COLLAPSE_ICON.equals(key)) { 270 updateIcon(); 271 } 272 } 273 274 public void actionPerformed(ActionEvent e) { 275 setCollapsed(!isCollapsed()); 276 } 277 278 public void propertyChange(PropertyChangeEvent evt) { 279 updateIcon(); 280 } 281 282 void updateIcon() { 283 if (isCollapsed()) { 284 putValue(SMALL_ICON, getValue(EXPAND_ICON)); 285 } else { 286 putValue(SMALL_ICON, getValue(COLLAPSE_ICON)); 287 } 288 } 289 } 290 291 /** 292 * Sets the content pane of this JXCollapsiblePane. 293 * 294 * @param contentPanel 295 * @throws IllegalArgumentException 296 * if contentPanel is null 297 */ 298 public void setContentPane(Container contentPanel) { 299 if (contentPanel == null) { 300 throw new IllegalArgumentException("Content pane can't be null"); 301 } 302 303 if (wrapper != null) { 304 //these next two lines are as they are because if I try to remove 305 //the "wrapper" component directly, then super.remove(comp) ends up 306 //calling remove(int), which is overridden in this class, leading to 307 //improper behavior. 308 assert super.getComponent(0) == wrapper; 309 super.remove(0); 310 } 311 wrapper = new WrapperContainer(contentPanel); 312 super.addImpl(wrapper, BorderLayout.CENTER, -1); 313 } 314 315 /** 316 * @return the content pane 317 */ 318 public Container getContentPane() { 319 return wrapper.c; 320 } 321 322 /** 323 * Overriden to redirect call to the content pane. 324 */ 325 @Override 326 public void setLayout(LayoutManager mgr) { 327 // wrapper can be null when setLayout is called by "super()" constructor 328 if (wrapper != null) { 329 getContentPane().setLayout(mgr); 330 } 331 } 332 333 /** 334 * Overriden to redirect call to the content pane. 335 */ 336 @Override 337 protected void addImpl(Component comp, Object constraints, int index) { 338 getContentPane().add(comp, constraints, index); 339 } 340 341 /** 342 * Overriden to redirect call to the content pane 343 */ 344 @Override 345 public void remove(Component comp) { 346 getContentPane().remove(comp); 347 } 348 349 /** 350 * Overriden to redirect call to the content pane. 351 */ 352 @Override 353 public void remove(int index) { 354 getContentPane().remove(index); 355 } 356 357 /** 358 * Overriden to redirect call to the content pane. 359 */ 360 @Override 361 public void removeAll() { 362 getContentPane().removeAll(); 363 } 364 365 /** 366 * If true, enables the animation when pane is collapsed/expanded. If false, 367 * animation is turned off. 368 * 369 * <p> 370 * When animated, the <code>JXCollapsiblePane</code> will progressively 371 * reduce (when collapsing) or enlarge (when expanding) the height of its 372 * content area until it becomes 0 or until it reaches the preferred height of 373 * the components it contains. The transparency of the content area will also 374 * change during the animation. 375 * 376 * <p> 377 * If not animated, the <code>JXCollapsiblePane</code> will simply hide 378 * (collapsing) or show (expanding) its content area. 379 * 380 * @param animated 381 * @javabean.property bound="true" preferred="true" 382 */ 383 public void setAnimated(boolean animated) { 384 if (animated != useAnimation) { 385 useAnimation = animated; 386 firePropertyChange("animated", !useAnimation, useAnimation); 387 } 388 } 389 390 /** 391 * @return true if the pane is animated, false otherwise 392 * @see #setAnimated(boolean) 393 */ 394 public boolean isAnimated() { 395 return useAnimation; 396 } 397 398 /** 399 * Changes the orientation of this collapsible pane. Doing so changes the 400 * layout of the underlying content pane. If the chosen orientation is 401 * vertical, a vertical layout with a gap of 2 pixels is chosen. Otherwise, 402 * a horizontal layout with a gap of 2 pixels is chosen. 403 * 404 * @see #getOrientation() 405 * @param orientation the new {@link Orientation} for this collapsible pane 406 * @throws IllegalStateException when this method is called while a 407 * collapsing/restore operation is running 408 * @javabean.property 409 * bound="true" 410 * preferred="true" 411 */ 412 public void setOrientation(Orientation orientation) { 413 if (orientation != this.orientation) { 414 if (animateTimer.isRunning()) { 415 throw new IllegalStateException("Orientation cannot be changed " + 416 "during collapsing."); 417 } 418 419 this.orientation = orientation; 420 421 if (orientation == Orientation.VERTICAL) { 422 getContentPane().setLayout(new VerticalLayout(2)); 423 } else { 424 getContentPane().setLayout(new HorizontalLayout(2)); 425 } 426 } 427 } 428 429 /** 430 * @see #setOrientation(Orientation) 431 * @return the current {@link Orientation} 432 */ 433 public Orientation getOrientation() { 434 return orientation; 435 } 436 437 /** 438 * @return true if the pane is collapsed, false if expanded 439 */ 440 public boolean isCollapsed() { 441 return collapsed; 442 } 443 444 /** 445 * Expands or collapses this <code>JXCollapsiblePane</code>. 446 * 447 * <p> 448 * If the component is collapsed and <code>val</code> is false, then this 449 * call expands the JXCollapsiblePane, such that the entire JXCollapsiblePane 450 * will be visible. If {@link #isAnimated()} returns true, the expansion will 451 * be accompanied by an animation. 452 * 453 * <p> 454 * However, if the component is expanded and <code>val</code> is true, then 455 * this call collapses the JXCollapsiblePane, such that the entire 456 * JXCollapsiblePane will be invisible. If {@link #isAnimated()} returns true, 457 * the collapse will be accompanied by an animation. 458 * 459 * @see #isAnimated() 460 * @see #setAnimated(boolean) 461 * @javabean.property 462 * bound="true" 463 * preferred="true" 464 */ 465 public void setCollapsed(boolean val) { 466 if (collapsed != val) { 467 collapsed = val; 468 if (isAnimated()) { 469 if (collapsed) { 470 int dimension = orientation == Orientation.VERTICAL ? 471 wrapper.getHeight() : wrapper.getWidth(); 472 setAnimationParams(new AnimationParams(30, 473 Math.max(8, dimension / 10), 1.0f, 0.01f)); 474 animator.reinit(dimension, 0); 475 animateTimer.start(); 476 } else { 477 int dimension = orientation == Orientation.VERTICAL ? 478 wrapper.getHeight() : wrapper.getWidth(); 479 int preferredDimension = orientation == Orientation.VERTICAL ? 480 getContentPane().getPreferredSize().height : 481 getContentPane().getPreferredSize().width; 482 int delta = Math.max(8, preferredDimension / 10); 483 484 setAnimationParams(new AnimationParams(30, delta, 0.01f, 1.0f)); 485 animator.reinit(dimension, preferredDimension); 486 animateTimer.start(); 487 } 488 } else { 489 wrapper.c.setVisible(!collapsed); 490 invalidate(); 491 doLayout(); 492 } 493 repaint(); 494 firePropertyChange("collapsed", !collapsed, collapsed); 495 } 496 } 497 498 @Override 499 public Dimension getMinimumSize() { 500 return getPreferredSize(); 501 } 502 503 /** 504 * The critical part of the animation of this <code>JXCollapsiblePane</code> 505 * relies on the calculation of its preferred size. During the animation, its 506 * preferred size (specially its height) will change, when expanding, from 0 507 * to the preferred size of the content pane, and the reverse when collapsing. 508 * 509 * @return this component preferred size 510 */ 511 @Override 512 public Dimension getPreferredSize() { 513 /* 514 * The preferred size is calculated based on the current position of the 515 * component in its animation sequence. If the Component is expanded, then 516 * the preferred size will be the preferred size of the top component plus 517 * the preferred size of the embedded content container. <p>However, if the 518 * scroll up is in any state of animation, the height component of the 519 * preferred size will be the current height of the component (as contained 520 * in the currentDimension variable and when orientation is VERTICAL, otherwise 521 * the same applies to the width) 522 */ 523 Dimension dim; 524 if (!isAnimated()) { 525 if (getContentPane().isVisible()) { 526 dim = getContentPane().getPreferredSize(); 527 } else { 528 dim = super.getPreferredSize(); 529 } 530 } else { 531 dim = new Dimension(getContentPane().getPreferredSize()); 532 if (!getContentPane().isVisible() && currentDimension != -1) { 533 if (orientation == Orientation.VERTICAL) { 534 dim.height = currentDimension; 535 } else { 536 dim.width = currentDimension; 537 } 538 } 539 } 540 return dim; 541 } 542 543 /** 544 * Sets the parameters controlling the animation 545 * 546 * @param params 547 * @throws IllegalArgumentException 548 * if params is null 549 */ 550 private void setAnimationParams(AnimationParams params) { 551 if (params == null) { throw new IllegalArgumentException( 552 "params can't be null"); } 553 if (animateTimer != null) { 554 animateTimer.stop(); 555 } 556 animationParams = params; 557 animateTimer = new Timer(animationParams.waitTime, animator); 558 animateTimer.setInitialDelay(0); 559 } 560 561 /** 562 * Tagging interface for containers in a JXCollapsiblePane hierarchy who needs 563 * to be revalidated (invalidate/validate/repaint) when the pane is expanding 564 * or collapsing. Usually validating only the parent of the JXCollapsiblePane 565 * is enough but there might be cases where the parent parent must be 566 * validated. 567 */ 568 public static interface JCollapsiblePaneContainer { 569 Container getValidatingContainer(); 570 } 571 572 /** 573 * Parameters controlling the animations 574 */ 575 private static class AnimationParams { 576 final int waitTime; 577 final int delta; 578 final float alphaStart; 579 final float alphaEnd; 580 581 /** 582 * @param waitTime 583 * the amount of time in milliseconds to wait between calls to the 584 * animation thread 585 * @param delta 586 * the delta, in the direction as specified by the orientation, 587 * to inc/dec the size of the scroll up by 588 * @param alphaStart 589 * the starting alpha transparency level 590 * @param alphaEnd 591 * the ending alpha transparency level 592 */ 593 public AnimationParams(int waitTime, int delta, float alphaStart, 594 float alphaEnd) { 595 this.waitTime = waitTime; 596 this.delta = delta; 597 this.alphaStart = alphaStart; 598 this.alphaEnd = alphaEnd; 599 } 600 } 601 602 /** 603 * This class actual provides the animation support for scrolling up/down this 604 * component. This listener is called whenever the animateTimer fires off. It 605 * fires off in response to scroll up/down requests. This listener is 606 * responsible for modifying the size of the content container and causing it 607 * to be repainted. 608 * 609 * @author Richard Bair 610 */ 611 private final class AnimationListener implements ActionListener { 612 /** 613 * Mutex used to ensure that the startDimension/finalDimension are not changed 614 * during a repaint operation. 615 */ 616 private final Object ANIMATION_MUTEX = "Animation Synchronization Mutex"; 617 /** 618 * This is the starting dimension when animating. If > finalDimension, then the 619 * animation is going to be to scroll up the component. If it is less than 620 * finalDimension, then the animation will scroll down the component. 621 */ 622 private int startDimension = 0; 623 /** 624 * This is the final dimension that the content container is going to be when 625 * scrolling is finished. 626 */ 627 private int finalDimension = 0; 628 /** 629 * The current alpha setting used during "animation" (fade-in/fade-out) 630 */ 631 @SuppressWarnings({"FieldCanBeLocal"}) 632 private float animateAlpha = 1.0f; 633 634 public void actionPerformed(ActionEvent e) { 635 /* 636 * Pre-1) If startDimension == finalDimension, then we're done so stop the timer 637 * 1) Calculate whether we're contracting or expanding. 2) Calculate the 638 * delta (which is either positive or negative, depending on the results 639 * of (1)) 3) Calculate the alpha value 4) Resize the ContentContainer 5) 640 * Revalidate/Repaint the content container 641 */ 642 synchronized (ANIMATION_MUTEX) { 643 if (startDimension == finalDimension) { 644 animateTimer.stop(); 645 animateAlpha = animationParams.alphaEnd; 646 // keep the content pane hidden when it is collapsed, other it may 647 // still receive focus. 648 if (finalDimension > 0) { 649 wrapper.showContent(); 650 validate(); 651 JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null, 652 "expanded"); 653 return; 654 } else { 655 JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null, 656 "collapsed"); 657 } 658 } 659 660 final boolean contracting = startDimension > finalDimension; 661 final int delta = contracting?-1 * animationParams.delta 662 :animationParams.delta; 663 int newDimension; 664 if (orientation == Orientation.VERTICAL) { 665 newDimension = wrapper.getHeight() + delta; 666 } else { 667 newDimension = wrapper.getWidth() + delta; 668 } 669 if (contracting) { 670 if (newDimension < finalDimension) { 671 newDimension = finalDimension; 672 } 673 } else { 674 if (newDimension > finalDimension) { 675 newDimension = finalDimension; 676 } 677 } 678 int dimension; 679 if (orientation == Orientation.VERTICAL) { 680 dimension = wrapper.c.getPreferredSize().height; 681 } else { 682 dimension = wrapper.c.getPreferredSize().width; 683 } 684 animateAlpha = (float)newDimension / (float)dimension; 685 686 Rectangle bounds = wrapper.getBounds(); 687 688 if (orientation == Orientation.VERTICAL) { 689 int oldHeight = bounds.height; 690 bounds.height = newDimension; 691 wrapper.setBounds(bounds); 692 bounds = getBounds(); 693 bounds.height = (bounds.height - oldHeight) + newDimension; 694 currentDimension = bounds.height; 695 } else { 696 int oldWidth = bounds.width; 697 bounds.width = newDimension; 698 wrapper.setBounds(bounds); 699 bounds = getBounds(); 700 bounds.width = (bounds.width - oldWidth) + newDimension; 701 currentDimension = bounds.width; 702 } 703 704 setBounds(bounds); 705 startDimension = newDimension; 706 707 // it happens the animateAlpha goes over the alphaStart/alphaEnd range 708 // this code ensures it stays in bounds. This behavior is seen when 709 // component such as JTextComponents are used in the container. 710 if (contracting) { 711 // alphaStart > animateAlpha > alphaEnd 712 if (animateAlpha < animationParams.alphaEnd) { 713 animateAlpha = animationParams.alphaEnd; 714 } 715 if (animateAlpha > animationParams.alphaStart) { 716 animateAlpha = animationParams.alphaStart; 717 } 718 } else { 719 // alphaStart < animateAlpha < alphaEnd 720 if (animateAlpha > animationParams.alphaEnd) { 721 animateAlpha = animationParams.alphaEnd; 722 } 723 if (animateAlpha < animationParams.alphaStart) { 724 animateAlpha = animationParams.alphaStart; 725 } 726 } 727 wrapper.alpha = animateAlpha; 728 729 validate(); 730 } 731 } 732 733 void validate() { 734 Container parent = SwingUtilities.getAncestorOfClass( 735 JCollapsiblePaneContainer.class, JXCollapsiblePane.this); 736 if (parent != null) { 737 parent = ((JCollapsiblePaneContainer)parent).getValidatingContainer(); 738 } else { 739 parent = getParent(); 740 } 741 742 if (parent != null) { 743 if (parent instanceof JComponent) { 744 ((JComponent)parent).revalidate(); 745 } else { 746 parent.invalidate(); 747 } 748 parent.doLayout(); 749 parent.repaint(); 750 } 751 } 752 753 /** 754 * Reinitializes the timer for scrolling up/down the component. This method 755 * is properly synchronized, so you may make this call regardless of whether 756 * the timer is currently executing or not. 757 * 758 * @param startDimension 759 * @param stopDimension 760 */ 761 public void reinit(int startDimension, int stopDimension) { 762 synchronized (ANIMATION_MUTEX) { 763 JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null, 764 "reinit"); 765 this.startDimension = startDimension; 766 this.finalDimension = stopDimension; 767 animateAlpha = animationParams.alphaStart; 768 currentDimension = -1; 769 wrapper.showImage(); 770 } 771 } 772 } 773 774 private final class WrapperContainer extends JXPanel { 775 private BufferedImage img; 776 private Container c; 777 float alpha = 1.0f; 778 779 public WrapperContainer(Container c) { 780 super(new BorderLayout()); 781 this.c = c; 782 add(c, BorderLayout.CENTER); 783 784 // we must ensure the container is opaque. It is not opaque it introduces 785 // painting glitches specially on Linux with JDK 1.5 and GTK look and feel. 786 // GTK look and feel calls setOpaque(false) 787 if (c instanceof JComponent && !c.isOpaque()) { 788 ((JComponent) c).setOpaque(true); 789 } 790 } 791 792 public void showImage() { 793 // render c into the img 794 makeImage(); 795 c.setVisible(false); 796 } 797 798 public void showContent() { 799 currentDimension = -1; 800 c.setVisible(true); 801 } 802 803 void makeImage() { 804 // if we have no image or if the image has changed 805 if (getGraphicsConfiguration() != null && getWidth() > 0) { 806 Dimension dim = c.getPreferredSize(); 807 // width and height must be > 0 to be able to create an image 808 int dimension; 809 int width; 810 int height; 811 812 if (orientation == Orientation.VERTICAL) { 813 width = getWidth(); 814 dimension = height = dim.height; 815 } else { 816 height = getHeight(); 817 dimension = width = dim.width; 818 } 819 820 if (dimension > 0) { 821 img = getGraphicsConfiguration().createCompatibleImage(width, 822 height); 823 c.setSize(width, height); 824 c.paint(img.getGraphics()); 825 } else { 826 img = null; 827 } 828 } 829 } 830 831 @Override 832 protected void paintComponent(Graphics g) { 833 if (!useAnimation || c.isVisible()) { 834 super.paintComponent(g); 835 } else { 836 // within netbeans, it happens we arrive here and the image has not been 837 // created yet. We ensure it is. 838 if (img == null) { 839 makeImage(); 840 } 841 // and we paint it only if it has been created and only if we have a 842 // valid graphics 843 if (g != null && img != null) { 844 // draw the image with y being height - imageHeight 845 if (orientation == Orientation.VERTICAL) { 846 g.drawImage(img, 0, getHeight() - img.getHeight(), null); 847 } else { 848 g.drawImage(img, getWidth() - img.getWidth(), 0, null); 849 } 850 } 851 } 852 } 853 854 @Override 855 public void paint(Graphics g) { 856 Graphics2D g2d = (Graphics2D)g; 857 Composite oldComp = g2d.getComposite(); 858 Composite alphaComp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 859 alpha); 860 g2d.setComposite(alphaComp); 861 super.paint(g2d); 862 g2d.setComposite(oldComp); 863 } 864 865 } 866 867 // TEST CASE 868 // public static void main(String[] args) { 869 // SwingUtilities.invokeLater(new Runnable() { 870 // public void run() { 871 // JFrame f = new JFrame("Test Oriented Collapsible Pane"); 872 // 873 // f.add(new JLabel("Press Ctrl+F or Ctrl+G to collapse panes."), 874 // BorderLayout.NORTH); 875 // 876 // JTree tree1 = new JTree(); 877 // tree1.setBorder(BorderFactory.createEtchedBorder()); 878 // f.add(tree1); 879 // 880 // JXCollapsiblePane pane = new JXCollapsiblePane(Orientation.VERTICAL); 881 // JTree tree2 = new JTree(); 882 // tree2.setBorder(BorderFactory.createEtchedBorder()); 883 // pane.add(tree2); 884 // f.add(pane, BorderLayout.SOUTH); 885 // 886 // pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 887 // KeyStroke.getKeyStroke("ctrl F"), 888 // JXCollapsiblePane.TOGGLE_ACTION); 889 // 890 // pane = new JXCollapsiblePane(Orientation.HORIZONTAL); 891 // JTree tree3 = new JTree(); 892 // pane.add(tree3); 893 // tree3.setBorder(BorderFactory.createEtchedBorder()); 894 // f.add(pane, BorderLayout.WEST); 895 // 896 // pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 897 // KeyStroke.getKeyStroke("ctrl G"), 898 // JXCollapsiblePane.TOGGLE_ACTION); 899 // 900 // f.setSize(640, 480); 901 // f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 902 // f.setVisible(true); 903 // } 904 // }); 905 // } 906 }