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 }