/* class Component
*
* Copyright (C) 2001 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.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.Vector;
import charva.awt.event.AWTEvent;
import charva.awt.event.FocusEvent;
import charva.awt.event.FocusListener;
import charva.awt.event.KeyEvent;
import charva.awt.event.KeyListener;
import charva.awt.event.MouseEvent;
import charva.awt.event.PaintEvent;
import charva.awt.event.SyncEvent;
/**
* Component is the abstract superclass of all the other CHARVA widgets.
*/
public abstract class Component
{
/** Constructor
*/
public Component() {
}
/**
* Shows or hides this component depending on the value of the
* parameter <code>visible_</code>
*/
public void setVisible(boolean visible_)
{
if (visible_)
show();
else
hide();
}
/**
* @deprecated This method has been replaced by
* <code>setVisible(boolean)</code>.
*/
public void show()
{
if ( !_visible) {
_visible = true;
repaint(); // post a PaintEvent
}
}
/**
* @deprecated This method has been replaced by
* <code>setVisible(boolean)</code>.
*/
public void hide()
{
if (_visible) {
_visible = false;
// try to move focus to next focusTraversable component
if (hasFocus()) {
getParent().nextFocus();
if (hasFocus()) {
// there was no next focusTraversable component
getParent().previousFocus();
if (hasFocus()) {
throw new IllegalComponentStateException(
"cannot hide component; it was the only " +
"focusTraversable component in this window");
}
}
}
Component parent = getParent();
if(parent != null){
parent.repaint();
}
//repaint(); // post a PaintEvent
}
}
/**
* Returns true if this component is displayed when its parent
* container is displayed.
*/
public boolean isVisible() { return _visible; }
/** To be implemented by concrete subclasses.
* @param toolkit
*/
public abstract void draw(Toolkit toolkit);
/**
* A component is "displayed" if it is contained within a displayed Window.
* Even though it may be "displayed", it may be obscured by other
* Windows that are on top of it; and it may not be visible to the user
* because the <code>_visible</code> flag may be false.
*/
public boolean isDisplayed() {
/*
* Every component that has been added to a Container has a parent.
* The Window class overrides this method because it is never added to
* a Container.
*/
Container parent = getParent();
if (parent == null)
return false;
return parent.isDisplayed();
}
public Point getLocation() {
return new Point(_origin);
}
public void setLocation(Point origin_) {
_origin = new Point(origin_);
}
public void setLocation(int x_, int y_) {
_origin.x = x_;
_origin.y = y_;
}
/**
* Return the absolute coordinates of this component's origin.
* Note that Window (which is a subclass of Container)
* has a _parent value of null, but it overrides this method.
*/
public Point getLocationOnScreen() {
Container parent = getParent();
if (parent == null) {
throw new IllegalComponentStateException(
"cannot get component location " +
"before it has been added to a container");
}
return parent.getLocationOnScreen().addOffset(_origin);
}
public abstract Dimension getSize();
public abstract int getWidth();
public abstract int getHeight();
/**
* Get the bounding rectangle of this component, relative to
* the origin of its parent Container.
*/
public Rectangle getBounds() {
return new Rectangle(_origin, getSize());
}
/** Checks whether this component "contains" the specified point,
* where the point's x and y coordinates are defined to be relative
* to the top left corner of the parent Container.
*/
public boolean contains(Point p) {
return this.contains(p.x, p.y);
}
public boolean contains(int x, int y) {
return this.getBounds().contains(x, y);
}
public abstract Dimension minimumSize();
/**
* Set the parent container of this component. This is intended to
* be called by Container objects only.
* Note that we use a WeakReference so that the parent can be garbage-
* collected when there are no more strong references to it.
*/
public void setParent(Container container_) {
_parent = new WeakReference<Container>(container_);
// If this component's colors have not been set yet, inherit
// the parent container's colors.
if (getForeground() == null)
setForeground(container_.getForeground());
if (getBackground() == null)
setBackground(container_.getBackground());
}
/**
* Get the parent container of this component. Can return null if the
* component has no parent.
*/
protected Container getParent() {
if (_parent == null)
return null;
/* Note that _parent is a WeakReference.
*/
return (Container) _parent.get();
}
/**
* Register a KeyListener object for this component.
*/
public void addKeyListener(KeyListener kl_) {
if (_keyListeners == null)
_keyListeners = new Vector<KeyListener>();
_keyListeners.add(kl_);
}
/**
* Register a FocusListener object for this component.
*/
public void addFocusListener(FocusListener fl_) {
if (_focusListeners == null)
_focusListeners = new Vector<FocusListener>();
_focusListeners.add(fl_);
}
/**
* Process events that are implemented by all components.
* This can be overridden by subclasses, to handle custom events.
*/
protected void processEvent(AWTEvent evt_) {
if (evt_ instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) evt_;
/* Find the ancestor Window that contains the component that
* generated the keystroke.
* Then we call the processKeyEvent method
* of the ancestor Window, which calls the same method in its
* current-focus container, and so on, until the KeyEvent
* gets down to the component that generated the keystroke.
* This allows KeyEvents to be processed by outer enclosing
* containers, then by inner containers, and finally by the
* component that generated the KeyEvent.
*/
this.getAncestorWindow().processKeyEvent(ke);
}
else if (evt_ instanceof FocusEvent)
processFocusEvent((FocusEvent) evt_);
else if (evt_ instanceof MouseEvent) {
MouseEvent e = (MouseEvent) evt_;
// if (e.getModifiers() != MouseEvent.MOUSE_PRESSED)
// return;
processMouseEvent(e);
}
}
/** Invoke all the KeyListener callbacks that may have been registered
* for this component. The KeyListener objects may modify the
* keycodes, and can also set the "consumed" flag.
*/
public void processKeyEvent(KeyEvent ke_) {
if (_keyListeners != null) {
for (Enumeration<KeyListener> e = _keyListeners.elements();
e.hasMoreElements(); ) {
KeyListener kl = (KeyListener) e.nextElement();
if (ke_.getID() == AWTEvent.KEY_PRESSED)
kl.keyPressed(ke_);
else if (ke_.getID() == AWTEvent.KEY_TYPED)
kl.keyTyped(ke_);
if (ke_.isConsumed())
break;
}
}
}
/** Process a MouseEvent that was generated by clicking the mouse
* somewhere inside this component.
*/
public void processMouseEvent(MouseEvent e) {
// The default for a left-button-press is to request the focus;
// this is overridden by components such as buttons.
if (e.getButton() == MouseEvent.BUTTON1 &&
e.getModifiers() == MouseEvent.MOUSE_PRESSED &&
this.isFocusTraversable())
requestFocus();
}
/**
* Invoke all the FocusListener callbacks that may have been registered
* for this component.
*/
public void processFocusEvent(FocusEvent fe_) {
if (_focusListeners != null) {
for (Enumeration<FocusListener> e = _focusListeners.elements();
e.hasMoreElements(); ) {
FocusListener fl = (FocusListener) e.nextElement();
if (fe_.getID() == AWTEvent.FOCUS_GAINED)
fl.focusGained(fe_);
else
fl.focusLost(fe_);
}
}
}
/** Get the Window that contains this component.
*/
public Window getAncestorWindow() {
Container ancestor;
Container nextancestor;
if (this instanceof Window)
return (Window) this;
for (ancestor = getParent();
(ancestor instanceof Window) == false;
ancestor = nextancestor) {
if (ancestor == null)
return null;
if ((nextancestor = ancestor.getParent()) == null)
return null;
}
return (Window) ancestor;
}
/** This method should be invoked by all subclasses of Component
* which override this method; because this method generates the
* FOCUS_GAINED event when the component gains the keyboard focus.
*/
public void requestFocus() {
/* Generate the FOCUS_GAINED only if the component does not
* already have the focus.
*/
Window ancestor = getAncestorWindow();
Component currentFocus = ancestor.getCurrentFocus();
if ( currentFocus != this) {
EventQueue evtQueue =
Toolkit.getDefaultToolkit().getSystemEventQueue();
FocusEvent evt = new FocusEvent(AWTEvent.FOCUS_LOST, currentFocus);
evtQueue.postEvent(evt);
evt = new FocusEvent(AWTEvent.FOCUS_GAINED, this);
evtQueue.postEvent(evt);
// if (getParent() != null)
getParent().setFocus(this);
// requestSync();
repaint();
}
}
/**
* Returns true if this Component has the keyboard input focus.
*/
public boolean hasFocus()
{
// Modified 19-Feb-02 by rgittens to handle null ancestor.
Window ancestor = getAncestorWindow();
if (ancestor == null)
return false;
return (ancestor.getCurrentFocus() == this);
}
/**
* Indicates whether this component can be traversed using Tab or
* Shift-Tab keyboard focus traversal. If this
* method returns "false" it can still request focus using requestFocus(),
* but it will not automatically be assigned focus during keyboard focus
* traversal.
*/
public boolean isFocusTraversable()
{
return (_enabled && _visible);
}
/**
* Return true if this component is totally obscured by one or more
* windows that are stacked above it.
*/
public boolean isTotallyObscured() {
Rectangle bounds = getBounds();
Window ancestor = getAncestorWindow();
Vector<Window> windowList = Toolkit.getDefaultToolkit().getWindowList();
boolean obscured = false;
synchronized (windowList) {
/* Ignore windows that are stacked below this component's
* ancestor.
*/
int i;
for (i=0; i<windowList.size(); i++) {
Window w = (Window) windowList.elementAt(i);
if (w == ancestor)
break;
}
i++;
/* Return true if any of the overlying windows totally obscures
* this component.
*/
for ( ; i<windowList.size(); i++) {
Window w = (Window) windowList.elementAt(i);
Rectangle windowRect = w.getBounds();
if (bounds.equals(windowRect.intersection(bounds))) {
obscured = true;
break;
}
}
}
return obscured;
}
/** Returns the alignment along the X axis. This indicates how the
* component would like to be aligned relative to ther components.
* 0 indicates left-aligned, 0.5 indicates centered and 1 indicates
* right-aligned.
*/
public float getAlignmentX() { return _alignmentX; }
/** Returns the alignment along the Y axis. This indicates how the
* component would like to be aligned relative to ther components.
* 0 indicates top-aligned, 0.5 indicates centered and 1 indicates
* bottom-aligned.
*/
public float getAlignmentY() { return _alignmentY; }
/**
* Get the foreground color of this component. If it is null,
* the component will inherit the foreground color of its
* parent container.
*/
public Color getForeground() {
return _foreground;
}
/**
* Get the background color of this component. If it is null,
* the component will inherit the background color of its
* parent container.
*/
public Color getBackground() {
return _background;
}
/** Set the foreground color of this component.
*/
public void setForeground(Color color_) {
_foreground = color_;
validateCursesColor();
}
/** Set the background color of this component.
*/
public void setBackground(Color color_) {
_background = color_;
validateCursesColor();
}
/** Enable this component to react to user input. Components
* are enabled by default.
*/
public void setEnabled(boolean flag_) {
_enabled = flag_;
/* If this component is already displayed, generate a PaintEvent
* and post it onto the queue.
*/
this.repaint();
}
/**
* Determine whether this component can react to user input.
*/
public boolean isEnabled() { return _enabled; }
/**
* Marks the component and all parents above it as needing to be laid out
* again. This method is overridden by Container.
*/
public void invalidate() {
Container parent = getParent();
if (parent != null)
parent.invalidate();
}
/**
* Ensures that this component is laid out correctly.
* This method is primarily intended to be used on instances of
* Container. The default implementation does nothing; it is
* overridden by Container.
*/
public void validate() { }
/** Causes this component to be repainted as soon as possible
* (this is done by posting a RepaintEvent onto the system queue).
*/
public void repaint()
{
if (isDisplayed() == false)
return;
PaintEvent evt = new PaintEvent(this, getBounds());
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
queue.postEvent(evt);
}
/** Causes a SyncEvent to be posted onto the AWT queue, thus requesting
* a refresh of the physical screen.
*/
public void requestSync() {
SyncEvent evt = new SyncEvent(this);
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
queue.postEvent(evt);
}
/**
* Determines whether this component has a valid layout. A component
* is valid when it is correctly sized and positioned within its
* parent container and all its children (in the case of a Container)
* are also valid. The default implementation returns true; this method
* is overridden by Container.
*/
public boolean isValid() { return true; }
public abstract void debug(int level_);
/** Sets the name of the component.
*/
public void setName(String name_) {
_name = name_;
}
/** Returns the name of the component.
*/
public String getName() {
return _name;
}
/** Compute the component's ncurses color-pair from its foreground
* and background colors. If either color is null, it means that the
* component has not been added to a container yet, so don't do
* anything (the colors will be validated when the component is added
* to the container).
*/
public void validateCursesColor() {
if (_foreground == null || _background == null)
return;
_cursesColor = Color.getCursesColor(_foreground, _background);
}
public int getCursesColor() {
return _cursesColor;
}
//====================================================================
// PRIVATE METHODS
//====================================================================
// INSTANCE VARIABLES
/**
* The coordinates of the top-left corner of the component, relative to
* its parent container.
*/
protected Point _origin = new Point(0,0);
/**
* A WeakReference to the Container (e.g Window, Panel or Dialog)
* that contains us. The reason that we use a WeakReference is to
* allow the parent to be garbage-collected when there are no more
* strong references to it.
*/
protected WeakReference<Container> _parent = null;
/**
* This flag is true if this component can react to user input.
*/
protected boolean _enabled = true;
/**
* A flag that determines whether this component should be displayed
* (if its parent is displayed).
* This flag is set to true by default, except for Window which is
* initially invisible.
* @see #setVisible(boolean)
* @see #isVisible()
*/
protected boolean _visible = true;
/**
* A list of KeyListeners registered for this component.
*/
protected Vector<KeyListener> _keyListeners = null;
/**
* A list of FocusListeners registered for this component.
*/
protected Vector<FocusListener> _focusListeners = null;
/**
* the X-alignment of this component
*/
protected float _alignmentX = LEFT_ALIGNMENT;
/**
* the Y-alignment of this component
*/
protected float _alignmentY = TOP_ALIGNMENT;
/** The name of this component.
*/
private String _name = "";
/** If the foreground color is null, this component inherits the
* foreground color of its parent Container.
*/
protected Color _foreground = null;
/** If the background color is null, this component inherits the
* background color of its parent Container.
*/
protected Color _background = null;
/**
* The number of this component's color-pair, as computed by the
* ncurses COLOR_PAIR macro. This is set when the component
* is added to a container and whenever the colors are changed after
* that, so we don't have to re-determine it every time we draw the
* component.<p>
* A value of -1 indicates that the color-pair number needs to be
* recomputed.
*/
protected int _cursesColor = 0;
public static final float TOP_ALIGNMENT = (float) 0.0;
public static final float CENTER_ALIGNMENT = (float) 0.5;
public static final float BOTTOM_ALIGNMENT = (float) 1.0;
public static final float LEFT_ALIGNMENT = (float) 0.0;
public static final float RIGHT_ALIGNMENT = (float) 1.0;
}