001/* CardLayout.java -- Card-based layout engine
002   Copyright (C) 1999, 2000, 2002, 2003, 2004  Free Software Foundation
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt;
040
041import java.io.Serializable;
042import java.util.Enumeration;
043import java.util.Hashtable;
044
045/**
046 * This class implements a card-based layout scheme.  Each included
047 * component is treated as a card.  Only one card can be shown at a
048 * time.  This class includes methods for changing which card is
049 * shown.
050 *
051 * @author Tom Tromey (tromey@redhat.com)
052 * @author Aaron M. Renn (arenn@urbanophile.com)
053 */
054public class CardLayout implements LayoutManager2, Serializable
055{
056  private static final long serialVersionUID = -4328196481005934313L;
057
058  /**
059   * Initializes a new instance of <code>CardLayout</code> with horizontal
060   * and vertical gaps of 0.
061   */
062  public CardLayout ()
063  {
064    this (0, 0);
065  }
066
067  /**
068   * Create a new <code>CardLayout</code> object with the specified
069   * horizontal and vertical gaps.
070   *
071   * @param hgap The horizontal gap
072   * @param vgap The vertical gap
073   */
074  public CardLayout (int hgap, int vgap)
075  {
076    this.hgap = hgap;
077    this.vgap = vgap;
078    this.tab = new Hashtable ();
079  }
080
081  /**
082   * Add a new component to the layout.  The constraint must be a
083   * string which is used to name the component.  This string can
084   * later be used to refer to the particular component.
085   *
086   * @param comp The component to add
087   * @param constraints The name by which the component can later be called
088   *
089   * @exception IllegalArgumentException If `constraints' is not a
090   * <code>String</code>
091   */
092  public void addLayoutComponent (Component comp, Object constraints)
093  {
094    if (! (constraints instanceof String))
095      throw new IllegalArgumentException ("Object " + constraints
096                                          + " is not a string");
097    addLayoutComponent ((String) constraints, comp);
098  }
099
100  /**
101   * Add a new component to the layout.  The name can be used later
102   * to refer to the component.
103   *
104   * @param name The name by which the component can later be called
105   * @param comp The component to add
106   *
107   * @deprecated This method is deprecated in favor of
108   * <code>addLayoutComponent(Component, Object)</code>.
109   */
110  public void addLayoutComponent (String name, Component comp)
111  {
112    tab.put (name, comp);
113    // First component added is the default component.
114    comp.setVisible(tab.size() == 1);
115  }
116
117  /**
118   * Cause the first component in the container to be displayed.
119   *
120   * @param parent The parent container, not <code>null</code>.
121   */
122  public void first (Container parent)
123  {
124    gotoComponent (parent, FIRST);
125  }
126
127  /**
128   * Return this layout manager's horizontal gap.
129   *
130   * @return the horizontal gap
131   */
132  public int getHgap ()
133  {
134    return hgap;
135  }
136
137  /**
138   * Return this layout manager's x alignment.  This method always
139   * returns Component.CENTER_ALIGNMENT.
140   *
141   * @param parent Container using this layout manager instance
142   *
143   * @return the x-axis alignment
144   */
145  public float getLayoutAlignmentX (Container parent)
146  {
147    return Component.CENTER_ALIGNMENT;
148  }
149
150  /**
151   * Returns this layout manager's y alignment.  This method always
152   * returns Component.CENTER_ALIGNMENT.
153   *
154   * @param parent Container using this layout manager instance
155   *
156   * @return the y-axis alignment
157   */
158  public float getLayoutAlignmentY (Container parent)
159  {
160    return Component.CENTER_ALIGNMENT;
161  }
162
163  /**
164   * Return this layout manager's vertical gap.
165   *
166   * @return the vertical gap
167   */
168  public int getVgap ()
169  {
170    return vgap;
171  }
172
173  /**
174   * Invalidate this layout manager's state.
175   */
176  public void invalidateLayout (Container target)
177  {
178    // Do nothing.
179  }
180
181  /**
182   * Cause the last component in the container to be displayed.
183   *
184   * @param parent The parent container, not <code>null</code>.
185   */
186  public void last (Container parent)
187  {
188    gotoComponent (parent, LAST);
189  }
190
191  /**
192   * Lays out the container.  This is done by resizing the child components
193   * to be the same size as the parent, less insets and gaps.
194   *
195   * @param parent The parent container.
196   */
197  public void layoutContainer (Container parent)
198  {
199    synchronized (parent.getTreeLock ())
200      {
201        int width = parent.width;
202        int height = parent.height;
203
204        Insets ins = parent.getInsets ();
205
206        int num = parent.ncomponents;
207        Component[] comps = parent.component;
208
209        int x = ins.left + hgap;
210        int y = ins.top + vgap;
211        width = width - 2 * hgap - ins.left - ins.right;
212        height = height - 2 * vgap - ins.top - ins.bottom;
213
214        for (int i = 0; i < num; ++i)
215          comps[i].setBounds (x, y, width, height);
216      }
217  }
218
219  /**
220   * Get the maximum layout size of the container.
221   *
222   * @param target The parent container
223   *
224   * @return the maximum layout size
225   */
226  public Dimension maximumLayoutSize (Container target)
227  {
228    if (target == null || target.ncomponents == 0)
229      return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
230    // The JCL says that this returns Integer.MAX_VALUE for both
231    // dimensions.  But that just seems wrong to me.
232    return getSize (target, MAX);
233  }
234
235  /**
236   * Get the minimum layout size of the container.
237   *
238   * @param target The parent container
239   *
240   * @return the minimum layout size
241   */
242  public Dimension minimumLayoutSize (Container target)
243  {
244    return getSize (target, MIN);
245  }
246
247  /**
248   * Cause the next component in the container to be displayed.  If
249   * this current card is the  last one in the deck, the first
250   * component is displayed.
251   *
252   * @param parent The parent container, not <code>null</code>.
253   */
254  public void next (Container parent)
255  {
256    gotoComponent (parent, NEXT);
257  }
258
259  /**
260   * Get the preferred layout size of the container.
261   *
262   * @param parent The parent container
263   *
264   * @return the preferred layout size
265   */
266  public Dimension preferredLayoutSize (Container parent)
267  {
268    return getSize (parent, PREF);
269  }
270
271  /**
272   * Cause the previous component in the container to be displayed.
273   * If this current card is the first one in the deck, the last
274   * component is displayed.
275   *
276   * @param parent The parent container, not <code>null</code>.
277   */
278  public void previous (Container parent)
279  {
280    gotoComponent (parent, PREV);
281  }
282
283  /**
284   * Remove the indicated component from this layout manager.
285   *
286   * @param comp The component to remove
287   */
288  public void removeLayoutComponent (Component comp)
289  {
290    Enumeration e = tab.keys ();
291    while (e.hasMoreElements ())
292      {
293        Object key = e.nextElement ();
294        if (tab.get (key) == comp)
295          {
296            tab.remove (key);
297            Container parent = comp.getParent();
298            next(parent);
299            break;
300          }
301      }
302  }
303
304  /**
305   * Set this layout manager's horizontal gap.
306   *
307   * @param hgap The new gap
308   */
309  public void setHgap (int hgap)
310  {
311    this.hgap = hgap;
312  }
313
314  /**
315   * Set this layout manager's vertical gap.
316   *
317   * @param vgap The new gap
318   */
319  public void setVgap (int vgap)
320  {
321    this.vgap = vgap;
322  }
323
324  /**
325   * Cause the named component to be shown.  If the component name is
326   * unknown or <code>null</code>, this method does nothing.
327   *
328   * @param parent The parent container, not <code>null</code>.
329   * @param name The name of the component to show
330   */
331  public void show (Container parent, String name)
332  {
333    if (name == null)
334      return;
335
336    if (parent.getLayout() != this)
337      throw new IllegalArgumentException("parent's layout is not this CardLayout");
338
339    Object target = tab.get (name);
340    if (target != null)
341      {
342        int num = parent.ncomponents;
343        // This is more efficient than calling getComponents().
344        Component[] comps = parent.component;
345        for (int i = 0; i < num; ++i)
346          {
347            if (comps[i].isVisible())
348              {
349                if (target == comps[i])
350                  return;
351                comps[i].setVisible (false);
352              }
353          }
354        ((Component) target).setVisible (true);
355        parent.validate();
356      }
357  }
358
359  /**
360   * Returns a string representation of this layout manager.
361   *
362   * @return A string representation of this object.
363   */
364  public String toString ()
365  {
366    return getClass ().getName () + "[hgap=" + hgap + ",vgap=" + vgap + "]";
367  }
368
369  /**
370   * This implements first(), last(), next(), and previous().
371   *
372   * @param parent The parent container
373   * @param what The type of goto: FIRST, LAST, NEXT or PREV
374   *
375   * @throws IllegalArgumentException if parent has not this
376   * CardLayout set as its layout.
377   */
378  private void gotoComponent (Container parent, int what)
379  {
380    if (parent.getLayout() != this)
381      throw new IllegalArgumentException("parent's layout is not this CardLayout");
382
383    synchronized (parent.getTreeLock ())
384      {
385        int num = parent.ncomponents;
386        // This is more efficient than calling getComponents().
387        Component[] comps = parent.component;
388
389        if (num == 1)
390          {
391            comps[0].setVisible(true);
392            return;
393          }
394
395        int choice = -1;
396
397        if (what == FIRST)
398          choice = 0;
399        else if (what == LAST)
400          choice = num - 1;
401
402        for (int i = 0; i < num; ++i)
403          {
404            if (comps[i].isVisible ())
405              {
406                if (choice == i)
407                  {
408                    // Do nothing if we're already looking at the right
409                    // component.
410                    return;
411                  }
412                else if (what == PREV)
413                  {
414                    choice = i - 1;
415                    if (choice < 0)
416                      choice = num - 1;
417                  }
418                else if (what == NEXT)
419                  {
420                    choice = i + 1;
421                    if (choice == num)
422                      choice = 0;
423                  }
424                comps[i].setVisible (false);
425
426                if (choice >= 0)
427                  break;
428              } else
429                {
430                  comps[i].setVisible(true);
431                }
432          }
433
434        if (choice >= 0 && choice < num)
435          comps[choice].setVisible (true);
436      }
437  }
438
439  // Compute the size according to WHAT.
440  private Dimension getSize (Container parent, int what)
441  {
442    synchronized (parent.getTreeLock ())
443      {
444        int w = 0, h = 0, num = parent.ncomponents;
445        Component[] comps = parent.component;
446
447        for (int i = 0; i < num; ++i)
448          {
449            Dimension d;
450
451            if (what == MIN)
452              d = comps[i].getMinimumSize ();
453            else if (what == MAX)
454              d = comps[i].getMaximumSize ();
455            else
456              d = comps[i].getPreferredSize ();
457
458            w = Math.max (d.width, w);
459            h = Math.max (d.height, h);
460          }
461
462        Insets i = parent.getInsets ();
463        w += 2 * hgap + i.right + i.left;
464        h += 2 * vgap + i.bottom + i.top;
465
466        // Handle overflow.
467        if (w < 0)
468          w = Integer.MAX_VALUE;
469        if (h < 0)
470          h = Integer.MAX_VALUE;
471
472        return new Dimension (w, h);
473      }
474  }
475
476  /**
477   * @serial Horizontal gap value.
478   */
479  private int hgap;
480
481  /**
482   * @serial Vertical gap value.
483   */
484  private int vgap;
485
486  /**
487   * @serial Table of named components.
488   */
489  private Hashtable tab;
490
491  // These constants are used by the private gotoComponent method.
492  private static final int FIRST = 0;
493  private static final int LAST = 1;
494  private static final int NEXT = 2;
495  private static final int PREV = 3;
496
497  // These constants are used by the private getSize method.
498  private static final int MIN = 0;
499  private static final int MAX = 1;
500  private static final int PREF = 2;
501}