View Javadoc

1   /**
2    * This program is free software: you can redistribute it and/or modify
3    * it under the terms of the GNU General Public License as published by
4    * the Free Software Foundation, version 3.
5    *
6    * This program is distributed in the hope that it will be useful,
7    * but WITHOUT ANY WARRANTY; without even the implied warranty of
8    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9    * GNU General Public License for more details.
10   *
11   * You should have received a copy of the GNU General Public License
12   * along with this program. If not, see <http://www.gnu.org/licenses/>.
13   */
14  
15  package net.curre.prefcount.gui;
16  
17  import java.awt.*;
18  import java.awt.event.ActionEvent;
19  import java.awt.event.ActionListener;
20  import java.awt.image.BufferedImage;
21  import javax.swing.*;
22  import javax.swing.border.LineBorder;
23  
24  /**
25   * Object of this class represents a frame that has an
26   * animated internal pane to display additionial information.
27   * The idea and some code were borrowed from the "Swing Hacks" book, hack #45.
28   * <p/>
29   * A frame that requires this additional functionality should extend this
30   * class and call openAnimatedInnerPanel() with a created JOptionPane
31   * object. This pane should have a listener attached that would call
32   * closeAnimatedInnerPanel() when done. It's recomended to create a new
33   * JOptionPane object every time an animated pane is required.
34   * <p/>
35   * Created date: Jan 25, 2008
36   *
37   * @author Yevgeny Nyden
38   */
39  public class JFrameWithAnimatedInnerPanel extends JFrame {
40  
41    /** Length of the animation in msec. */
42    public static final float ANIMATION_DURATION = 1000f;
43  
44    /** Animation sleep time ("frame rate") in msec. */
45    public static final int ANIMATION_SLEEP = 50;
46  
47    /** Enumeration to represent animation direction. */
48    private static enum AnimationMode {
49  
50      ROLLING_OUT, ROLLING_IN
51    }
52  
53    /** Direction of the animation (out or in). */
54    private AnimationMode animationMode;
55  
56    /** Reference to the JDialog's content pane. */
57    private JComponent sheet;
58  
59    /** Reference to the panel's glass pane. */
60    private JPanel glass;
61  
62    /** Represents the animated panel. */
63    private JFrameWithAnimatedInnerPanel.AnimatedInnerPanel animatedPanel;
64  
65    /** Flag to assist with animation. */
66    private boolean animating;
67  
68    /** Animation timer. */
69    private Timer animationTimer;
70  
71    /** Variable to store animation start time. */
72    private long animationStart;
73  
74    /** Flag to coordinate open/close actions. */
75    private boolean isOpen = false;
76  
77    /**
78     * Constructor that sets the panel name.
79     *
80     * @param name Panel name to set.
81     */
82    public JFrameWithAnimatedInnerPanel(String name) {
83      super(name);
84    }
85  
86    /**
87     * Opens an inner dialog pane.
88     *
89     * @param dialogPane JOptionPane object to use.
90     * @return A <code>JDialog</code> object created from the given dialog pane
91     *         or null if another inner pane has already been opened.
92     */
93    public JDialog openAnimatedInnerPanel(JOptionPane dialogPane) {
94      if (!askPermissionToOpenOrClose(true)) {
95        return null;
96      }
97      super.setResizable(false);
98      JDialog dialog = dialogPane.createDialog(this, "dialog");
99      glass = (JPanel) getGlassPane();
100     glass.setLayout(new GridBagLayout());
101     animatedPanel = new JFrameWithAnimatedInnerPanel.AnimatedInnerPanel();
102     animatedPanel.setBorder(new LineBorder(Color.black, 1));
103 
104     sheet = (JComponent) dialog.getContentPane();
105     sheet.setBorder(new LineBorder(Color.black, 1));
106     glass.removeAll();
107     animationMode = AnimationMode.ROLLING_OUT;
108     startAnimation();
109     return dialog;
110   }
111 
112   /** Closes the inner pane if it's open. */
113   public void closeAnimatedInnerPanel() {
114     if (askPermissionToOpenOrClose(false)) {
115       animationMode = AnimationMode.ROLLING_IN;
116       startAnimation();
117       super.setResizable(true);
118       isOpen = false;
119     }
120   }
121 
122   /** Private classes and methods ********************** */
123 
124   /** Helper class that represents an animated panel. */
125   private class AnimatedInnerPanel extends JPanel {
126 
127     Dimension animatingSize = new Dimension(0, 1);
128     JComponent source;
129     BufferedImage offScreenImage;
130 
131     /** Default constructor. */
132     public AnimatedInnerPanel() {
133       super();
134       setOpaque(true);
135     }
136 
137     /**
138      * Sets the panel source and
139      *
140      * @param source Source component to set.
141      */
142     public void setSource(JComponent source) {
143       this.source = source;
144       animatingSize.width = source.getWidth();
145       GraphicsConfiguration gfxConfig =
146           GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
147       offScreenImage = gfxConfig.createCompatibleImage(source.getWidth(), source.getHeight());
148       Graphics2D offscreenGraphics = (Graphics2D) offScreenImage.getGraphics();
149       source.paint(offscreenGraphics);
150     }
151 
152     /**
153      * Sets the current height.
154      *
155      * @param height Height to set.
156      */
157     public void setAnimatingHeight(int height) {
158       animatingSize.height = height;
159       setSize(animatingSize);
160     }
161 
162     /** {@inheritDoc} */
163     public Dimension getPreferredSize() {
164       return animatingSize;
165     }
166 
167     /** {@inheritDoc} */
168     public Dimension getMinimumSize() {
169       return animatingSize;
170     }
171 
172     /** {@inheritDoc} */
173     public Dimension getMaximumSize() {
174       return animatingSize;
175     }
176 
177     /** {@inheritDoc} */
178     public void paint(Graphics g) {
179       // taking the lower portion of the panel (of height size) and
180       // painting it into the current graphics context
181       BufferedImage frgm = offScreenImage.getSubimage(0, offScreenImage.getHeight() - animatingSize.height,
182                                                       source.getWidth(), animatingSize.height);
183       g.drawImage(frgm, 0, 0, this);
184     }
185   }
186 
187   /** Helper listener class to assist with animation. */
188   private class SomeActionListener implements ActionListener {
189 
190     /** {@inheritDoc} */
191     public void actionPerformed(ActionEvent actionEvent) {
192       if (animating) {
193         // calculate height to show
194         float animationPercent =
195             (System.currentTimeMillis() - animationStart) / ANIMATION_DURATION;
196         animationPercent = Math.min(1.0f, animationPercent);
197         int animatingHeight;
198         if (animationMode == AnimationMode.ROLLING_OUT) {
199           animatingHeight =
200               (int) (animationPercent * sheet.getHeight());
201         } else {
202           animatingHeight =
203               (int) ((1.0f - animationPercent) * sheet.getHeight());
204         }
205         // clip off that much from sheet and blit it into animatedPanel
206         animatedPanel.setAnimatingHeight(animatingHeight);
207         animatedPanel.repaint();
208         if (animationPercent >= 1.0f) {
209           stopAnimation();
210           if (animationMode == AnimationMode.ROLLING_OUT) {
211             // PANEL is OPEN (ROLLED OUT)
212             finishShowingSheet();
213           } else {
214             // PANEL is CLOSED (ROLLED IN)
215             glass.removeAll();
216             glass.setVisible(false);
217           }
218         }
219       }
220     }
221   }
222 
223   /**
224    * This methods coordinates the open/close pane actions.
225    *
226    * @param isToOpen True if it's an open pane request; false - close pane.
227    * @return True if permission is granted; false otherwise.
228    */
229   private synchronized boolean askPermissionToOpenOrClose(boolean isToOpen) {
230     if (isToOpen) {
231       if (isOpen) {
232         return false;
233       } else {
234         isOpen = true;
235         return true;
236       }
237     } else {
238       return isOpen;
239     }
240   }
241 
242   /** Starts animation. */
243   private void startAnimation() {
244     glass.repaint();
245     // clear glasspane and set up animatedPanel
246     animatedPanel.setSource(sheet);
247     glass.removeAll();
248     GridBagConstraints gbc = new GridBagConstraints();
249     gbc.anchor = GridBagConstraints.NORTH;
250     glass.add(animatedPanel, gbc);
251     gbc.gridy = 1;
252     gbc.weighty = Integer.MAX_VALUE;
253     glass.add(Box.createGlue(), gbc);
254     glass.setVisible(true);
255 
256     // start animation timer
257     animationStart = System.currentTimeMillis();
258     if (animationTimer == null) {
259       animationTimer = new Timer(ANIMATION_SLEEP, new JFrameWithAnimatedInnerPanel.SomeActionListener());
260     }
261     animating = true;
262     animationTimer.start();
263   }
264 
265   /** Stops animation. */
266   private void stopAnimation() {
267     animationTimer.stop();
268     animating = false;
269   }
270 
271   /** Finishes showing the inner pane. */
272   private void finishShowingSheet() {
273     glass.removeAll();
274     GridBagConstraints gbc = new GridBagConstraints();
275     gbc.anchor = GridBagConstraints.NORTH;
276     glass.add(sheet, gbc);
277     gbc.gridy = 1;
278     gbc.weighty = Integer.MAX_VALUE;
279     glass.add(Box.createGlue(), gbc);
280     // glass.setVisible(true);
281     glass.revalidate();
282     glass.repaint();
283   }
284 
285 }