Frames | No Frames |
1: /* JLayeredPane.java -- 2: Copyright (C) 2002, 2004 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing; 40: 41: import java.awt.Color; 42: import java.awt.Component; 43: import java.awt.Container; 44: import java.awt.Graphics; 45: import java.awt.Rectangle; 46: import java.awt.Shape; 47: import java.util.Hashtable; 48: import java.util.Iterator; 49: import java.util.Map; 50: import java.util.TreeMap; 51: 52: import javax.accessibility.Accessible; 53: import javax.accessibility.AccessibleContext; 54: import javax.accessibility.AccessibleRole; 55: 56: /** 57: * A container that adds depth to the usual <code>Container</code> semantics. 58: * Each child component of a <code>Layered Pane</code> is placed within one 59: * of several layers. <code>JLayeredPane</code> defines a set of standard 60: * layers. The pre-defined sets are (in the order from button to top): 61: * 62: * <dl> 63: * <dt>{@link #DEFAULT_LAYER}</dt> 64: * <dd>The layer where most of the normal components are placed. This 65: * is the bottommost layer.</dd> 66: * 67: * <dt>{@link #PALETTE_LAYER}</dt> 68: * <dd>Palette windows are placed in this layer.</dd> 69: * 70: * <dt>{@link #MODAL_LAYER}</dt> 71: * <dd>The layer where internal modal dialog windows are placed.</dd> 72: * 73: * <dt>{@link #POPUP_LAYER}</dt> 74: * <dd>The layer for popup menus</dd> 75: * 76: * <dt>{@link #DRAG_LAYER}</dt> 77: * <dd>Components that are beeing dragged are temporarily placed in 78: * this layer.</dd> 79: * </dl> 80: * 81: * <p>A child is in exactly one of these layers at any time, though there may 82: * be other layers if someone creates them.</p> 83: * 84: * <p>You can add a component to a specific layer using the 85: * {@link Container#add(Component, Object)} method. I.e. 86: * <code>layeredPane.add(comp, JLayeredPane.MODAL_LAYER)</code> will add the 87: * component <code>comp</code> to the modal layer of <code>layeredPane</code>. 88: * </p> 89: * 90: * <p>To change the layer of a component that is already a child of 91: * a <code>JLayeredPane</code>, use the {@link #setLayer(Component, int)} 92: * method.</p> 93: * 94: * <p>The purpose of this class is to translate this view of "layers" into a 95: * contiguous array of components: the one held in our ancestor, 96: * {@link java.awt.Container}.</p> 97: * 98: * <p>There is a precise set of words we will use to refer to numbers within 99: * this class:</p> 100: * 101: * <dl> 102: * <dt>Component Index:</dt> 103: * <dd>An offset into the <code>component</code> array held in our ancestor, 104: * {@link java.awt.Container}, from <code>[0 .. component.length)</code>. The drawing 105: * rule with indices is that 0 is drawn last.</dd> 106: * 107: * <dt>Layer Number:</dt> 108: * <dd>A general <code>int</code> specifying a layer within this component. Negative 109: * numbers are drawn first, then layer 0, then positive numbered layers, in 110: * ascending order.</dd> 111: * 112: * <dt>Position:</dt> 113: * <dd>An offset into a layer's "logical drawing order". Layer position 0 114: * is drawn last. Layer position -1 is a synonym for the first layer 115: * position (the logical "bottom").</dd> 116: * </dl> 117: * 118: * <p><b>Note:</b> the layer numbering order is the <em>reverse</em> of the 119: * component indexing and position order</p> 120: * 121: * @author Graydon Hoare (graydon@redhat.com) 122: */ 123: public class JLayeredPane extends JComponent implements Accessible 124: { 125: 126: /** 127: * Provides accessibility support for <code>JLayeredPane</code>. 128: */ 129: protected class AccessibleJLayeredPane extends AccessibleJComponent 130: { 131: /** 132: * Creates a new instance of <code>AccessibleJLayeredPane</code>. 133: */ 134: public AccessibleJLayeredPane() 135: { 136: // Nothing to do here. 137: } 138: 139: /** 140: * Returns the accessble role of <code>JLayeredPane</code>, 141: * {@link AccessibleRole#LAYERED_PANE}. 142: */ 143: public AccessibleRole getAccessibleRole() 144: { 145: return AccessibleRole.LAYERED_PANE; 146: } 147: } 148: 149: private static final long serialVersionUID = 5534920399324590459L; 150: 151: public static final String LAYER_PROPERTY = "layeredContainerLayer"; 152: 153: public static Integer FRAME_CONTENT_LAYER = new Integer (-30000); 154: 155: public static Integer DEFAULT_LAYER = new Integer (0); 156: public static Integer PALETTE_LAYER = new Integer (100); 157: public static Integer MODAL_LAYER = new Integer (200); 158: public static Integer POPUP_LAYER = new Integer (300); 159: public static Integer DRAG_LAYER = new Integer (400); 160: 161: TreeMap layers; // Layer Number (Integer) -> Layer Size (Integer) 162: Hashtable componentToLayer; // Component -> Layer Number (Integer) 163: 164: private transient Rectangle rectCache; 165: 166: public JLayeredPane() 167: { 168: layers = new TreeMap (); 169: componentToLayer = new Hashtable (); 170: setLayout(null); 171: } 172: 173: /** 174: * Looks up the layer a child component is currently assigned to. 175: * 176: * @param c the component to look up. 177: * @return the layer the component is currently assigned to, in this container. 178: * @throws IllegalArgumentException if the component is not a child of this container. 179: */ 180: public int getLayer(Component c) 181: { 182: Component myComp = c; 183: while(! componentToLayer.containsKey(myComp)) 184: { 185: myComp = myComp.getParent(); 186: if (myComp == null) 187: break; 188: } 189: if (myComp == null) 190: throw new IllegalArgumentException 191: ("component is not in this JLayeredPane"); 192: Integer layerObj = (Integer) componentToLayer.get(myComp); 193: return layerObj.intValue(); 194: } 195: 196: /** 197: * Looks up the layer of <code>comp</code> in the component's nearest 198: * JLayeredPane ancestor. If <code>comp</code> is not contained 199: * in a JLayeredPane, the value 0 (default layer) is returned. 200: * 201: * @param comp the component for which the layer is looked up 202: * 203: * @return the layer of <code>comp</code> in its nearest JLayeredPane 204: * ancestor 205: */ 206: public static int getLayer(JComponent comp) 207: { 208: JLayeredPane lp = (JLayeredPane) SwingUtilities.getAncestorOfClass 209: (JLayeredPane.class, comp); 210: if (lp == null) 211: return 0; 212: else 213: // The cast here forces the call to the instance method getLayer() 214: // instead of the static method (this would lead to infinite 215: // recursion). 216: return lp.getLayer((Component) comp); 217: } 218: 219: /** 220: * Returns the first JLayeredPane that contains the Component 221: * <code>comp</code> or <code>null</code> if <code>comp</code> is 222: * not contained in a JLayeredPane. 223: * 224: * @param comp the component for which we are searching the JLayeredPane 225: * ancestor 226: * 227: * @return the first JLayeredPane that contains the Component 228: * <code>comp</code> or <code>null</code> if <code>comp</code> is 229: * not contained in a JLayeredPane 230: */ 231: public static JLayeredPane getLayeredPaneAbove(Component comp) 232: { 233: JLayeredPane lp = (JLayeredPane) SwingUtilities.getAncestorOfClass 234: (JLayeredPane.class, comp); 235: return lp; 236: } 237: 238: /** 239: * <p>Returns a pair of ints representing a half-open interval 240: * <code>[top, bottom)</code>, which is the range of component indices 241: * the provided layer number corresponds to.</p> 242: * 243: * <p>Note that "bottom" is <em>not</em> included in the interval of 244: * component indices in this layer: a layer with 0 elements in it has 245: * <code>ret[0] == ret[1]</code>.</p> 246: * 247: * @param layer the layer to look up. 248: * @return the half-open range of indices this layer spans. 249: * @throws IllegalArgumentException if layer does not refer to an active layer 250: * in this container. 251: */ 252: private int[] layerToRange (Integer layer) 253: { 254: int[] ret = new int[2]; 255: ret[1] = getComponents ().length; 256: Iterator i = layers.entrySet ().iterator (); 257: while (i.hasNext()) 258: { 259: Map.Entry pair = (Map.Entry) i.next(); 260: Integer layerNum = (Integer) pair.getKey (); 261: Integer layerSz = (Integer) pair.getValue (); 262: int layerInt = layerNum.intValue(); 263: if (layerInt == layer.intValue()) 264: { 265: ret[0] = ret[1] - layerSz.intValue (); 266: break; 267: } 268: // In the following case there exists no layer with the specified 269: // number, so we return an empty interval here with the index at which 270: // such a layer would be inserted 271: else if (layerInt > layer.intValue()) 272: { 273: ret[1] = ret[0]; 274: break; 275: } 276: else 277: { 278: ret[1] -= layerSz.intValue (); 279: } 280: } 281: return ret; 282: } 283: 284: /** 285: * Increments the recorded size of a given layer. 286: * 287: * @param layer the layer number to increment. 288: * @see #incrLayer 289: */ 290: private void incrLayer(Integer layer) 291: { 292: int sz = 1; 293: if (layers.containsKey (layer)) 294: sz += ((Integer)(layers.get (layer))).intValue (); 295: layers.put (layer, new Integer(sz)); 296: } 297: 298: /** 299: * Decrements the recorded size of a given layer. 300: * 301: * @param layer the layer number to decrement. 302: * @see #incrLayer 303: */ 304: private void decrLayer(Integer layer) 305: { 306: int sz = 0; 307: if (layers.containsKey (layer)) 308: sz = ((Integer)(layers.get (layer))).intValue () - 1; 309: layers.put (layer, new Integer(sz)); 310: } 311: 312: /** 313: * Return the greatest layer number currently in use, in this container. 314: * This number may legally be positive <em>or</em> negative. 315: * 316: * @return the least layer number. 317: * @see #lowestLayer() 318: */ 319: public int highestLayer() 320: { 321: if (layers.size() == 0) 322: return 0; 323: return ((Integer)(layers.lastKey ())).intValue (); 324: } 325: 326: /** 327: * Return the least layer number currently in use, in this container. 328: * This number may legally be positive <em>or</em> negative. 329: * 330: * @return the least layer number. 331: * @see #highestLayer() 332: */ 333: public int lowestLayer() 334: { 335: if (layers.size() == 0) 336: return 0; 337: return ((Integer)(layers.firstKey ())).intValue (); 338: } 339: 340: /** 341: * Moves a component to the "front" of its layer. The "front" is a 342: * synonym for position 0, which is also the last position drawn in each 343: * layer, so is usually the component which occludes the most other 344: * components in its layer. 345: * 346: * @param c the component to move to the front of its layer. 347: * @throws IllegalArgumentException if the component is not a child of 348: * this container. 349: * @see #moveToBack 350: */ 351: public void moveToFront(Component c) 352: { 353: setPosition (c, 0); 354: } 355: 356: /** 357: * <p>Moves a component to the "back" of its layer. The "back" is a 358: * synonym for position N-1 (also known as position -1), where N is the 359: * size of the layer.</p> 360: * 361: * <p>The "back" of a layer is the first position drawn, so the component at 362: * the "back" is usually the component which is occluded by the most 363: * other components in its layer.</p> 364: * 365: * @param c the component to move to the back of its layer. 366: * @throws IllegalArgumentException if the component is not a child of 367: * this container. 368: * @see #moveToFront 369: */ 370: public void moveToBack(Component c) 371: { 372: setPosition (c, -1); 373: } 374: 375: /** 376: * Return the position of a component within its layer. Positions are assigned 377: * from the "front" (position 0) to the "back" (position N-1), and drawn from 378: * the back towards the front. 379: * 380: * @param c the component to get the position of. 381: * @throws IllegalArgumentException if the component is not a child of 382: * this container. 383: * @see #setPosition 384: */ 385: public int getPosition(Component c) 386: { 387: int layer = getLayer (c); 388: int[] range = layerToRange(new Integer(layer)); 389: int top = range[0]; 390: int bot = range[1]; 391: Component[] comps = getComponents (); 392: for (int i = top; i < bot; ++i) 393: { 394: if (comps[i] == c) 395: return i - top; 396: } 397: // should have found it 398: throw new IllegalArgumentException (); 399: } 400: 401: /** 402: * Change the position of a component within its layer. Positions are assigned 403: * from the "front" (position 0) to the "back" (position N-1), and drawn from 404: * the back towards the front. 405: * 406: * @param c the component to change the position of. 407: * @param position the position to assign the component to. 408: * @throws IllegalArgumentException if the component is not a child of 409: * this container. 410: * @see #getPosition 411: */ 412: public void setPosition(Component c, int position) 413: { 414: int layer = getLayer (c); 415: int[] range = layerToRange(new Integer(layer)); 416: if (range[0] == range[1]) 417: throw new IllegalArgumentException (); 418: 419: int top = range[0]; 420: int bot = range[1]; 421: if (position == -1) 422: position = (bot - top) - 1; 423: int targ = Math.min(top + position, bot-1); 424: int curr = -1; 425: 426: Component[] comps = getComponents(); 427: for (int i = top; i < bot; ++i) 428: { 429: if (comps[i] == c) 430: { 431: curr = i; 432: break; 433: } 434: } 435: if (curr == -1) 436: // should have found it 437: throw new IllegalArgumentException(); 438: 439: super.swapComponents (curr, targ); 440: revalidate(); 441: repaint(); 442: } 443: 444: /** 445: * Return an array of all components within a layer of this 446: * container. Components are ordered front-to-back, with the "front" 447: * element (which draws last) at position 0 of the returned array. 448: * 449: * @param layer the layer to return components from. 450: * @return the components in the layer. 451: */ 452: public Component[] getComponentsInLayer(int layer) 453: { 454: int[] range = layerToRange (getObjectForLayer (layer)); 455: if (range[0] == range[1]) 456: return new Component[0]; 457: else 458: { 459: Component[] comps = getComponents (); 460: int sz = range[1] - range[0]; 461: Component[] nc = new Component[sz]; 462: for (int i = 0; i < sz; ++i) 463: nc[i] = comps[range[0] + i]; 464: return nc; 465: } 466: } 467: 468: /** 469: * Return the number of components within a layer of this 470: * container. 471: * 472: * @param layer the layer count components in. 473: * @return the number of components in the layer. 474: */ 475: public int getComponentCountInLayer(int layer) 476: { 477: int[] range = layerToRange (getObjectForLayer (layer)); 478: if (range[0] == range[1]) 479: return 0; 480: else 481: return (range[1] - range[0]); 482: } 483: 484: /** 485: * Return a hashtable mapping child components of this container to 486: * Integer objects representing the component's layer assignments. 487: */ 488: protected Hashtable getComponentToLayer() 489: { 490: return componentToLayer; 491: } 492: 493: /** 494: * Return the index of a component within the underlying (contiguous) 495: * array of children. This is a "raw" number which does not represent the 496: * child's position in a layer, but rather its position in the logical 497: * drawing order of all children of the container. 498: * 499: * @param c the component to look up. 500: * @return the external index of the component. 501: * @throws IllegalArgumentException if the component is not a child of 502: * this container. 503: */ 504: public int getIndexOf(Component c) 505: { 506: int layer = getLayer (c); 507: int[] range = layerToRange(new Integer(layer)); 508: Component[] comps = getComponents(); 509: for (int i = range[0]; i < range[1]; ++i) 510: { 511: if (comps[i] == c) 512: return i; 513: } 514: // should have found the component during iteration 515: throw new IllegalArgumentException (); 516: } 517: 518: /** 519: * Return an Integer object which holds the same int value as the 520: * parameter. This is strictly an optimization to minimize the number of 521: * identical Integer objects which we allocate. 522: * 523: * @param layer the layer number as an int. 524: * @return the layer number as an Integer, possibly shared. 525: */ 526: protected Integer getObjectForLayer(int layer) 527: { 528: switch (layer) 529: { 530: case -30000: 531: return FRAME_CONTENT_LAYER; 532: 533: case 0: 534: return DEFAULT_LAYER; 535: 536: case 100: 537: return PALETTE_LAYER; 538: 539: case 200: 540: return MODAL_LAYER; 541: 542: case 300: 543: return POPUP_LAYER; 544: 545: case 400: 546: return DRAG_LAYER; 547: 548: default: 549: break; 550: } 551: 552: return new Integer(layer); 553: } 554: 555: /** 556: * Computes an index at which to request the superclass {@link 557: * java.awt.Container} inserts a component, given an abstract layer and 558: * position number. 559: * 560: * @param layer the layer in which to insert a component. 561: * @param position the position in the layer at which to insert a component. 562: * @return the index at which to insert the component. 563: */ 564: protected int insertIndexForLayer(int layer, int position) 565: { 566: 567: Integer lobj = getObjectForLayer (layer); 568: if (! layers.containsKey(lobj)) 569: layers.put (lobj, new Integer (0)); 570: int[] range = layerToRange (lobj); 571: if (range[0] == range[1]) 572: return range[0]; 573: 574: int top = range[0]; 575: int bot = range[1]; 576: 577: if (position == -1 || position > (bot - top)) 578: return bot; 579: else 580: return top + position; 581: } 582: 583: /** 584: * Removes a child from this container. The child is specified by 585: * index. After removal, the child no longer occupies a layer. 586: * 587: * @param index the index of the child component to remove. 588: */ 589: public void remove(int index) 590: { 591: Component c = getComponent(index); 592: int layer = getLayer(c); 593: decrLayer(new Integer(layer)); 594: componentToLayer.remove(c); 595: super.remove(index); 596: // FIXME: Figure out if this call is correct. 597: revalidate(); 598: } 599: 600: /** 601: * <p>Set the layer property for a component, within this container. The 602: * component will be implicitly mapped to the bottom-most position in the 603: * layer, but only if added <em>after</em> calling this method.</p> 604: * 605: * <p>Read that carefully: this method should be called <em>before</em> the 606: * component is added to the container.</p> 607: * 608: * @param c the component to set the layer property for. 609: * @param layer the layer number to assign to the component. 610: */ 611: public void setLayer(Component c, int layer) 612: { 613: componentToLayer.put (c, getObjectForLayer (layer)); 614: } 615: 616: /** 617: * Set the layer and position of a component, within this container. 618: * 619: * @param c the child component to set the layer property for. 620: * @param layer the layer number to assign to the component. 621: * @param position the position number to assign to the component. 622: */ 623: public void setLayer(Component c, 624: int layer, 625: int position) 626: { 627: remove(c); 628: add(c, getObjectForLayer (layer)); 629: setPosition(c, position); 630: revalidate(); 631: repaint(); 632: } 633: 634: /** 635: * Overrides the default implementation from {@link java.awt.Container} 636: * such that <code>layerConstraint</code> is interpreted as an {@link 637: * Integer}, specifying the layer to which the component will be added 638: * (at the bottom position). 639: * 640: * @param comp the component to add. 641: * @param layerConstraint an integer specifying the layer to add the component to. 642: * @param index an ignored parameter, for compatibility. 643: */ 644: protected void addImpl(Component comp, Object layerConstraint, int index) 645: { 646: Integer layer; 647: if (layerConstraint != null && layerConstraint instanceof Integer) 648: layer = (Integer) layerConstraint; 649: else if (componentToLayer.containsKey (comp)) 650: layer = (Integer) componentToLayer.remove (comp); 651: else 652: layer = DEFAULT_LAYER; 653: 654: int newIdx = insertIndexForLayer(layer.intValue (), index); 655: 656: componentToLayer.put (comp, layer); 657: incrLayer (layer); 658: 659: super.addImpl(comp, null, newIdx); 660: } 661: 662: /** 663: * Sets the layer property for a JComponent. 664: * 665: * @param component the component for which to set the layer 666: * @param layer the layer property to set 667: */ 668: public static void putLayer(JComponent component, int layer) 669: { 670: getLayeredPaneAbove(component).setLayer(component, layer); 671: } 672: 673: /** 674: * Returns the accessible context for this <code>JLayeredPane</code>. 675: * 676: * @return the accessible context for this <code>JLayeredPane</code> 677: */ 678: public AccessibleContext getAccessibleContext() 679: { 680: if (accessibleContext == null) 681: accessibleContext = new AccessibleJLayeredPane(); 682: return accessibleContext; 683: } 684: 685: /** 686: * This method is overridden order to provide a reasonable painting 687: * mechanism for <code>JLayeredPane</code>. This is necessary since 688: * <code>JLayeredPane</code>'s do not have an own UI delegate. 689: * 690: * Basically this method clears the background for the 691: * <code>JLayeredPane</code> and then calls <code>super.paint(g)</code>. 692: * 693: * @param g the graphics context to use 694: */ 695: public void paint(Graphics g) 696: { 697: if (isOpaque()) 698: { 699: Color oldColor = g.getColor(); 700: Rectangle clip = g.getClipBounds(); 701: g.setColor(getBackground()); 702: g.fillRect(clip.x, clip.y, clip.width, clip.height); 703: g.setColor(oldColor); 704: } 705: super.paint(g); 706: } 707: 708: /** 709: * Overridden to return <code>false</code>, since <code>JLayeredPane</code> 710: * cannot guarantee that its children don't overlap. 711: * 712: * @return <code>false</code> 713: */ 714: public boolean isOptimizedDrawingEnabled() 715: { 716: return false; 717: } 718: }