2
* $Id: JXCollapsiblePane.java,v 1.23 2008/02/29 02:21:13 kschaefe Exp $
4
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5
* Santa Clara, California 95054, U.S.A. All rights reserved.
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.
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.
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
21
package org.jdesktop.swingx;
23
import java.awt.BorderLayout;
24
import java.awt.Component;
25
import java.awt.Container;
26
import java.awt.Dimension;
27
import java.awt.LayoutManager;
28
import java.awt.Point;
29
import java.awt.Rectangle;
30
import java.awt.event.ActionEvent;
31
import java.awt.event.ActionListener;
32
import java.beans.PropertyChangeEvent;
33
import java.beans.PropertyChangeListener;
35
import javax.swing.AbstractAction;
36
import javax.swing.JComponent;
37
import javax.swing.JPanel;
38
import javax.swing.JViewport;
39
import javax.swing.Scrollable;
40
import javax.swing.SwingUtilities;
41
import javax.swing.Timer;
42
import javax.swing.border.Border;
45
* <code>JXCollapsiblePane</code> provides a component which can collapse or
46
* expand its content area with animation and fade in/fade out effects.
47
* It also acts as a standard container for other Swing components.
50
* In this example, the <code>JXCollapsiblePane</code> is used to build
51
* a Search pane which can be shown and hidden on demand.
55
* JXCollapsiblePane cp = new JXCollapsiblePane();
57
* // JXCollapsiblePane can be used like any other container
58
* cp.setLayout(new BorderLayout());
60
* // the Controls panel with a textfield to filter the tree
61
* JPanel controls = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
62
* controls.add(new JLabel("Search:"));
63
* controls.add(new JTextField(10));
64
* controls.add(new JButton("Refresh"));
65
* controls.setBorder(new TitledBorder("Filters"));
66
* cp.add("Center", controls);
68
* JXFrame frame = new JXFrame();
69
* frame.setLayout(new BorderLayout());
71
* // Put the "Controls" first
72
* frame.add("North", cp);
74
* // Then the tree - we assume the Controls would somehow filter the tree
75
* JScrollPane scroll = new JScrollPane(new JTree());
76
* frame.add("Center", scroll);
78
* // Show/hide the "Controls"
79
* JButton toggle = new JButton(cp.getActionMap().get(JXCollapsiblePane.TOGGLE_ACTION));
80
* toggle.setText("Show/Hide Search Panel");
81
* frame.add("South", toggle);
84
* frame.setVisible(true);
89
* The <code>JXCollapsiblePane</code> has a default toggle action registered
90
* under the name {@link #TOGGLE_ACTION}. Bind this action to a button and
91
* pressing the button will automatically toggle the pane between expanded
92
* and collapsed states. Additionally, you can define the icons to use through
93
* the {@link #EXPAND_ICON} and {@link #COLLAPSE_ICON} properties on the action.
97
* // get the built-in toggle action
98
* Action toggleAction = collapsible.getActionMap().
99
* get(JXCollapsiblePane.TOGGLE_ACTION);
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"));
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}).
115
* @javabean.attribute
117
* value="Boolean.TRUE"
120
* @javabean.attribute
121
* name="containerDelegate"
122
* value="getContentPane"
125
* name="JXCollapsiblePane"
126
* shortDescription="A pane which hides its content with an animation."
127
* stopClass="java.awt.Component"
129
* @author rbair (from the JDNC project)
130
* @author <a href="mailto:fred@L2FProd.com">Frederic Lavigne</a>
132
public class JXCollapsiblePane extends JXPanel {
134
* The orientation defines in which direction the collapsible pane will
137
public enum Orientation {
139
* The horizontal orientation makes the collapsible pane
140
* expand horizontally
144
* The horizontal orientation makes the collapsible pane
151
* Used when generating PropertyChangeEvents for the "animationState"
152
* property. The PropertyChangeEvent will takes the following different values
153
* for {@link PropertyChangeEvent#getNewValue()}:
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
160
public final static String ANIMATION_STATE_KEY = "animationState";
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>.
167
public final static String TOGGLE_ACTION = "toggle";
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.
173
public final static String COLLAPSE_ICON = "collapseIcon";
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.
179
public final static String EXPAND_ICON = "expandIcon";
182
* Indicates whether the component is collapsed or expanded
184
private boolean collapsed = false;
187
* Defines the orientation of the component.
189
private Orientation orientation = Orientation.VERTICAL;
192
* Timer used for doing the transparency animation (fade-in)
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;
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.
206
public JXCollapsiblePane() {
207
this(Orientation.VERTICAL, new BorderLayout(0, 0));
211
* Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane
212
* and the specified orientation.
214
public JXCollapsiblePane(Orientation orientation) {
215
this(orientation, new BorderLayout(0, 0));
219
* Constructs a new JXCollapsiblePane with a {@link JPanel} as content pane
220
* and the given LayoutManager and a vertical orientation
222
public JXCollapsiblePane(LayoutManager layout) {
223
this(Orientation.VERTICAL, layout);
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
233
public JXCollapsiblePane(Orientation orientation, LayoutManager layout) {
234
super.setLayout(layout);
236
this.orientation = orientation;
238
JXPanel panel = new JXPanel();
239
if (orientation == Orientation.VERTICAL) {
240
panel.setLayout(new VerticalLayout(2));
242
panel.setLayout(new HorizontalLayout(2));
244
setContentPane(panel);
246
animator = new AnimationListener();
247
setAnimationParams(new AnimationParams(30, 8, 0.01f, 1.0f));
249
// add an action to automatically toggle the state of the pane
250
getActionMap().put(TOGGLE_ACTION, new ToggleAction());
254
* Toggles the JXCollapsiblePane state and updates its icon based on the
255
* JXCollapsiblePane "collapsed" status.
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
263
JXCollapsiblePane.this.addPropertyChangeListener("collapsed", this);
267
public void putValue(String key, Object newValue) {
268
super.putValue(key, newValue);
269
if (EXPAND_ICON.equals(key) || COLLAPSE_ICON.equals(key)) {
274
public void actionPerformed(ActionEvent e) {
275
setCollapsed(!isCollapsed());
278
public void propertyChange(PropertyChangeEvent evt) {
284
putValue(SMALL_ICON, getValue(EXPAND_ICON));
286
putValue(SMALL_ICON, getValue(COLLAPSE_ICON));
292
* Sets the content pane of this JXCollapsiblePane. The {@code contentPanel}
293
* <i>should</i> implement {@code Scrollable} and return {@code true} from
294
* {@link Scrollable#getScrollableTracksViewportHeight()} and
295
* {@link Scrollable#getScrollableTracksViewportWidth()}. If the content
296
* pane fails to do so and a {@code JScrollPane} is added as a child, it is
297
* likely that the scroll pane will never correctly size. While it is not
298
* strictly necessary to implement {@code Scrollable} in this way, the
299
* default content pane does so.
301
* @param contentPanel
302
* the container delegate used to hold all of the contents
303
* for this collapsible pane
304
* @throws IllegalArgumentException
305
* if contentPanel is null
307
public void setContentPane(Container contentPanel) {
308
if (contentPanel == null) {
309
throw new IllegalArgumentException("Content pane can't be null");
312
if (wrapper != null) {
313
//these next two lines are as they are because if I try to remove
314
//the "wrapper" component directly, then super.remove(comp) ends up
315
//calling remove(int), which is overridden in this class, leading to
317
assert super.getComponent(0) == wrapper;
320
wrapper = new WrapperContainer(contentPanel);
321
wrapper.collapsedState = isCollapsed();
322
super.addImpl(wrapper, BorderLayout.CENTER, -1);
326
* @return the content pane
328
public Container getContentPane() {
329
if (wrapper == null) {
333
return (Container) wrapper.getView();
337
* Overriden to redirect call to the content pane.
340
public void setLayout(LayoutManager mgr) {
341
// wrapper can be null when setLayout is called by "super()" constructor
342
if (wrapper != null) {
343
getContentPane().setLayout(mgr);
348
* Overriden to redirect call to the content pane.
351
protected void addImpl(Component comp, Object constraints, int index) {
352
getContentPane().add(comp, constraints, index);
356
* Overriden to redirect call to the content pane
359
public void remove(Component comp) {
360
getContentPane().remove(comp);
364
* Overriden to redirect call to the content pane.
367
public void remove(int index) {
368
getContentPane().remove(index);
372
* Overriden to redirect call to the content pane.
375
public void removeAll() {
376
getContentPane().removeAll();
380
* If true, enables the animation when pane is collapsed/expanded. If false,
381
* animation is turned off.
384
* When animated, the <code>JXCollapsiblePane</code> will progressively
385
* reduce (when collapsing) or enlarge (when expanding) the height of its
386
* content area until it becomes 0 or until it reaches the preferred height of
387
* the components it contains. The transparency of the content area will also
388
* change during the animation.
391
* If not animated, the <code>JXCollapsiblePane</code> will simply hide
392
* (collapsing) or show (expanding) its content area.
395
* @javabean.property bound="true" preferred="true"
397
public void setAnimated(boolean animated) {
398
if (animated != useAnimation) {
399
useAnimation = animated;
400
firePropertyChange("animated", !useAnimation, useAnimation);
405
* @return true if the pane is animated, false otherwise
406
* @see #setAnimated(boolean)
408
public boolean isAnimated() {
413
* Changes the orientation of this collapsible pane. Doing so changes the
414
* layout of the underlying content pane. If the chosen orientation is
415
* vertical, a vertical layout with a gap of 2 pixels is chosen. Otherwise,
416
* a horizontal layout with a gap of 2 pixels is chosen.
418
* @see #getOrientation()
419
* @param orientation the new {@link Orientation} for this collapsible pane
420
* @throws IllegalStateException when this method is called while a
421
* collapsing/restore operation is running
426
public void setOrientation(Orientation orientation) {
427
if (orientation != this.orientation) {
428
if (animateTimer.isRunning()) {
429
throw new IllegalStateException("Orientation cannot be changed " +
430
"during collapsing.");
433
this.orientation = orientation;
435
if (orientation == Orientation.VERTICAL) {
436
getContentPane().setLayout(new VerticalLayout(2));
438
getContentPane().setLayout(new HorizontalLayout(2));
444
* @see #setOrientation(Orientation)
445
* @return the current {@link Orientation}
447
public Orientation getOrientation() {
452
* @return true if the pane is collapsed, false if expanded
454
public boolean isCollapsed() {
459
* Expands or collapses this <code>JXCollapsiblePane</code>.
462
* If the component is collapsed and <code>val</code> is false, then this
463
* call expands the JXCollapsiblePane, such that the entire JXCollapsiblePane
464
* will be visible. If {@link #isAnimated()} returns true, the expansion will
465
* be accompanied by an animation.
468
* However, if the component is expanded and <code>val</code> is true, then
469
* this call collapses the JXCollapsiblePane, such that the entire
470
* JXCollapsiblePane will be invisible. If {@link #isAnimated()} returns true,
471
* the collapse will be accompanied by an animation.
474
* @see #setAnimated(boolean)
479
public void setCollapsed(boolean val) {
480
if (collapsed != val) {
484
int dimension = orientation == Orientation.VERTICAL ?
485
wrapper.getHeight() : wrapper.getWidth();
486
setAnimationParams(new AnimationParams(30,
487
Math.max(8, dimension / 10), 1.0f, 0.01f));
488
animator.reinit(dimension, 0);
489
animateTimer.start();
491
int dimension = orientation == Orientation.VERTICAL ?
492
wrapper.getHeight() : wrapper.getWidth();
493
int preferredDimension = orientation == Orientation.VERTICAL ?
494
getContentPane().getPreferredSize().height :
495
getContentPane().getPreferredSize().width;
496
int delta = Math.max(8, preferredDimension / 10);
498
setAnimationParams(new AnimationParams(30, delta, 0.01f, 1.0f));
499
animator.reinit(dimension, preferredDimension);
500
animateTimer.start();
503
wrapper.collapsedState = collapsed;
507
firePropertyChange("collapsed", !collapsed, collapsed);
514
public Border getBorder() {
515
if (getContentPane() instanceof JComponent) {
516
return ((JComponent) getContentPane()).getBorder();
525
public void setBorder(Border border) {
526
if (getContentPane() instanceof JComponent) {
527
((JComponent) getContentPane()).setBorder(border);
535
public Dimension getMinimumSize() {
536
return getPreferredSize();
540
* The critical part of the animation of this <code>JXCollapsiblePane</code>
541
* relies on the calculation of its preferred size. During the animation, its
542
* preferred size (specially its height) will change, when expanding, from 0
543
* to the preferred size of the content pane, and the reverse when collapsing.
545
* @return this component preferred size
548
public Dimension getPreferredSize() {
550
* The preferred size is calculated based on the current position of the
551
* component in its animation sequence. If the Component is expanded, then
552
* the preferred size will be the preferred size of the top component plus
553
* the preferred size of the embedded content container. <p>However, if the
554
* scroll up is in any state of animation, the height component of the
555
* preferred size will be the current height of the component (as contained
556
* in the currentDimension variable and when orientation is VERTICAL, otherwise
557
* the same applies to the width)
559
Dimension dim = getContentPane().getPreferredSize();
560
if (currentDimension != -1) {
561
if (orientation == Orientation.VERTICAL) {
562
dim.height = currentDimension;
564
dim.width = currentDimension;
566
} else if(wrapper.collapsedState) {
567
if (orientation == Orientation.VERTICAL) {
577
public void setPreferredSize(Dimension preferredSize) {
578
getContentPane().setPreferredSize(preferredSize);
582
* Sets the parameters controlling the animation
585
* @throws IllegalArgumentException
588
private void setAnimationParams(AnimationParams params) {
589
if (params == null) { throw new IllegalArgumentException(
590
"params can't be null"); }
591
if (animateTimer != null) {
594
animationParams = params;
595
animateTimer = new Timer(animationParams.waitTime, animator);
596
animateTimer.setInitialDelay(0);
600
* Tagging interface for containers in a JXCollapsiblePane hierarchy who needs
601
* to be revalidated (invalidate/validate/repaint) when the pane is expanding
602
* or collapsing. Usually validating only the parent of the JXCollapsiblePane
603
* is enough but there might be cases where the parent parent must be
606
public static interface CollapsiblePaneContainer {
607
Container getValidatingContainer();
611
* Parameters controlling the animations
613
private static class AnimationParams {
616
final float alphaStart;
617
final float alphaEnd;
621
* the amount of time in milliseconds to wait between calls to the
624
* the delta, in the direction as specified by the orientation,
625
* to inc/dec the size of the scroll up by
627
* the starting alpha transparency level
629
* the ending alpha transparency level
631
public AnimationParams(int waitTime, int delta, float alphaStart,
633
this.waitTime = waitTime;
635
this.alphaStart = alphaStart;
636
this.alphaEnd = alphaEnd;
641
* This class actual provides the animation support for scrolling up/down this
642
* component. This listener is called whenever the animateTimer fires off. It
643
* fires off in response to scroll up/down requests. This listener is
644
* responsible for modifying the size of the content container and causing it
647
* @author Richard Bair
649
private final class AnimationListener implements ActionListener {
651
* Mutex used to ensure that the startDimension/finalDimension are not changed
652
* during a repaint operation.
654
private final Object ANIMATION_MUTEX = "Animation Synchronization Mutex";
656
* This is the starting dimension when animating. If > finalDimension, then the
657
* animation is going to be to scroll up the component. If it is less than
658
* finalDimension, then the animation will scroll down the component.
660
private int startDimension = 0;
662
* This is the final dimension that the content container is going to be when
663
* scrolling is finished.
665
private int finalDimension = 0;
667
* The current alpha setting used during "animation" (fade-in/fade-out)
669
@SuppressWarnings({"FieldCanBeLocal"})
670
private float animateAlpha = 1.0f;
672
public void actionPerformed(ActionEvent e) {
674
* Pre-1) If startDimension == finalDimension, then we're done so stop the timer
675
* 1) Calculate whether we're contracting or expanding. 2) Calculate the
676
* delta (which is either positive or negative, depending on the results
677
* of (1)) 3) Calculate the alpha value 4) Resize the ContentContainer 5)
678
* Revalidate/Repaint the content container
680
synchronized (ANIMATION_MUTEX) {
681
if (startDimension == finalDimension) {
683
animateAlpha = animationParams.alphaEnd;
684
// keep the content pane hidden when it is collapsed, other it may
685
// still receive focus.
686
if (finalDimension > 0) {
687
currentDimension = -1;
688
wrapper.collapsedState = false;
690
JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
694
wrapper.collapsedState = true;
695
JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
700
final boolean contracting = startDimension > finalDimension;
701
final int delta = contracting?-1 * animationParams.delta
702
:animationParams.delta;
704
if (orientation == Orientation.VERTICAL) {
705
newDimension = wrapper.getHeight() + delta;
707
newDimension = wrapper.getWidth() + delta;
710
if (newDimension < finalDimension) {
711
newDimension = finalDimension;
714
if (newDimension > finalDimension) {
715
newDimension = finalDimension;
719
if (orientation == Orientation.VERTICAL) {
720
dimension = wrapper.getView().getPreferredSize().height;
722
dimension = wrapper.getView().getPreferredSize().width;
724
animateAlpha = (float)newDimension / (float)dimension;
726
Rectangle bounds = wrapper.getBounds();
728
if (orientation == Orientation.VERTICAL) {
729
int oldHeight = bounds.height;
730
bounds.height = newDimension;
731
wrapper.setBounds(bounds);
732
wrapper.setViewPosition(new Point(0, wrapper.getView().getPreferredSize().height - newDimension));
733
bounds = getBounds();
734
bounds.height = (bounds.height - oldHeight) + newDimension;
735
currentDimension = bounds.height;
737
int oldWidth = bounds.width;
738
bounds.width = newDimension;
739
wrapper.setBounds(bounds);
740
wrapper.setViewPosition(new Point(wrapper.getView().getPreferredSize().width - newDimension, 0));
741
bounds = getBounds();
742
bounds.width = (bounds.width - oldWidth) + newDimension;
743
currentDimension = bounds.width;
747
startDimension = newDimension;
749
// it happens the animateAlpha goes over the alphaStart/alphaEnd range
750
// this code ensures it stays in bounds. This behavior is seen when
751
// component such as JTextComponents are used in the container.
753
// alphaStart > animateAlpha > alphaEnd
754
if (animateAlpha < animationParams.alphaEnd) {
755
animateAlpha = animationParams.alphaEnd;
757
if (animateAlpha > animationParams.alphaStart) {
758
animateAlpha = animationParams.alphaStart;
761
// alphaStart < animateAlpha < alphaEnd
762
if (animateAlpha > animationParams.alphaEnd) {
763
animateAlpha = animationParams.alphaEnd;
765
if (animateAlpha < animationParams.alphaStart) {
766
animateAlpha = animationParams.alphaStart;
769
wrapper.alpha = animateAlpha;
776
Container parent = SwingUtilities.getAncestorOfClass(
777
CollapsiblePaneContainer.class, JXCollapsiblePane.this);
778
if (parent != null) {
779
parent = ((CollapsiblePaneContainer)parent).getValidatingContainer();
781
parent = getParent();
784
if (parent != null) {
785
if (parent instanceof JComponent) {
786
((JComponent)parent).revalidate();
796
* Reinitializes the timer for scrolling up/down the component. This method
797
* is properly synchronized, so you may make this call regardless of whether
798
* the timer is currently executing or not.
800
* @param startDimension
801
* @param stopDimension
803
public void reinit(int startDimension, int stopDimension) {
804
synchronized (ANIMATION_MUTEX) {
805
JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
807
this.startDimension = startDimension;
808
this.finalDimension = stopDimension;
809
animateAlpha = animationParams.alphaStart;
810
currentDimension = -1;
815
private final class WrapperContainer extends JViewport {
817
boolean collapsedState;
819
public WrapperContainer(Container c) {
821
collapsedState = false;
824
// we must ensure the container is opaque. It is not opaque it introduces
825
// painting glitches specially on Linux with JDK 1.5 and GTK look and feel.
826
// GTK look and feel calls setOpaque(false)
827
if (c instanceof JComponent && !c.isOpaque()) {
828
((JComponent) c).setOpaque(true);
834
// public static void main(String[] args) {
835
// SwingUtilities.invokeLater(new Runnable() {
836
// public void run() {
837
// JFrame f = new JFrame("Test Oriented Collapsible Pane");
839
// f.add(new JLabel("Press Ctrl+F or Ctrl+G to collapse panes."),
840
// BorderLayout.NORTH);
842
// JTree tree1 = new JTree();
843
// tree1.setBorder(BorderFactory.createEtchedBorder());
846
// JXCollapsiblePane pane = new JXCollapsiblePane(Orientation.VERTICAL);
847
// pane.setCollapsed(true);
848
// JTree tree2 = new JTree();
849
// tree2.setBorder(BorderFactory.createEtchedBorder());
851
// f.add(pane, BorderLayout.SOUTH);
853
// pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
854
// KeyStroke.getKeyStroke("ctrl F"),
855
// JXCollapsiblePane.TOGGLE_ACTION);
857
// pane = new JXCollapsiblePane(Orientation.HORIZONTAL);
858
// JTree tree3 = new JTree();
860
// tree3.setBorder(BorderFactory.createEtchedBorder());
861
// f.add(pane, BorderLayout.WEST);
863
// pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
864
// KeyStroke.getKeyStroke("ctrl G"),
865
// JXCollapsiblePane.TOGGLE_ACTION);
867
// f.setSize(640, 480);
868
// f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
869
// f.setVisible(true);