/* class Container
*
* Copyright (C) 2001-2003 R M Pitman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package charva.awt;
import java.util.Enumeration;
import java.util.Vector;
import charva.awt.event.KeyEvent;
/**
* Container is the abstract superclass of Window and Panel.
*/
public abstract class Container
extends Component
{
public Container() {}
/**
* doLayout is intended to be used by subclasses of Container, such
* as Window, JPanel and JScrollPane.
*/
public void doLayout() {
if (_isValid)
return;
if (_layoutMgr != null) {
_layoutMgr.doLayout(this);
/* Don't set the _isValid flag if the layout manager flag
* is an instance of LayoutManager2; the doLayout method must
* be called every time because the parent window may have been
* resized.
* Instances of LayoutManager, on the other hand, are not affected
* be resizing of the parent window.
*/
if (_layoutMgr instanceof LayoutManager2 == false)
_isValid = true;
}
}
public Dimension getSize() { return new Dimension (_size); }
public int getHeight() { return _size.height; }
public int getWidth() { return _size.width; }
public void setSize(Dimension size_) {
_size = new Dimension(size_);
invalidate();
}
public void setSize(int width_, int height_) {
_size.width = width_;
_size.height = height_;
invalidate();
}
public void setHeight(int height_) {
_size.height = height_;
invalidate();
}
public void setWidth(int width_) {
_size.width = width_;
invalidate();
}
public Dimension minimumSize() {
if (_layoutMgr == null)
return _size;
if (_isValid == false)
_minimumSize = _layoutMgr.minimumSize(this);
return _minimumSize;
}
/** Returns the component at the specified index.
*/
public Component getComponent(int n) {
return (Component) _components.elementAt(n);
}
/** Returns the component that contains the specified point, or null
* if no component contains the point. The x and y coordinates of
* the point are relative to the origin of this container.
*/
public Component getComponentAt(Point p) {
return getComponentAt(p.x, p.y);
}
/** Returns the component that contains the specified point, or null
* if no component contains the point. The x and y coordinates of
* the point are relative to the origin of this container.
*/
public Component getComponentAt(int x, int y) {
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.contains(x, y)) {
if (c instanceof Container) {
// Calculate the coordinates of the point relative
// to the origin of the container
Point origin = c.getLocation();
return ((Container) c).getComponentAt(x-origin.x, y-origin.y);
}
else
return c;
}
}
return null;
}
/** The contained component will inherit the foreground and background
* colors of the container if they have not been set yet.
*/
public Component add(Component component_) {
_addComponent(component_);
if (_layoutMgr != null && _layoutMgr instanceof LayoutManager2) {
if (_layoutMgr instanceof BorderLayout) {
((BorderLayout) _layoutMgr).addLayoutComponent(
component_, BorderLayout.CENTER);
}
else {
throw new IllegalArgumentException(
"LayoutManager2 requires a constraint object");
}
}
invalidate();
return component_;
}
/**
* Removes the specified component from this container.
*/
public void remove(Component component_) {
_components.remove(component_);
component_.setParent(null);
if (_currentFocus == component_) {
_currentFocus = null;
_currentFocus = getCurrentFocus();
}
invalidate();
}
private void _addComponent(Component component_) {
/* Add the specified component to the list of components in this
* container.
*/
_components.addElement(component_);
/* Set this container as the parent of the component.
*/
component_.setParent(this);
}
/**
* Adds the specified component to the end of this container. Also
* notifies the layout manager to add this component to the layout
* using the specified constraint.
* If the layout manager does not implement the LayoutManager2 interface,
* i.e. does not know about layout constraints, we silently ignore the
* constraint (maybe fix this to throw an exception?).
*/
public void add(Component component_, Object constraint_) {
_addComponent(component_); // add to this container.
if (_layoutMgr != null && _layoutMgr instanceof LayoutManager2) {
((LayoutManager2) _layoutMgr).addLayoutComponent(
component_, constraint_);
}
}
public void setLayout(LayoutManager mgr_) { _layoutMgr = mgr_; }
/**
* Returns an array of all the components in this container.
*/
public Component[] getComponents() {
int arraylen = _components.size();
Component[] array = new Component[arraylen];
for (int i=0; i<arraylen; i++) {
array[i] = (Component) _components.elementAt(i);
}
return array;
}
/** Returns the number of components in this Container.
*/
public int getComponentCount() {
return _components.size();
}
/**
* Draw all the components in this container.
* @param toolkit
*/
public void draw(Toolkit toolkit) {
if ( !isVisible())
return;
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.isVisible())
c.draw(toolkit);
}
}
/**
* Sets the foreground color of this container and all its
* contained components that do not yet have their foreground
* color set. Overrides the same method in the Component class.
*/
public void setForeground(Color color_)
{
super.setForeground(color_);
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.getForeground() == null)
c.setForeground(color_);
}
}
/**
* Sets the background color of this container and all its
* contained components that do not yet have their background
* color set. Overrides the same method in the Component class.
*/
public void setBackground(Color color_)
{
super.setBackground(color_);
Enumeration <Component>e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.getBackground() == null)
c.setBackground(color_);
}
}
public void processKeyEvent(KeyEvent ke_) {
/** Invoke all the KeyListener callbacks that may have been registered
* for this Container.
*/
super.processKeyEvent(ke_);
if (ke_.isConsumed())
return;
/* Propagate the KeyEvent down to the current focus component
* inside this container.
*/
if (_currentFocus != null) {
_currentFocus.processKeyEvent(ke_);
}
}
public void requestFocus() {
getCurrentFocus().requestFocus();
}
/**
* Return a reference to the (non-container) component inside this
* Container that has the keyboard input focus (or would have it,
* if the focus was inside this container). If no component inside
* the container has the focus, choose the first FocusTraversable
* component.
* @return the Component in this container that would have the focus;
* never null.
* @exception IllegalComponentStateException if there is no
* focus-traversable component in this container.
*/
public Component getCurrentFocus() {
if (_currentFocus == null) {
/* _currentFocus is not yet set. Try to set it to the first
* FocusTraversable component contained in this container.
*/
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.isFocusTraversable()) {
_currentFocus = c;
break;
}
}
}
if (_currentFocus == null) {
throw new IllegalComponentStateException(
"no focus-traversable components inside this Container");
}
if (_currentFocus instanceof Container) {
return ((Container) _currentFocus).getCurrentFocus();
}
else
return _currentFocus;
}
/**
* Set the _currentFocus to refer to the next focus-traversable component
* in the list of contained components, and put FocusEvents on the queue,
* one for the component that is losing the focus and one for the component
* gaining the focus.
*/
public void nextFocus() {
/* Put a FOCUS_LOST event on the queue for the component that is
* losing the focus.
* If the current focus is a Container, then this method will have been
* called by that container (which would already have posted a
* FOCUS_LOST event for its own contained component that was losing
* focus).
if ((_currentFocus instanceof Container) == false) {
FocusEvent evt = new FocusEvent(AWTEvent.FOCUS_LOST, _currentFocus);
EventQueue evtQueue =
Toolkit.getDefaultToolkit().getSystemEventQueue();
evtQueue.postEvent(evt);
}
*/
/* Determine which component should get focus next.
*/
int index = _components.indexOf(_currentFocus);
if (index == -1) {
throw new IllegalComponentStateException(
"focus component not found in parent");
}
Component focusCandidate;
for (;;) {
/* If the focus was owned by the last component in this container,
* the new focus should go to the next component in the parent
* container, IF THERE IS A PARENT (this container may be a
* Window, in which case the parent is null).
*/
if (++index >= _components.size()) {
if (getParent() != null) {
getParent().nextFocus();
return;
}
else {
/* Don't need to worry about infinite loops. Worst case, we
* should just end up where we started.
*/
index = 0;
}
}
focusCandidate = (Component) _components.elementAt(index);
/* If the next component will not accept the focus, continue
* trying until we get one that does.
*/
if (focusCandidate.isFocusTraversable())
break;
}
if (focusCandidate instanceof Container)
((Container) focusCandidate).firstFocus();
focusCandidate.requestFocus();
}
/**
* Set the _currentFocus to refer to the previous focus-traversable
* component in the list of contained components, and put FocusEvents on
* the queue, one for the component that is losing the focus and one for
* the component gaining the focus.
*/
public void previousFocus() {
/* Put a FOCUS_LOST event on the queue for the component that is
* losing the focus.
* If the current focus is a Container, then this method will have been
* called by that container (which would already have posted a
* FOCUS_LOST event for its own contained component that was losing
* focus).
if ((_currentFocus instanceof Container) == false) {
FocusEvent evt = new FocusEvent(AWTEvent.FOCUS_LOST, _currentFocus);
EventQueue evtQueue =
Toolkit.getDefaultToolkit().getSystemEventQueue();
evtQueue.postEvent(evt);
}
*/
/* Determine which component should get focus next.
*/
int index = _components.indexOf(_currentFocus);
if (index == -1) {
throw new IllegalArgumentException(
"focus component not found in parent");
}
Component focusCandidate;
for (;;) {
/* If the focus was owned by the first component in this container,
* the new focus should go to the previous component in the parent
* container, IF THERE IS A PARENT (this container may be a
* Window, in which case the parent is null).
*/
if (--index < 0) {
if (getParent() != null) {
getParent().previousFocus();
return;
}
else {
index = _components.size() - 1;
}
}
focusCandidate = (Component) _components.elementAt(index);
/* If the next component will not accept the focus, continue
* trying until we get one that does.
*/
if (focusCandidate.isFocusTraversable())
break;
}
if (focusCandidate instanceof Container)
((Container) focusCandidate).lastFocus();
focusCandidate.requestFocus();
}
/**
* Set this container's current keyboard focus. Called by the
* requestFocus() method of the contained component.
*/
public void setFocus(Component focus_) {
_currentFocus = focus_;
if (getParent() != null)
getParent().setFocus(this);
}
/**
* Return true if any of the components within this Container
* are focus-traversable (i.e. will accept keyboard input focus when
* TAB or SHIFT-TAB is pressed).
*/
public boolean isFocusTraversable() {
if ( !super.isFocusTraversable())
return false;
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.isFocusTraversable())
return true;
}
return false;
}
public Insets getInsets() { return _insets; }
/* Default implementation of debug, gets overridden by subclasses.
*/
public void debug(int level_) {
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
c.debug(level_ + 1);
}
}
/**
* Sets the keyboard focus to the first component that is focusTraversable.
* Called by the nextFocus() method when it runs out of components in
* the current container to move the focus to. The nextFocus() method
* first checks that this container contains a focusTraversable component
* before calling this.
*/
private void firstFocus() {
Enumeration<Component> e = _components.elements();
while (e.hasMoreElements()) {
Component c = (Component) e.nextElement();
if (c.isFocusTraversable()) {
if (c instanceof Container) {
((Container) c).firstFocus();
}
_currentFocus = c;
return;
}
}
}
/**
* Sets the keyboard focus to the last component that is focusTraversable.
* Called by the previousFocus() method when it runs out of components in
* the current container to move the focus to. The previousFocus() method
* first checks that this container contains a focusTraversable component
* before calling this.
*/
private void lastFocus() {
for (int i=_components.size() - 1; i >= 0; i--) {
Component c = (Component) _components.elementAt(i);
if (c.isFocusTraversable()) {
if (c instanceof Container) {
((Container) c).lastFocus();
}
_currentFocus = c;
return;
}
}
}
/**
* Validates this container and all of its contained components.
* The programmer must call validate() on a container to cause it
* to re-layout its contained components after components have
* been added, removed or resized.
*/
public void validate() {
if (_isValid)
return;
/* doLayout sets the validate flag (unless the layout manager is
* an instance of LayoutManager2).
*/
doLayout();
}
/**
* Determines whether this component is valid. A container is valid when
* it is correctly sized and positioned within its parent container and
* all its children are also valid.
*/
public boolean isValid() { return _isValid; }
/**
* Marks the container and all parents above it as needing to be laid out
* again.
*/
public void invalidate() {
_isValid = false;
super.invalidate();
}
//====================================================================
// INSTANCE VARIABLES
/**
* The list of components contained within this Container.
*/
protected Vector<Component> _components = new Vector<Component>();
/** The container's size
*/
protected Dimension _size = new Dimension(1, 1);
/**
* The layout manager that will be used to lay out the components.
*/
protected LayoutManager _layoutMgr = null;
/**
* The component (which may itself be a Container) inside this Container
* that currently has the input focus (or, if the input focus is
* currently outside this Container, the component to which focus will
* return if and when this Container regains focus).
*/
protected Component _currentFocus = null;
/**
* The insets define how much padding to insert inside the Container,
* to take into account the border frame (if any).
* For a Window they will be (1,1); for a Panel, they will be (0,0).
*/
protected Insets _insets = new Insets(0,0,0,0);
/**
* A flag that is set to true when the container is laid out, and set to
* false when a component is added or removed from the container
* (indicating that it needs to be laid out again).
*/
protected boolean _isValid = false;
/**
* Used for caching the minimum size of this container, so that we don't
* have to keep recalculating it. This dimension is valid only if _isValid
* is true.
*/
protected Dimension _minimumSize;
}