/**************************************************************************
* Copyright (c) 1996 Jim Crossley
*
* Although it's unlikely that anyone would actually want it,
* permission is hereby granted, without written agreement and
* without license or royalty fees, to use, copy, modify, and
* distribute this software and its documentation for any purpose,
* provided that the above copyright notice and the following three
* paragraphs appear in all copies of this software, its documentation
* and any derivative work.
*
* In no event shall Jim Crossley or Automated Design Systems, Inc. be
* liable to any party for direct, indirect, special, incidental, or
* consequential damages arising out of the use of this software and
* its documentation.
*
* The software provided hereunder is on an "as is" basis, and neither
* Jim Crossley nor Automated Design Systems, Inc. has any obligation
* to provide maintenance, support, updates, enhancements, or modifications.
*
* Derived or altered versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
***************************************************************************/
package utilities;

import java.awt.Container;
import java.awt.Component;
import java.awt.Event;

/**
 * This class is a basic extension of the Panel class.  It enables "tab key"
 * functionality (forward and reverse) for the controls contained within it.
 * It can be used by an applet or application.
 *
 * @author Jim Crossley
 * @version 1.0, 9/17/1996
 */
public class TabKeyPanel extends java.awt.Panel {
    private static final int TAB_KEY = 9;

    // The following are initialized in the keyDown() if the Tab key is pressed.
    private boolean reverseTabOrder;
    private boolean activeFound;
    private boolean focusSet;
    private Component activeControl;

    /**
     * Public constructor that merely calls its parent constructor.
     */
    public TabKeyPanel() {
        super();
    }

    /**
     * This method monitors keyboard input to determine when the Tab key is pressed.
     * If there is a TabKeyPanel higher up in the ownership chain, we
     * pass the processing to it.  Actually, we pass it to 'super', so if there
     * are any non-tab-sensitive containers in between, they must each continue to
     * pass the event on up the chain.  If they don't, they essentially override
     * the tab-key processing of their tab-sensitive parent.
     */
    public boolean keyDown(Event evt, int key) {
        if (key == TAB_KEY) {
            // Ensure that this is the top-most TabKeyPanel
            for (Container c = getParent(); c != null; c = c.getParent()) {
                if (c instanceof TabKeyPanel) {
                    return super.keyDown(evt, key);
                }
            }
            // Set member variables
            reverseTabOrder = (evt.modifiers == Event.SHIFT_MASK);
            activeFound = focusSet = false;
            activeControl = (Component)evt.target;

            // Move focus to the next control
            moveNextCtrl(this, true);
            return true;
        }
        return super.keyDown(evt, key);
    }

    /**
     * Sets the focus to 'nextControl' and posts a LOST_FOCUS event to the control
     * that had the focus (class member 'activeControl').
     * This function will not set the focus if the 'focusSet' flag has been set
     * (it is initialized to false in the keyDown() method).
     * @param nextControl The component to receive focus:  labels and canvases cannot get focus.
     */
    private void setFocus(Component nextControl) {
        if (!focusSet && !(nextControl instanceof java.awt.Label) && !(nextControl instanceof java.awt.Canvas) && nextControl.isVisible() && nextControl.isEnabled()) {
            nextControl.requestFocus();
            Event lostFocus = new Event (activeControl, Event.LOST_FOCUS, null);
            activeControl.postEvent(lostFocus);
            focusSet = true;
        }
    }

    /**
     * The main loop that first finds the current active control, and then finds the
     * next control (in the creation order) that may obtain focus.
     *
     * The function is called recursively for those components that may also
     * be containers.
     *
     * The twoPass option should only be used for the top-most TabKeyPanel.
     * It signifies that if the last control (in the creation order) currently has
     * focus, focus should go back to the first control in the container.
     *
     * @param box The container in which to search for the active control, as well as the next control to which focus should be transferred.
     * @param twoPass True if two passes through the box's components should be made in order to find the next control.
     */
    private void moveNextCtrl(Container box, boolean twoPass) {
        int loopInit;
        int loopTest;
        int loopStep;

        // set loop parameters according to whether the SHIFT_KEY was pressed.
        if (reverseTabOrder) {
            loopInit = box.countComponents() - 1;
            loopTest = -1;  // zero-based array
            loopStep = -1;
        }
        else { // standard tab order
            loopInit = 0;
            loopTest = box.countComponents();
            loopStep = 1;
        }

        // loop through all of the components in the panel
        for (int i = loopInit; i != loopTest; i += loopStep) {
            Component ctrl = box.getComponent(i);
            if (ctrl instanceof Container) {
                moveNextCtrl((Container)ctrl, false);
            }
            else if (activeFound) {
                setFocus(ctrl);
            }
            else if (ctrl.equals(activeControl)) {
                activeFound = true;
            }

            if (focusSet) break;
        }

        // if the active component is last in the tab order, loop again
        if (twoPass && activeFound && !focusSet) {
            for (int i = loopInit; i != loopTest; i += loopStep) {
                Component ctrl = box.getComponent(i);
                if (ctrl instanceof Container)
                    moveNextCtrl((Container)ctrl, false);
                else
                    setFocus(ctrl);

                if (focusSet) break;
            }
        }
    }
}

