~ubuntu-branches/ubuntu/utopic/libswingx-java/utopic

« back to all changes in this revision

Viewing changes to src/java/org/jdesktop/swingx/JXCollapsiblePane.java

  • Committer: Bazaar Package Importer
  • Author(s): Torsten Werner
  • Date: 2008-03-08 16:18:24 UTC
  • Revision ID: james.westby@ubuntu.com-20080308161824-wsahvl9pwzjcea3g
Tags: upstream-0.9.2
ImportĀ upstreamĀ versionĀ 0.9.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * $Id: JXCollapsiblePane.java,v 1.23 2008/02/29 02:21:13 kschaefe Exp $
 
3
 *
 
4
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 
5
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 
6
 *
 
7
 * This library is free software; you can redistribute it and/or
 
8
 * modify it under the terms of the GNU Lesser General Public
 
9
 * License as published by the Free Software Foundation; either
 
10
 * version 2.1 of the License, or (at your option) any later version.
 
11
 * 
 
12
 * This library is distributed in the hope that it will be useful,
 
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
15
 * Lesser General Public License for more details.
 
16
 * 
 
17
 * You should have received a copy of the GNU Lesser General Public
 
18
 * License along with this library; if not, write to the Free Software
 
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
20
 */
 
21
package org.jdesktop.swingx;
 
22
 
 
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;
 
34
 
 
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;
 
43
 
 
44
/**
 
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.
 
48
 *
 
49
 * <p>
 
50
 * In this example, the <code>JXCollapsiblePane</code> is used to build
 
51
 * a Search pane which can be shown and hidden on demand.
 
52
 *
 
53
 * <pre>
 
54
 * <code>
 
55
 * JXCollapsiblePane cp = new JXCollapsiblePane();
 
56
 *
 
57
 * // JXCollapsiblePane can be used like any other container
 
58
 * cp.setLayout(new BorderLayout());
 
59
 *
 
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);
 
67
 *
 
68
 * JXFrame frame = new JXFrame();
 
69
 * frame.setLayout(new BorderLayout());
 
70
 *
 
71
 * // Put the "Controls" first
 
72
 * frame.add("North", cp);
 
73
 *
 
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);
 
77
 *
 
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);
 
82
 *
 
83
 * frame.pack();
 
84
 * frame.setVisible(true);
 
85
 * </code>
 
86
 * </pre>
 
87
 *
 
88
 * <p>
 
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.
 
94
 * Example
 
95
 * <pre>
 
96
 * <code>
 
97
 * // get the built-in toggle action
 
98
 * Action toggleAction = collapsible.getActionMap().
 
99
 *   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
        JXPanel panel = new JXPanel();
 
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. 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.
 
300
     * 
 
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
 
306
     */
 
307
    public void setContentPane(Container contentPanel) {
 
308
        if (contentPanel == null) {
 
309
            throw new IllegalArgumentException("Content pane can't be null");
 
310
        }
 
311
 
 
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
 
316
            //improper behavior.
 
317
            assert super.getComponent(0) == wrapper;
 
318
            super.remove(0);
 
319
        }
 
320
        wrapper = new WrapperContainer(contentPanel);
 
321
        wrapper.collapsedState = isCollapsed();
 
322
        super.addImpl(wrapper, BorderLayout.CENTER, -1);
 
323
    }
 
324
 
 
325
    /**
 
326
     * @return the content pane
 
327
     */
 
328
    public Container getContentPane() {
 
329
        if (wrapper == null) {
 
330
            return null;
 
331
        }
 
332
        
 
333
        return (Container) wrapper.getView();
 
334
    }
 
335
 
 
336
    /**
 
337
     * Overriden to redirect call to the content pane.
 
338
     */
 
339
    @Override
 
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);
 
344
        }
 
345
    }
 
346
 
 
347
    /**
 
348
     * Overriden to redirect call to the content pane.
 
349
     */
 
350
    @Override
 
351
    protected void addImpl(Component comp, Object constraints, int index) {
 
352
        getContentPane().add(comp, constraints, index);
 
353
    }
 
354
 
 
355
    /**
 
356
     * Overriden to redirect call to the content pane
 
357
     */
 
358
    @Override
 
359
    public void remove(Component comp) {
 
360
        getContentPane().remove(comp);
 
361
    }
 
362
 
 
363
    /**
 
364
     * Overriden to redirect call to the content pane.
 
365
     */
 
366
    @Override
 
367
    public void remove(int index) {
 
368
        getContentPane().remove(index);
 
369
    }
 
370
 
 
371
    /**
 
372
     * Overriden to redirect call to the content pane.
 
373
     */
 
374
    @Override
 
375
    public void removeAll() {
 
376
        getContentPane().removeAll();
 
377
    }
 
378
 
 
379
    /**
 
380
     * If true, enables the animation when pane is collapsed/expanded. If false,
 
381
     * animation is turned off.
 
382
     *
 
383
     * <p>
 
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.
 
389
     *
 
390
     * <p>
 
391
     * If not animated, the <code>JXCollapsiblePane</code> will simply hide
 
392
     * (collapsing) or show (expanding) its content area.
 
393
     *
 
394
     * @param animated
 
395
     * @javabean.property bound="true" preferred="true"
 
396
     */
 
397
    public void setAnimated(boolean animated) {
 
398
        if (animated != useAnimation) {
 
399
            useAnimation = animated;
 
400
            firePropertyChange("animated", !useAnimation, useAnimation);
 
401
        }
 
402
    }
 
403
 
 
404
    /**
 
405
     * @return true if the pane is animated, false otherwise
 
406
     * @see #setAnimated(boolean)
 
407
     */
 
408
    public boolean isAnimated() {
 
409
        return useAnimation;
 
410
    }
 
411
 
 
412
    /**
 
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.
 
417
     *
 
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
 
422
     * @javabean.property
 
423
     *    bound="true"
 
424
     *    preferred="true"
 
425
     */
 
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.");
 
431
            }
 
432
 
 
433
            this.orientation = orientation;
 
434
 
 
435
            if (orientation == Orientation.VERTICAL) {
 
436
                getContentPane().setLayout(new VerticalLayout(2));
 
437
            } else {
 
438
                getContentPane().setLayout(new HorizontalLayout(2));
 
439
            }
 
440
        }
 
441
    }
 
442
    
 
443
    /**
 
444
     * @see #setOrientation(Orientation)
 
445
     * @return the current {@link Orientation}
 
446
     */
 
447
    public Orientation getOrientation() {
 
448
        return orientation;
 
449
    }
 
450
 
 
451
    /**
 
452
     * @return true if the pane is collapsed, false if expanded
 
453
     */
 
454
    public boolean isCollapsed() {
 
455
        return collapsed;
 
456
    }
 
457
 
 
458
    /**
 
459
     * Expands or collapses this <code>JXCollapsiblePane</code>.
 
460
     *
 
461
     * <p>
 
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.
 
466
     *
 
467
     * <p>
 
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.
 
472
     *
 
473
     * @see #isAnimated()
 
474
     * @see #setAnimated(boolean)
 
475
     * @javabean.property
 
476
     *    bound="true"
 
477
     *    preferred="true"
 
478
     */
 
479
    public void setCollapsed(boolean val) {
 
480
        if (collapsed != val) {
 
481
            collapsed = val;
 
482
            if (isAnimated()) {
 
483
                if (collapsed) {
 
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();
 
490
                } else {
 
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);
 
497
 
 
498
                    setAnimationParams(new AnimationParams(30, delta, 0.01f, 1.0f));
 
499
                    animator.reinit(dimension, preferredDimension);
 
500
                    animateTimer.start();
 
501
                }
 
502
            } else {
 
503
                wrapper.collapsedState = collapsed;
 
504
                revalidate();
 
505
            }
 
506
            repaint();
 
507
            firePropertyChange("collapsed", !collapsed, collapsed);
 
508
        }
 
509
    }
 
510
 
 
511
    /**
 
512
     * {@inheritDoc}
 
513
     */
 
514
    public Border getBorder() {
 
515
        if (getContentPane() instanceof JComponent) {
 
516
            return ((JComponent) getContentPane()).getBorder();
 
517
        }
 
518
        
 
519
        return null;
 
520
    }
 
521
    
 
522
    /**
 
523
     * {@inheritDoc}
 
524
     */
 
525
    public void setBorder(Border border) {
 
526
        if (getContentPane() instanceof JComponent) {
 
527
            ((JComponent) getContentPane()).setBorder(border);
 
528
        }
 
529
    }
 
530
    
 
531
    /**
 
532
     * {@inheritDoc}
 
533
     */
 
534
    @Override
 
535
    public Dimension getMinimumSize() {
 
536
        return getPreferredSize();
 
537
    }
 
538
 
 
539
    /**
 
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.
 
544
     *
 
545
     * @return this component preferred size
 
546
     */
 
547
    @Override
 
548
    public Dimension getPreferredSize() {
 
549
        /*
 
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)
 
558
         */
 
559
        Dimension dim = getContentPane().getPreferredSize();
 
560
        if (currentDimension != -1) {
 
561
                if (orientation == Orientation.VERTICAL) {
 
562
                    dim.height = currentDimension;
 
563
                } else {
 
564
                    dim.width = currentDimension;
 
565
                }
 
566
        } else if(wrapper.collapsedState) {
 
567
            if (orientation == Orientation.VERTICAL) {
 
568
                dim.height = 0;
 
569
            } else {
 
570
                dim.width = 0;
 
571
            }
 
572
        }
 
573
        return dim;
 
574
    }
 
575
 
 
576
    @Override
 
577
    public void setPreferredSize(Dimension preferredSize) {
 
578
        getContentPane().setPreferredSize(preferredSize);
 
579
    }
 
580
 
 
581
    /**
 
582
     * Sets the parameters controlling the animation
 
583
     *
 
584
     * @param params
 
585
     * @throws IllegalArgumentException
 
586
     *           if params is null
 
587
     */
 
588
    private void setAnimationParams(AnimationParams params) {
 
589
        if (params == null) { throw new IllegalArgumentException(
 
590
                "params can't be null"); }
 
591
        if (animateTimer != null) {
 
592
            animateTimer.stop();
 
593
        }
 
594
        animationParams = params;
 
595
        animateTimer = new Timer(animationParams.waitTime, animator);
 
596
        animateTimer.setInitialDelay(0);
 
597
    }
 
598
 
 
599
    /**
 
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
 
604
     * validated.
 
605
     */
 
606
    public static interface CollapsiblePaneContainer {
 
607
        Container getValidatingContainer();
 
608
    }
 
609
 
 
610
    /**
 
611
     * Parameters controlling the animations
 
612
     */
 
613
    private static class AnimationParams {
 
614
        final int waitTime;
 
615
        final int delta;
 
616
        final float alphaStart;
 
617
        final float alphaEnd;
 
618
 
 
619
        /**
 
620
         * @param waitTime
 
621
         *          the amount of time in milliseconds to wait between calls to the
 
622
         *          animation thread
 
623
         * @param delta
 
624
         *          the delta, in the direction as specified by the orientation,
 
625
         *          to inc/dec the size of the scroll up by
 
626
         * @param alphaStart
 
627
         *          the starting alpha transparency level
 
628
         * @param alphaEnd
 
629
         *          the ending alpha transparency level
 
630
         */
 
631
        public AnimationParams(int waitTime, int delta, float alphaStart,
 
632
                               float alphaEnd) {
 
633
            this.waitTime = waitTime;
 
634
            this.delta = delta;
 
635
            this.alphaStart = alphaStart;
 
636
            this.alphaEnd = alphaEnd;
 
637
        }
 
638
    }
 
639
 
 
640
    /**
 
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
 
645
     * to be repainted.
 
646
     *
 
647
     * @author Richard Bair
 
648
     */
 
649
    private final class AnimationListener implements ActionListener {
 
650
        /**
 
651
         * Mutex used to ensure that the startDimension/finalDimension are not changed
 
652
         * during a repaint operation.
 
653
         */
 
654
        private final Object ANIMATION_MUTEX = "Animation Synchronization Mutex";
 
655
        /**
 
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.
 
659
         */
 
660
        private int startDimension = 0;
 
661
        /**
 
662
         * This is the final dimension that the content container is going to be when
 
663
         * scrolling is finished.
 
664
         */
 
665
        private int finalDimension = 0;
 
666
        /**
 
667
         * The current alpha setting used during "animation" (fade-in/fade-out)
 
668
         */
 
669
        @SuppressWarnings({"FieldCanBeLocal"})
 
670
        private float animateAlpha = 1.0f;
 
671
 
 
672
        public void actionPerformed(ActionEvent e) {
 
673
            /*
 
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
 
679
            */
 
680
            synchronized (ANIMATION_MUTEX) {
 
681
                if (startDimension == finalDimension) {
 
682
                    animateTimer.stop();
 
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;
 
689
                        validate();
 
690
                        JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
 
691
                                                                  "expanded");
 
692
                        return;
 
693
                    } else {
 
694
                        wrapper.collapsedState = true;
 
695
                        JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
 
696
                                                                  "collapsed");
 
697
                    }
 
698
                }
 
699
 
 
700
                final boolean contracting = startDimension > finalDimension;
 
701
                final int delta = contracting?-1 * animationParams.delta
 
702
                                  :animationParams.delta;
 
703
                int newDimension;
 
704
                if (orientation == Orientation.VERTICAL) {
 
705
                    newDimension = wrapper.getHeight() + delta;
 
706
                } else {
 
707
                    newDimension = wrapper.getWidth() + delta;
 
708
                }
 
709
                if (contracting) {
 
710
                    if (newDimension < finalDimension) {
 
711
                        newDimension = finalDimension;
 
712
                    }
 
713
                } else {
 
714
                    if (newDimension > finalDimension) {
 
715
                        newDimension = finalDimension;
 
716
                    }
 
717
                }
 
718
                int dimension;
 
719
                if (orientation == Orientation.VERTICAL) {
 
720
                    dimension = wrapper.getView().getPreferredSize().height;
 
721
                } else {
 
722
                    dimension = wrapper.getView().getPreferredSize().width;
 
723
                }
 
724
                animateAlpha = (float)newDimension / (float)dimension;
 
725
 
 
726
                Rectangle bounds = wrapper.getBounds();
 
727
 
 
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;
 
736
                } else {
 
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;
 
744
                }
 
745
 
 
746
                setBounds(bounds);
 
747
                startDimension = newDimension;
 
748
 
 
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.
 
752
                if (contracting) {
 
753
                    // alphaStart > animateAlpha > alphaEnd
 
754
                    if (animateAlpha < animationParams.alphaEnd) {
 
755
                        animateAlpha = animationParams.alphaEnd;
 
756
                    }
 
757
                    if (animateAlpha > animationParams.alphaStart) {
 
758
                        animateAlpha = animationParams.alphaStart;
 
759
                    }
 
760
                } else {
 
761
                    // alphaStart < animateAlpha < alphaEnd
 
762
                    if (animateAlpha > animationParams.alphaEnd) {
 
763
                        animateAlpha = animationParams.alphaEnd;
 
764
                    }
 
765
                    if (animateAlpha < animationParams.alphaStart) {
 
766
                        animateAlpha = animationParams.alphaStart;
 
767
                    }
 
768
                }
 
769
                wrapper.alpha = animateAlpha;
 
770
 
 
771
                validate();
 
772
            }
 
773
        }
 
774
 
 
775
        void validate() {
 
776
            Container parent = SwingUtilities.getAncestorOfClass(
 
777
                    CollapsiblePaneContainer.class, JXCollapsiblePane.this);
 
778
            if (parent != null) {
 
779
                parent = ((CollapsiblePaneContainer)parent).getValidatingContainer();
 
780
            } else {
 
781
                parent = getParent();
 
782
            }
 
783
 
 
784
            if (parent != null) {
 
785
                if (parent instanceof JComponent) {
 
786
                    ((JComponent)parent).revalidate();
 
787
                } else {
 
788
                    parent.invalidate();
 
789
                }
 
790
                parent.doLayout();
 
791
                parent.repaint();
 
792
            }
 
793
        }
 
794
 
 
795
        /**
 
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.
 
799
         *
 
800
         * @param startDimension
 
801
         * @param stopDimension
 
802
         */
 
803
        public void reinit(int startDimension, int stopDimension) {
 
804
            synchronized (ANIMATION_MUTEX) {
 
805
                JXCollapsiblePane.this.firePropertyChange(ANIMATION_STATE_KEY, null,
 
806
                                                          "reinit");
 
807
                this.startDimension = startDimension;
 
808
                this.finalDimension = stopDimension;
 
809
                animateAlpha = animationParams.alphaStart;
 
810
                currentDimension = -1;
 
811
            }
 
812
        }
 
813
    }
 
814
 
 
815
    private final class WrapperContainer extends JViewport {
 
816
        float alpha;
 
817
        boolean collapsedState;
 
818
 
 
819
        public WrapperContainer(Container c) {
 
820
            alpha = 1.0f;
 
821
            collapsedState = false;
 
822
            setView(c);
 
823
 
 
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);
 
829
            }
 
830
        }
 
831
    }
 
832
 
 
833
// TEST CASE
 
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");
 
838
//
 
839
//                f.add(new JLabel("Press Ctrl+F or Ctrl+G to collapse panes."),
 
840
//                      BorderLayout.NORTH);
 
841
//
 
842
//                JTree tree1 = new JTree();
 
843
//                tree1.setBorder(BorderFactory.createEtchedBorder());
 
844
//                f.add(tree1);
 
845
//
 
846
//                JXCollapsiblePane pane = new JXCollapsiblePane(Orientation.VERTICAL);
 
847
//                pane.setCollapsed(true);
 
848
//                JTree tree2 = new JTree();
 
849
//                tree2.setBorder(BorderFactory.createEtchedBorder());
 
850
//                pane.add(tree2);
 
851
//                f.add(pane, BorderLayout.SOUTH);
 
852
//
 
853
//                pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
 
854
//                        KeyStroke.getKeyStroke("ctrl F"),
 
855
//                        JXCollapsiblePane.TOGGLE_ACTION);
 
856
//                    
 
857
//                pane = new JXCollapsiblePane(Orientation.HORIZONTAL);
 
858
//                JTree tree3 = new JTree();
 
859
//                pane.add(tree3);
 
860
//                tree3.setBorder(BorderFactory.createEtchedBorder());
 
861
//                f.add(pane, BorderLayout.WEST);
 
862
//
 
863
//                pane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
 
864
//                        KeyStroke.getKeyStroke("ctrl G"),
 
865
//                        JXCollapsiblePane.TOGGLE_ACTION);
 
866
//
 
867
//                f.setSize(640, 480);
 
868
//                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
869
//                f.setVisible(true);
 
870
//        }
 
871
//        });
 
872
//    }
 
873
}