1 /*
   2  * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package java.awt;
  27 
  28 import java.util.Hashtable;
  29 import java.util.Vector;
  30 import java.util.Enumeration;
  31 
  32 import java.io.Serializable;
  33 import java.io.ObjectInputStream;
  34 import java.io.ObjectOutputStream;
  35 import java.io.ObjectStreamField;
  36 import java.io.IOException;
  37 
  38 /**
  39  * A <code>CardLayout</code> object is a layout manager for a
  40  * container. It treats each component in the container as a card.
  41  * Only one card is visible at a time, and the container acts as
  42  * a stack of cards. The first component added to a
  43  * <code>CardLayout</code> object is the visible component when the
  44  * container is first displayed.
  45  * <p>
  46  * The ordering of cards is determined by the container's own internal
  47  * ordering of its component objects. <code>CardLayout</code>
  48  * defines a set of methods that allow an application to flip
  49  * through these cards sequentially, or to show a specified card.
  50  * The {@link CardLayout#addLayoutComponent}
  51  * method can be used to associate a string identifier with a given card
  52  * for fast random access.
  53  *
  54  * @author      Arthur van Hoff
  55  * @see         java.awt.Container
  56  * @since       1.0
  57  */
  58 
  59 public class CardLayout implements LayoutManager2,
  60                                    Serializable {
  61 
  62     private static final long serialVersionUID = -4328196481005934313L;
  63 
  64     /*
  65      * This creates a Vector to store associated
  66      * pairs of components and their names.
  67      * @see java.util.Vector
  68      */
  69     Vector<Card> vector = new Vector<>();
  70 
  71     /*
  72      * A pair of Component and String that represents its name.
  73      */
  74     class Card implements Serializable {
  75         static final long serialVersionUID = 6640330810709497518L;
  76         public String name;
  77         public Component comp;
  78         public Card(String cardName, Component cardComponent) {
  79             name = cardName;
  80             comp = cardComponent;
  81         }
  82     }
  83 
  84     /*
  85      * Index of Component currently displayed by CardLayout.
  86      */
  87     int currentCard = 0;
  88 
  89 
  90     /*
  91     * A cards horizontal Layout gap (inset). It specifies
  92     * the space between the left and right edges of a
  93     * container and the current component.
  94     * This should be a non negative Integer.
  95     * @see getHgap()
  96     * @see setHgap()
  97     */
  98     int hgap;
  99 
 100     /*
 101     * A cards vertical Layout gap (inset). It specifies
 102     * the space between the top and bottom edges of a
 103     * container and the current component.
 104     * This should be a non negative Integer.
 105     * @see getVgap()
 106     * @see setVgap()
 107     */
 108     int vgap;
 109 
 110     /**
 111      * @serialField tab         Hashtable
 112      *      deprectated, for forward compatibility only
 113      * @serialField hgap        int
 114      * @serialField vgap        int
 115      * @serialField vector      Vector
 116      * @serialField currentCard int
 117      */
 118     private static final ObjectStreamField[] serialPersistentFields = {
 119         new ObjectStreamField("tab", Hashtable.class),
 120         new ObjectStreamField("hgap", Integer.TYPE),
 121         new ObjectStreamField("vgap", Integer.TYPE),
 122         new ObjectStreamField("vector", Vector.class),
 123         new ObjectStreamField("currentCard", Integer.TYPE)
 124     };
 125 
 126     /**
 127      * Creates a new card layout with gaps of size zero.
 128      */
 129     public CardLayout() {
 130         this(0, 0);
 131     }
 132 
 133     /**
 134      * Creates a new card layout with the specified horizontal and
 135      * vertical gaps. The horizontal gaps are placed at the left and
 136      * right edges. The vertical gaps are placed at the top and bottom
 137      * edges.
 138      * @param     hgap   the horizontal gap.
 139      * @param     vgap   the vertical gap.
 140      */
 141     public CardLayout(int hgap, int vgap) {
 142         this.hgap = hgap;
 143         this.vgap = vgap;
 144     }
 145 
 146     /**
 147      * Gets the horizontal gap between components.
 148      * @return    the horizontal gap between components.
 149      * @see       java.awt.CardLayout#setHgap(int)
 150      * @see       java.awt.CardLayout#getVgap()
 151      * @since     1.1
 152      */
 153     public int getHgap() {
 154         return hgap;
 155     }
 156 
 157     /**
 158      * Sets the horizontal gap between components.
 159      * @param hgap the horizontal gap between components.
 160      * @see       java.awt.CardLayout#getHgap()
 161      * @see       java.awt.CardLayout#setVgap(int)
 162      * @since     1.1
 163      */
 164     public void setHgap(int hgap) {
 165         this.hgap = hgap;
 166     }
 167 
 168     /**
 169      * Gets the vertical gap between components.
 170      * @return the vertical gap between components.
 171      * @see       java.awt.CardLayout#setVgap(int)
 172      * @see       java.awt.CardLayout#getHgap()
 173      */
 174     public int getVgap() {
 175         return vgap;
 176     }
 177 
 178     /**
 179      * Sets the vertical gap between components.
 180      * @param     vgap the vertical gap between components.
 181      * @see       java.awt.CardLayout#getVgap()
 182      * @see       java.awt.CardLayout#setHgap(int)
 183      * @since     1.1
 184      */
 185     public void setVgap(int vgap) {
 186         this.vgap = vgap;
 187     }
 188 
 189     /**
 190      * Adds the specified component to this card layout's internal
 191      * table of names. The object specified by <code>constraints</code>
 192      * must be a string. The card layout stores this string as a key-value
 193      * pair that can be used for random access to a particular card.
 194      * By calling the <code>show</code> method, an application can
 195      * display the component with the specified name.
 196      * @param     comp          the component to be added.
 197      * @param     constraints   a tag that identifies a particular
 198      *                                        card in the layout.
 199      * @see       java.awt.CardLayout#show(java.awt.Container, java.lang.String)
 200      * @exception  IllegalArgumentException  if the constraint is not a string.
 201      */
 202     public void addLayoutComponent(Component comp, Object constraints) {
 203       synchronized (comp.getTreeLock()) {
 204           if (constraints == null){
 205               constraints = "";
 206           }
 207         if (constraints instanceof String) {
 208             addLayoutComponent((String)constraints, comp);
 209         } else {
 210             throw new IllegalArgumentException("cannot add to layout: constraint must be a string");
 211         }
 212       }
 213     }
 214 
 215     /**
 216      * @deprecated   replaced by
 217      *      <code>addLayoutComponent(Component, Object)</code>.
 218      */
 219     @Deprecated
 220     public void addLayoutComponent(String name, Component comp) {
 221         synchronized (comp.getTreeLock()) {
 222             if (!vector.isEmpty()) {
 223                 comp.setVisible(false);
 224             }
 225             for (int i=0; i < vector.size(); i++) {
 226                 if ((vector.get(i)).name.equals(name)) {
 227                     (vector.get(i)).comp = comp;
 228                     return;
 229                 }
 230             }
 231             vector.add(new Card(name, comp));
 232         }
 233     }
 234 
 235     /**
 236      * Removes the specified component from the layout.
 237      * If the card was visible on top, the next card underneath it is shown.
 238      * @param   comp   the component to be removed.
 239      * @see     java.awt.Container#remove(java.awt.Component)
 240      * @see     java.awt.Container#removeAll()
 241      */
 242     public void removeLayoutComponent(Component comp) {
 243         synchronized (comp.getTreeLock()) {
 244             for (int i = 0; i < vector.size(); i++) {
 245                 if ((vector.get(i)).comp == comp) {
 246                     // if we remove current component we should show next one
 247                     if (comp.isVisible() && (comp.getParent() != null)) {
 248                         next(comp.getParent());
 249                     }
 250 
 251                     vector.remove(i);
 252 
 253                     // correct currentCard if this is necessary
 254                     if (currentCard > i) {
 255                         currentCard--;
 256                     }
 257                     break;
 258                 }
 259             }
 260         }
 261     }
 262 
 263     /**
 264      * Determines the preferred size of the container argument using
 265      * this card layout.
 266      * @param   parent the parent container in which to do the layout
 267      * @return  the preferred dimensions to lay out the subcomponents
 268      *                of the specified container
 269      * @see     java.awt.Container#getPreferredSize
 270      * @see     java.awt.CardLayout#minimumLayoutSize
 271      */
 272     public Dimension preferredLayoutSize(Container parent) {
 273         synchronized (parent.getTreeLock()) {
 274             Insets insets = parent.getInsets();
 275             int ncomponents = parent.getComponentCount();
 276             int w = 0;
 277             int h = 0;
 278 
 279             for (int i = 0 ; i < ncomponents ; i++) {
 280                 Component comp = parent.getComponent(i);
 281                 Dimension d = comp.getPreferredSize();
 282                 if (d.width > w) {
 283                     w = d.width;
 284                 }
 285                 if (d.height > h) {
 286                     h = d.height;
 287                 }
 288             }
 289             return new Dimension(insets.left + insets.right + w + hgap*2,
 290                                  insets.top + insets.bottom + h + vgap*2);
 291         }
 292     }
 293 
 294     /**
 295      * Calculates the minimum size for the specified panel.
 296      * @param     parent the parent container in which to do the layout
 297      * @return    the minimum dimensions required to lay out the
 298      *                subcomponents of the specified container
 299      * @see       java.awt.Container#doLayout
 300      * @see       java.awt.CardLayout#preferredLayoutSize
 301      */
 302     public Dimension minimumLayoutSize(Container parent) {
 303         synchronized (parent.getTreeLock()) {
 304             Insets insets = parent.getInsets();
 305             int ncomponents = parent.getComponentCount();
 306             int w = 0;
 307             int h = 0;
 308 
 309             for (int i = 0 ; i < ncomponents ; i++) {
 310                 Component comp = parent.getComponent(i);
 311                 Dimension d = comp.getMinimumSize();
 312                 if (d.width > w) {
 313                     w = d.width;
 314                 }
 315                 if (d.height > h) {
 316                     h = d.height;
 317                 }
 318             }
 319             return new Dimension(insets.left + insets.right + w + hgap*2,
 320                                  insets.top + insets.bottom + h + vgap*2);
 321         }
 322     }
 323 
 324     /**
 325      * Returns the maximum dimensions for this layout given the components
 326      * in the specified target container.
 327      * @param target the component which needs to be laid out
 328      * @see Container
 329      * @see #minimumLayoutSize
 330      * @see #preferredLayoutSize
 331      */
 332     public Dimension maximumLayoutSize(Container target) {
 333         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
 334     }
 335 
 336     /**
 337      * Returns the alignment along the x axis.  This specifies how
 338      * the component would like to be aligned relative to other
 339      * components.  The value should be a number between 0 and 1
 340      * where 0 represents alignment along the origin, 1 is aligned
 341      * the furthest away from the origin, 0.5 is centered, etc.
 342      */
 343     public float getLayoutAlignmentX(Container parent) {
 344         return 0.5f;
 345     }
 346 
 347     /**
 348      * Returns the alignment along the y axis.  This specifies how
 349      * the component would like to be aligned relative to other
 350      * components.  The value should be a number between 0 and 1
 351      * where 0 represents alignment along the origin, 1 is aligned
 352      * the furthest away from the origin, 0.5 is centered, etc.
 353      */
 354     public float getLayoutAlignmentY(Container parent) {
 355         return 0.5f;
 356     }
 357 
 358     /**
 359      * Invalidates the layout, indicating that if the layout manager
 360      * has cached information it should be discarded.
 361      */
 362     public void invalidateLayout(Container target) {
 363     }
 364 
 365     /**
 366      * Lays out the specified container using this card layout.
 367      * <p>
 368      * Each component in the <code>parent</code> container is reshaped
 369      * to be the size of the container, minus space for surrounding
 370      * insets, horizontal gaps, and vertical gaps.
 371      *
 372      * @param     parent the parent container in which to do the layout
 373      * @see       java.awt.Container#doLayout
 374      */
 375     public void layoutContainer(Container parent) {
 376         synchronized (parent.getTreeLock()) {
 377             Insets insets = parent.getInsets();
 378             int ncomponents = parent.getComponentCount();
 379             Component comp = null;
 380             boolean currentFound = false;
 381 
 382             for (int i = 0 ; i < ncomponents ; i++) {
 383                 comp = parent.getComponent(i);
 384                 comp.setBounds(hgap + insets.left, vgap + insets.top,
 385                                parent.width - (hgap*2 + insets.left + insets.right),
 386                                parent.height - (vgap*2 + insets.top + insets.bottom));
 387                 if (comp.isVisible()) {
 388                     currentFound = true;
 389                 }
 390             }
 391 
 392             if (!currentFound && ncomponents > 0) {
 393                 parent.getComponent(0).setVisible(true);
 394             }
 395         }
 396     }
 397 
 398     /**
 399      * Make sure that the Container really has a CardLayout installed.
 400      * Otherwise havoc can ensue!
 401      */
 402     void checkLayout(Container parent) {
 403         if (parent.getLayout() != this) {
 404             throw new IllegalArgumentException("wrong parent for CardLayout");
 405         }
 406     }
 407 
 408     /**
 409      * Flips to the first card of the container.
 410      * @param     parent   the parent container in which to do the layout
 411      * @see       java.awt.CardLayout#last
 412      */
 413     public void first(Container parent) {
 414         synchronized (parent.getTreeLock()) {
 415             checkLayout(parent);
 416             int ncomponents = parent.getComponentCount();
 417             for (int i = 0 ; i < ncomponents ; i++) {
 418                 Component comp = parent.getComponent(i);
 419                 if (comp.isVisible()) {
 420                     comp.setVisible(false);
 421                     break;
 422                 }
 423             }
 424             if (ncomponents > 0) {
 425                 currentCard = 0;
 426                 parent.getComponent(0).setVisible(true);
 427                 parent.validate();
 428             }
 429         }
 430     }
 431 
 432     /**
 433      * Flips to the next card of the specified container. If the
 434      * currently visible card is the last one, this method flips to the
 435      * first card in the layout.
 436      * @param     parent   the parent container in which to do the layout
 437      * @see       java.awt.CardLayout#previous
 438      */
 439     public void next(Container parent) {
 440         synchronized (parent.getTreeLock()) {
 441             checkLayout(parent);
 442             int ncomponents = parent.getComponentCount();
 443             for (int i = 0 ; i < ncomponents ; i++) {
 444                 Component comp = parent.getComponent(i);
 445                 if (comp.isVisible()) {
 446                     comp.setVisible(false);
 447                     currentCard = (i + 1) % ncomponents;
 448                     comp = parent.getComponent(currentCard);
 449                     comp.setVisible(true);
 450                     parent.validate();
 451                     return;
 452                 }
 453             }
 454             showDefaultComponent(parent);
 455         }
 456     }
 457 
 458     /**
 459      * Flips to the previous card of the specified container. If the
 460      * currently visible card is the first one, this method flips to the
 461      * last card in the layout.
 462      * @param     parent   the parent container in which to do the layout
 463      * @see       java.awt.CardLayout#next
 464      */
 465     public void previous(Container parent) {
 466         synchronized (parent.getTreeLock()) {
 467             checkLayout(parent);
 468             int ncomponents = parent.getComponentCount();
 469             for (int i = 0 ; i < ncomponents ; i++) {
 470                 Component comp = parent.getComponent(i);
 471                 if (comp.isVisible()) {
 472                     comp.setVisible(false);
 473                     currentCard = ((i > 0) ? i-1 : ncomponents-1);
 474                     comp = parent.getComponent(currentCard);
 475                     comp.setVisible(true);
 476                     parent.validate();
 477                     return;
 478                 }
 479             }
 480             showDefaultComponent(parent);
 481         }
 482     }
 483 
 484     void showDefaultComponent(Container parent) {
 485         if (parent.getComponentCount() > 0) {
 486             currentCard = 0;
 487             parent.getComponent(0).setVisible(true);
 488             parent.validate();
 489         }
 490     }
 491 
 492     /**
 493      * Flips to the last card of the container.
 494      * @param     parent   the parent container in which to do the layout
 495      * @see       java.awt.CardLayout#first
 496      */
 497     public void last(Container parent) {
 498         synchronized (parent.getTreeLock()) {
 499             checkLayout(parent);
 500             int ncomponents = parent.getComponentCount();
 501             for (int i = 0 ; i < ncomponents ; i++) {
 502                 Component comp = parent.getComponent(i);
 503                 if (comp.isVisible()) {
 504                     comp.setVisible(false);
 505                     break;
 506                 }
 507             }
 508             if (ncomponents > 0) {
 509                 currentCard = ncomponents - 1;
 510                 parent.getComponent(currentCard).setVisible(true);
 511                 parent.validate();
 512             }
 513         }
 514     }
 515 
 516     /**
 517      * Flips to the component that was added to this layout with the
 518      * specified <code>name</code>, using <code>addLayoutComponent</code>.
 519      * If no such component exists, then nothing happens.
 520      * @param     parent   the parent container in which to do the layout
 521      * @param     name     the component name
 522      * @see       java.awt.CardLayout#addLayoutComponent(java.awt.Component, java.lang.Object)
 523      */
 524     public void show(Container parent, String name) {
 525         synchronized (parent.getTreeLock()) {
 526             checkLayout(parent);
 527             Component next = null;
 528             int ncomponents = vector.size();
 529             for (int i = 0; i < ncomponents; i++) {
 530                 Card card = vector.get(i);
 531                 if (card.name.equals(name)) {
 532                     next = card.comp;
 533                     currentCard = i;
 534                     break;
 535                 }
 536             }
 537             if ((next != null) && !next.isVisible()) {
 538                 ncomponents = parent.getComponentCount();
 539                 for (int i = 0; i < ncomponents; i++) {
 540                     Component comp = parent.getComponent(i);
 541                     if (comp.isVisible()) {
 542                         comp.setVisible(false);
 543                         break;
 544                     }
 545                 }
 546                 next.setVisible(true);
 547                 parent.validate();
 548             }
 549         }
 550     }
 551 
 552     /**
 553      * Returns a string representation of the state of this card layout.
 554      * @return    a string representation of this card layout.
 555      */
 556     public String toString() {
 557         return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
 558     }
 559 
 560     /**
 561      * Reads serializable fields from stream.
 562      */
 563     @SuppressWarnings("unchecked")
 564     private void readObject(ObjectInputStream s)
 565         throws ClassNotFoundException, IOException
 566     {
 567         ObjectInputStream.GetField f = s.readFields();
 568 
 569         hgap = f.get("hgap", 0);
 570         vgap = f.get("vgap", 0);
 571 
 572         if (f.defaulted("vector")) {
 573             //  pre-1.4 stream
 574             Hashtable<String, Component> tab = (Hashtable)f.get("tab", null);
 575             vector = new Vector<>();
 576             if (tab != null && !tab.isEmpty()) {
 577                 for (Enumeration<String> e = tab.keys() ; e.hasMoreElements() ; ) {
 578                     String key = e.nextElement();
 579                     Component comp = tab.get(key);
 580                     vector.add(new Card(key, comp));
 581                     if (comp.isVisible()) {
 582                         currentCard = vector.size() - 1;
 583                     }
 584                 }
 585             }
 586         } else {
 587             vector = (Vector)f.get("vector", null);
 588             currentCard = f.get("currentCard", 0);
 589         }
 590     }
 591 
 592     /**
 593      * Writes serializable fields to stream.
 594      */
 595     private void writeObject(ObjectOutputStream s)
 596         throws IOException
 597     {
 598         Hashtable<String, Component> tab = new Hashtable<>();
 599         int ncomponents = vector.size();
 600         for (int i = 0; i < ncomponents; i++) {
 601             Card card = vector.get(i);
 602             tab.put(card.name, card.comp);
 603         }
 604 
 605         ObjectOutputStream.PutField f = s.putFields();
 606         f.put("hgap", hgap);
 607         f.put("vgap", vgap);
 608         f.put("vector", vector);
 609         f.put("currentCard", currentCard);
 610         f.put("tab", tab);
 611         s.writeFields();
 612     }
 613 }