001 /* JLayeredPane.java --
002 Copyright (C) 2002, 2004 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.awt.Color;
042 import java.awt.Component;
043 import java.awt.Container;
044 import java.awt.Graphics;
045 import java.awt.Rectangle;
046 import java.util.ArrayList;
047 import java.util.Hashtable;
048
049 import javax.accessibility.Accessible;
050 import javax.accessibility.AccessibleContext;
051 import javax.accessibility.AccessibleRole;
052
053 /**
054 * A container that adds depth to the usual <code>Container</code> semantics.
055 * Each child component of a <code>Layered Pane</code> is placed within one
056 * of several layers. <code>JLayeredPane</code> defines a set of standard
057 * layers. The pre-defined sets are (in the order from button to top):
058 *
059 * <dl>
060 * <dt>{@link #DEFAULT_LAYER}</dt>
061 * <dd>The layer where most of the normal components are placed. This
062 * is the bottommost layer.</dd>
063 *
064 * <dt>{@link #PALETTE_LAYER}</dt>
065 * <dd>Palette windows are placed in this layer.</dd>
066 *
067 * <dt>{@link #MODAL_LAYER}</dt>
068 * <dd>The layer where internal modal dialog windows are placed.</dd>
069 *
070 * <dt>{@link #POPUP_LAYER}</dt>
071 * <dd>The layer for popup menus</dd>
072 *
073 * <dt>{@link #DRAG_LAYER}</dt>
074 * <dd>Components that are beeing dragged are temporarily placed in
075 * this layer.</dd>
076 * </dl>
077 *
078 * <p>A child is in exactly one of these layers at any time, though there may
079 * be other layers if someone creates them.</p>
080 *
081 * <p>You can add a component to a specific layer using the
082 * {@link Container#add(Component, Object)} method. I.e.
083 * <code>layeredPane.add(comp, JLayeredPane.MODAL_LAYER)</code> will add the
084 * component <code>comp</code> to the modal layer of <code>layeredPane</code>.
085 * </p>
086 *
087 * <p>To change the layer of a component that is already a child of
088 * a <code>JLayeredPane</code>, use the {@link #setLayer(Component, int)}
089 * method.</p>
090 *
091 * <p>The purpose of this class is to translate this view of "layers" into a
092 * contiguous array of components: the one held in our ancestor,
093 * {@link java.awt.Container}.</p>
094 *
095 * <p>There is a precise set of words we will use to refer to numbers within
096 * this class:</p>
097 *
098 * <dl>
099 * <dt>Component Index:</dt>
100 * <dd>An offset into the <code>component</code> array held in our ancestor,
101 * {@link java.awt.Container}, from <code>[0 .. component.length)</code>. The drawing
102 * rule with indices is that 0 is drawn last.</dd>
103 *
104 * <dt>Layer Number:</dt>
105 * <dd>A general <code>int</code> specifying a layer within this component. Negative
106 * numbers are drawn first, then layer 0, then positive numbered layers, in
107 * ascending order.</dd>
108 *
109 * <dt>Position:</dt>
110 * <dd>An offset into a layer's "logical drawing order". Layer position 0
111 * is drawn last. Layer position -1 is a synonym for the first layer
112 * position (the logical "bottom").</dd>
113 * </dl>
114 *
115 * <p><b>Note:</b> the layer numbering order is the <em>reverse</em> of the
116 * component indexing and position order</p>
117 *
118 * @author Graydon Hoare (graydon@redhat.com)
119 * @author Roman Kennke (kennke@aicas.com)
120 */
121 public class JLayeredPane extends JComponent implements Accessible
122 {
123
124 /**
125 * Provides accessibility support for <code>JLayeredPane</code>.
126 */
127 protected class AccessibleJLayeredPane extends AccessibleJComponent
128 {
129 /**
130 * Creates a new instance of <code>AccessibleJLayeredPane</code>.
131 */
132 protected AccessibleJLayeredPane()
133 {
134 // Nothing to do here.
135 }
136
137 /**
138 * Returns the accessble role of <code>JLayeredPane</code>,
139 * {@link AccessibleRole#LAYERED_PANE}.
140 */
141 public AccessibleRole getAccessibleRole()
142 {
143 return AccessibleRole.LAYERED_PANE;
144 }
145 }
146
147 private static final long serialVersionUID = 5534920399324590459L;
148
149 public static final String LAYER_PROPERTY = "layeredContainerLayer";
150
151 public static final Integer FRAME_CONTENT_LAYER = new Integer(-30000);
152
153 public static final Integer DEFAULT_LAYER = new Integer(0);
154 public static final Integer PALETTE_LAYER = new Integer(100);
155 public static final Integer MODAL_LAYER = new Integer(200);
156 public static final Integer POPUP_LAYER = new Integer(300);
157 public static final Integer DRAG_LAYER = new Integer(400);
158
159 private Hashtable componentToLayer; // Component -> Layer Number (Integer)
160
161 public JLayeredPane()
162 {
163 componentToLayer = new Hashtable();
164 setLayout(null);
165 }
166
167 /**
168 * Looks up the layer a child component is currently assigned to.
169 *
170 * If <code>c</code> is an instance of {@link JComponent}, then the layer
171 * is fetched from the client property with the key {@link #LAYER_PROPERTY}.
172 * Otherwise it is looked up in an internal hashtable that maps
173 * non-JComponent components to layers. If the components cannot be found
174 * in either way, the {@link #DEFAULT_LAYER} is returned.
175 *
176 * @param c the component to look up.
177 *
178 * @return the layer the component is currently assigned to; if the component
179 * is not in this layered pane, then 0 (DEFAULT_LAYER) is returned
180 */
181 public int getLayer(Component c)
182 {
183 Integer layerObj;
184 if (c instanceof JComponent)
185 {
186 JComponent jc = (JComponent) c;
187 layerObj = (Integer) jc.getClientProperty(LAYER_PROPERTY);
188 }
189 else
190 layerObj = (Integer) componentToLayer.get(c);
191
192 if (layerObj == null)
193 layerObj = DEFAULT_LAYER;
194
195 return layerObj.intValue();
196 }
197
198 /**
199 * Looks up the layer in the client property with the key
200 * {@link #LAYER_PROPERTY} of <code>comp</code>. If no such property can be
201 * found, we return <code>0</code> ({@link #DEFAULT_LAYER}).
202 *
203 * @param comp the component for which the layer is looked up
204 *
205 * @return the layer of <code>comp</code> as stored in the corresponding
206 * client property, or <code>0</code> if there is no such property
207 */
208 public static int getLayer(JComponent comp)
209 {
210 Integer layerObj = (Integer) comp.getClientProperty(LAYER_PROPERTY);
211 if (layerObj == null)
212 layerObj = DEFAULT_LAYER;
213 return layerObj.intValue();
214 }
215
216 /**
217 * Returns the first JLayeredPane that contains the Component
218 * <code>comp</code> or <code>null</code> if <code>comp</code> is
219 * not contained in a JLayeredPane.
220 *
221 * @param comp the component for which we are searching the JLayeredPane
222 * ancestor
223 *
224 * @return the first JLayeredPane that contains the Component
225 * <code>comp</code> or <code>null</code> if <code>comp</code> is
226 * not contained in a JLayeredPane
227 */
228 public static JLayeredPane getLayeredPaneAbove(Component comp)
229 {
230 JLayeredPane lp = (JLayeredPane) SwingUtilities.getAncestorOfClass
231 (JLayeredPane.class, comp);
232 return lp;
233 }
234
235 /**
236 * Return the greatest layer number currently in use, in this container.
237 * This number may legally be positive <em>or</em> negative.
238 *
239 * @return the highest layer number
240 *
241 * @see #lowestLayer()
242 */
243 public int highestLayer()
244 {
245 Component[] components = getComponents();
246 int highest;
247 if (components.length == 0)
248 highest = 0;
249 else
250 {
251 highest = Integer.MIN_VALUE;
252 for (int i = 0; i < components.length; i++)
253 highest = Math.max(highest, getLayer(components[i]));
254 }
255 return highest;
256 }
257
258 /**
259 * Return the least layer number currently in use, in this container.
260 * This number may legally be positive <em>or</em> negative.
261 *
262 * @return the least layer number
263 *
264 * @see #highestLayer()
265 */
266 public int lowestLayer()
267 {
268 Component[] components = getComponents();
269 int lowest;
270 if (components.length == 0)
271 lowest = 0;
272 else
273 {
274 lowest = Integer.MAX_VALUE;
275 for (int i = 0; i < components.length; i++)
276 lowest = Math.max(lowest, getLayer(components[i]));
277 }
278 return lowest;
279 }
280
281 /**
282 * Moves a component to the "front" of its layer. The "front" is a
283 * synonym for position 0, which is also the last position drawn in each
284 * layer, so is usually the component which occludes the most other
285 * components in its layer.
286 *
287 * @param c the component to move to the front of its layer
288 *
289 * @see #moveToBack
290 */
291 public void moveToFront(Component c)
292 {
293 setPosition (c, 0);
294 }
295
296 /**
297 * <p>Moves a component to the "back" of its layer. The "back" is a
298 * synonym for position N-1 (also known as position -1), where N is the
299 * size of the layer.</p>
300 *
301 * <p>The "back" of a layer is the first position drawn, so the component at
302 * the "back" is usually the component which is occluded by the most
303 * other components in its layer.</p>
304 *
305 * @param c the component to move to the back of its layer.
306 *
307 * @see #moveToFront
308 */
309 public void moveToBack(Component c)
310 {
311 setPosition (c, -1);
312 }
313
314 /**
315 * Return the position of a component within its layer. Positions are assigned
316 * from the "front" (position 0) to the "back" (position N-1), and drawn from
317 * the back towards the front.
318 *
319 * @param c the component to get the position of
320 *
321 * @return the position of <code>c</code> within its layer or -1 if
322 * <code>c</code> is not a child of this layered pane
323 *
324 * @see #setPosition
325 */
326 public int getPosition(Component c)
327 {
328 int pos = -1;
329 int index = getIndexOf(c);
330 if (index >= 0)
331 {
332 pos = 0;
333 int layer = getLayer(c);
334 for (int i = index - 1; i >= 0; --i)
335 {
336 if (layer == getLayer(getComponent(i)))
337 pos++;
338 else
339 break;
340 }
341 }
342 return pos;
343 }
344
345 /**
346 * Change the position of a component within its layer. Positions are assigned
347 * from the "front" (position 0) to the "back" (position N-1), and drawn from
348 * the back towards the front.
349 *
350 * @param c the component to change the position of
351 * @param position the position to assign the component to
352 *
353 * @see #getPosition
354 */
355 public void setPosition(Component c, int position)
356 {
357 setLayer(c, getLayer(c), position);
358 }
359
360 /**
361 * Return an array of all components within a layer of this
362 * container. Components are ordered front-to-back, with the "front"
363 * element (which draws last) at position 0 of the returned array.
364 *
365 * @param layer the layer to return components from
366 *
367 * @return the components in the layer
368 */
369 public Component[] getComponentsInLayer(int layer)
370 {
371 Component[] inLayer = new Component[getComponentCountInLayer(layer)];
372 Component[] components = getComponents();
373 int j = 0;
374 for (int i = 0; i < components.length; ++i)
375 {
376 if (layer == getLayer(components[i]))
377 {
378 inLayer[j] = components[i];
379 j++;
380 }
381 }
382 return inLayer;
383 }
384
385 /**
386 * Return the number of components within a layer of this
387 * container.
388 *
389 * @param layer the layer count components in
390 *
391 * @return the number of components in the layer
392 */
393 public int getComponentCountInLayer(int layer)
394 {
395 Component[] components = getComponents();
396 int count = 0;
397 for (int i = components.length - 1; i >= 0; --i)
398 {
399 if (getLayer(components[i]) == layer)
400 count++;
401 }
402 return count;
403 }
404
405 /**
406 * Return a hashtable mapping child components of this container to
407 * Integer objects representing the component's layer assignments.
408 */
409 protected Hashtable<Component, Integer> getComponentToLayer()
410 {
411 return componentToLayer;
412 }
413
414 /**
415 * Return the index of a component within the underlying (contiguous)
416 * array of children. This is a "raw" number which does not represent the
417 * child's position in a layer, but rather its position in the logical
418 * drawing order of all children of the container.
419 *
420 * @param c the component to look up.
421 *
422 * @return the external index of the component or <code>-1</code> if
423 * <code>c</code> is not a child of this layered pane
424 */
425 public int getIndexOf(Component c)
426 {
427 return getComponentZOrder(c);
428 }
429
430 /**
431 * Return an Integer object which holds the same int value as the
432 * parameter. This is strictly an optimization to minimize the number of
433 * identical Integer objects which we allocate.
434 *
435 * @param layer the layer number as an int.
436 *
437 * @return the layer number as an Integer, possibly shared.
438 */
439 protected Integer getObjectForLayer(int layer)
440 {
441 switch (layer)
442 {
443 case -30000:
444 return FRAME_CONTENT_LAYER;
445
446 case 0:
447 return DEFAULT_LAYER;
448
449 case 100:
450 return PALETTE_LAYER;
451
452 case 200:
453 return MODAL_LAYER;
454
455 case 300:
456 return POPUP_LAYER;
457
458 case 400:
459 return DRAG_LAYER;
460
461 default:
462 break;
463 }
464
465 return new Integer(layer);
466 }
467
468 /**
469 * Computes an index at which to request the superclass {@link
470 * java.awt.Container} inserts a component, given an abstract layer and
471 * position number.
472 *
473 * @param layer the layer in which to insert a component.
474 * @param position the position in the layer at which to insert a component.
475 *
476 * @return the index at which to insert the component.
477 */
478 protected int insertIndexForLayer(int layer, int position)
479 {
480 return insertIndexForLayer(null, layer, position);
481 }
482
483 /**
484 * Similar to {@link #insertIndexForLayer(int, int)}, only that it takes a
485 * component parameter, which should be ignored in the search. This is
486 * necessary to support {@link #setLayer(Component, int, int)} which uses
487 * Container.setComponentZOrder(), which doesn't remove the component.
488 *
489 * @param comp the component to ignore
490 * @param layer the layer
491 * @param position the position
492 *
493 * @return the insertion index
494 */
495 private int insertIndexForLayer(Component comp, int layer, int position)
496 {
497 // Create the component list to search through.
498 ArrayList l = new ArrayList();
499 int count = getComponentCount();
500 for (int i = 0; i < count; i++)
501 {
502 Component c = getComponent(i);
503 if (c != comp)
504 l.add(c);
505 }
506
507 count = l.size();
508 int layerStart = -1;
509 int layerEnd = -1;
510 for (int i = 0; i < count; i++)
511 {
512 int layerOfComponent = getLayer((Component) l.get(i));
513 if (layerStart == -1 && layerOfComponent == layer)
514 layerStart = i;
515 if (layerOfComponent < layer)
516 {
517 // We are beyond the layer that we are looking for. Update the
518 // layerStart and layerEnd and exit the loop.
519 if (i == 0)
520 {
521 layerStart = 0;
522 layerEnd = 0;
523 }
524 else
525 layerEnd = i;
526 break;
527 }
528 }
529
530 // No layer found. The requested layer is lower than any existing layer,
531 // put the component at the end.
532 int insertIndex;
533 if (layerStart == -1 && layerEnd == -1)
534 {
535 insertIndex = count;
536 }
537 else
538 {
539 // Corner cases.
540 if (layerStart != -1 && layerEnd == -1)
541 layerEnd = count;
542 if (layerStart == -1 && layerEnd != -1)
543 layerStart = layerEnd;
544
545 // Adding to the bottom of a layer returns the end index
546 // in the layer.
547 if (position == -1)
548 insertIndex = layerEnd;
549 else
550 {
551 // Insert into a layer.
552 if (position > -1 && layerStart + position <= layerEnd)
553 insertIndex = layerStart + position;
554 else
555 insertIndex = layerEnd;
556 }
557 }
558 return insertIndex;
559 }
560
561 /**
562 * Removes a child from this container. The child is specified by
563 * index. After removal, the child no longer occupies a layer.
564 *
565 * @param index the index of the child component to remove.
566 */
567 public void remove(int index)
568 {
569 Component c = getComponent(index);
570 if (! (c instanceof JComponent))
571 componentToLayer.remove(c);
572 super.remove(index);
573 }
574
575 /**
576 * Removes all components from this container.
577 *
578 * @since 1.5
579 */
580 public void removeAll()
581 {
582 componentToLayer.clear();
583 super.removeAll();
584 }
585
586 /**
587 * <p>Set the layer property for a component, within this container. The
588 * component will be implicitly mapped to the bottom-most position in the
589 * layer, but only if added <em>after</em> calling this method.</p>
590 *
591 * <p>Read that carefully: this method should be called <em>before</em> the
592 * component is added to the container.</p>
593 *
594 * @param c the component to set the layer property for.
595 * @param layer the layer number to assign to the component.
596 */
597 public void setLayer(Component c, int layer)
598 {
599 setLayer(c, layer, -1);
600 }
601
602 /**
603 * Set the layer and position of a component, within this container.
604 *
605 * @param c the child component to set the layer property for.
606 * @param layer the layer number to assign to the component.
607 * @param position the position number to assign to the component.
608 */
609 public void setLayer(Component c, int layer, int position)
610 {
611 Integer layerObj = getObjectForLayer(layer);
612
613 // Nothing to do if neither the layer nor the position is
614 // changed.
615 if (layer != getLayer(c) || position != getPosition(c))
616 {
617 // Store the layer either in the JComponent or in the hashtable
618 if (c instanceof JComponent)
619 {
620 JComponent jc = (JComponent) c;
621 jc.putClientProperty(LAYER_PROPERTY, layerObj);
622 }
623 else
624 componentToLayer.put (c, layerObj);
625
626 // Update the component in the Z order of the Container.
627 Container parent = c.getParent();
628 if (parent == this)
629 {
630 int index = insertIndexForLayer(c, layer, position);
631 setComponentZOrder(c, index);
632 }
633 }
634 repaint(c.getX(), c.getY(), c.getWidth(), c.getHeight());
635 }
636
637 /**
638 * Overrides the default implementation from {@link java.awt.Container}
639 * such that <code>layerConstraint</code> is interpreted as an {@link
640 * Integer}, specifying the layer to which the component will be added
641 * (at the bottom position).
642 *
643 * The argument <code>index</code> specifies the position within the layer
644 * at which the component should be added, where <code>0</code> is the top
645 * position greater values specify positions below that and <code>-1</code>
646 * specifies the bottom position.
647 *
648 * @param comp the component to add
649 * @param layerConstraint an integer specifying the layer to add the
650 * component to
651 * @param index the position within the layer
652 */
653 protected void addImpl(Component comp, Object layerConstraint, int index)
654 {
655 int layer;
656 if (layerConstraint != null && layerConstraint instanceof Integer)
657 {
658 layer = ((Integer) layerConstraint).intValue();
659 setLayer(comp, layer);
660 }
661 else
662 layer = getLayer(comp);
663
664 int newIdx = insertIndexForLayer(layer, index);
665 super.addImpl(comp, layerConstraint, newIdx);
666 comp.validate();
667 comp.repaint();
668 }
669
670 /**
671 * Sets the layer property for a JComponent.
672 *
673 * @param component the component for which to set the layer
674 * @param layer the layer property to set
675 */
676 public static void putLayer(JComponent component, int layer)
677 {
678 component.putClientProperty(LAYER_PROPERTY, new Integer(layer));
679 }
680
681 /**
682 * Returns the accessible context for this <code>JLayeredPane</code>.
683 *
684 * @return the accessible context for this <code>JLayeredPane</code>
685 */
686 public AccessibleContext getAccessibleContext()
687 {
688 if (accessibleContext == null)
689 accessibleContext = new AccessibleJLayeredPane();
690 return accessibleContext;
691 }
692
693 /**
694 * This method is overridden order to provide a reasonable painting
695 * mechanism for <code>JLayeredPane</code>. This is necessary since
696 * <code>JLayeredPane</code>'s do not have an own UI delegate.
697 *
698 * Basically this method clears the background for the
699 * <code>JLayeredPane</code> and then calls <code>super.paint(g)</code>.
700 *
701 * @param g the graphics context to use
702 */
703 public void paint(Graphics g)
704 {
705 if (isOpaque())
706 {
707 Color oldColor = g.getColor();
708 Rectangle clip = g.getClipBounds();
709 g.setColor(getBackground());
710 g.fillRect(clip.x, clip.y, clip.width, clip.height);
711 g.setColor(oldColor);
712 }
713 super.paint(g);
714 }
715
716 /**
717 * Returns <code>false</code> if components in this layered pane can overlap,
718 * otherwise <code>true</code>.
719 *
720 * @return <code>false</code> if components in this layered pane can overlap,
721 * otherwise <code>true</code>
722 */
723 public boolean isOptimizedDrawingEnabled()
724 {
725 int numChildren = getComponentCount();
726 boolean result = true;
727 for (int i = 0; i < numChildren; ++i)
728 {
729 Component c1 = getComponent(i);
730 if (! c1.isVisible())
731 continue;
732 Rectangle r1 = c1.getBounds();
733 if (r1.isEmpty())
734 continue;
735
736 for (int j = i + 1; j < numChildren; ++j)
737 {
738 Component c2 = getComponent(j);
739 if (! c2.isVisible())
740 continue;
741 Rectangle r2 = c2.getBounds();
742 if (r2.isEmpty())
743 continue;
744 if (r1.intersects(r2))
745 {
746 result = false;
747 break;
748 }
749 if (result == false)
750 break;
751 }
752 }
753 return result;
754 }
755 }