com.jmex.bui.BComponent.java Source code

Java tutorial

Introduction

Here is the source code for com.jmex.bui.BComponent.java

Source

//
// $Id: BComponent.java,v 1.5 2007/05/04 17:36:17 vivaldi Exp $
//
// BUI - a user interface library for the JME 3D engine
// Copyright (C) 2005, Michael Bayne, All Rights Reserved
//
// 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 com.jmex.bui;

import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.lwjgl.opengl.GL11;

import com.jme.input.KeyInput;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.RenderContext;
import com.jme.renderer.Renderer;
import com.jme.system.DisplaySystem;
import com.jme.util.geom.BufferUtils;
import com.jmex.bui.background.BBackground;
import com.jmex.bui.border.BBorder;
import com.jmex.bui.event.BEvent;
import com.jmex.bui.event.ComponentListener;
import com.jmex.bui.event.KeyEvent;
import com.jmex.bui.event.MouseEvent;
import com.jmex.bui.text.HTMLView;
import com.jmex.bui.util.Dimension;
import com.jmex.bui.util.Insets;
import com.jmex.bui.util.Rectangle;

/**
 * The basic entity in the BUI user interface system. A hierarchy of components and component
 * derivations make up a user interface.
 */
public class BComponent {
    /**
     * The default component state. This is used to select the component's style pseudoclass among other things.
     */
    public static final int DEFAULT = 0;

    /**
     * A component state indicating that the mouse is hovering over the component. This is used to select the
     * component's style pseudoclass among other things.
     */
    public static final int HOVER = 1;

    /**
     * A component state indicating that the component is disabled. This is used to select the component's style
     * pseudoclass among other things.
     */
    public static final int DISABLED = 2;

    private boolean hoverEnabled = true;
    private String name;
    private boolean consumeMouseEvents = false;

    public BComponent() {
    }

    public BComponent(String _name) {
        name = _name;
    }

    public static void applyDefaultStates() {
        RenderContext<?> ctx = DisplaySystem.getDisplaySystem().getCurrentContext();
        for (int ii = 0; ii < Renderer.defaultStateList.length; ii++) {
            if (Renderer.defaultStateList[ii] != null && Renderer.defaultStateList[ii] != ctx.getCurrentState(ii)) {
                Renderer.defaultStateList[ii].apply();
            }
        }
        ctx.clearCurrentStates();
    }

    public boolean isConsumeMouseEvents() {
        return consumeMouseEvents;
    }

    public void setConsumeMouseEvents(final boolean _consumeMouseEvents) {
        consumeMouseEvents = _consumeMouseEvents;
    }

    /**
     * Configures this component with a custom stylesheet class. By default a component's class is
     * defined by its component type (label, button, checkbox, etc.) but one can provide custom
     * style information to a component by configuring it with a custom class and defining that
     * class in the applicable stylesheet.
     */
    public void setStyleClass(String styleClass) {
        if (isAdded()) {
            System.err.println("Warning: attempt to set style class after component was added to "
                    + "the interface heirarchy [comp=" + this + "].");
            Thread.dumpStack();
        }
        _styleClass = styleClass;
    }

    /**
     * Returns the Style class to be used for this component.
     */
    public String getStyleClass() {
        return (_styleClass == null) ? getDefaultStyleClass() : _styleClass;
    }

    /**
     * Informs this component of its parent in the interface heirarchy.
     */
    public void setParent(BContainer parent) {
        if (_parent != null && parent != null) {
            Log.log.warning("Already added child readded to interface hierarchy! [comp=" + this + ", oparent="
                    + _parent + ", nparent=" + parent + "].");
            Thread.dumpStack();
        } else if (_parent == null && parent == null) {
            Log.log.warning("Already removed child reremoved from interface hierarchy! " + "[comp=" + this + "].");
            Thread.dumpStack();
        }
        _parent = parent;
    }

    /**
     * Returns the parent of this component in the interface hierarchy.
     */
    public BContainer getParent() {
        return _parent;
    }

    /**
     * Returns the preferred size of this component, supplying a width and or height hint to the
     * component to inform it of restrictions in one of the two dimensions. Not all components will
     * make use of the hints, but layout managers should provide them if they know the component
     * will be forced to a particular width or height regardless of what it prefers.
     */
    public Dimension getPreferredSize(int whint, int hhint) {
        Dimension ps;
        // if we have a fully specified preferred size, just use it
        if (_preferredSize != null && _preferredSize.width != -1 && _preferredSize.height != -1) {
            ps = new Dimension(_preferredSize);
        } else {
            // override hints with preferred size
            if (_preferredSize != null) {
                if (_preferredSize.width > 0) {
                    whint = _preferredSize.width;
                }
                if (_preferredSize.height > 0) {
                    hhint = _preferredSize.height;
                }
            }

            // extract space from the hints for our insets
            Insets insets = getInsets();
            if (whint > 0) {
                whint -= insets.getHorizontal();
            }
            if (hhint > 0) {
                hhint -= insets.getVertical();
            }

            // compute our "natural" preferred size
            ps = computePreferredSize(whint, hhint);

            // now add our insets back in
            ps.width += insets.getHorizontal();
            ps.height += insets.getVertical();

            // then override it with user supplied values
            if (_preferredSize != null) {
                if (_preferredSize.width != -1) {
                    ps.width = _preferredSize.width;
                }
                if (_preferredSize.height != -1) {
                    ps.height = _preferredSize.height;
                }
            }
        }

        // now make sure we're not smaller in either dimension than our
        // background will allow
        BBackground background = getBackground();
        if (background != null) {
            ps.width = Math.max(ps.width, background.getMinimumWidth());
            ps.height = Math.max(ps.height, background.getMinimumHeight());
        }

        return ps;
    }

    /**
     * Configures the preferred size of this component. This will override any information provided
     * by derived classes that have opinions about their preferred size. Either the width or the
     * height can be configured as -1 in which case the computed preferred size will be used for
     * that dimension.
     */
    public void setPreferredSize(Dimension preferredSize) {
        _preferredSize = preferredSize;
    }

    /**
     * Configures the preferred size of this component. See {@link #setPreferredSize(Dimension)}.
     */
    public void setPreferredSize(int width, int height) {
        setPreferredSize(new Dimension(width, height));
    }

    /**
     * Returns the x coordinate of this component.
     */
    public int getX() {
        return _x;
    }

    /**
     * Returns the y coordinate of this component.
     */
    public int getY() {
        return _y;
    }

    /**
     * Returns the width of this component.
     */
    public int getWidth() {
        return _width;
    }

    /**
     * Returns the height of this component.
     */
    public int getHeight() {
        return _height;
    }

    /**
     * Returns the x position of this component in absolute screen coordinates.
     */
    public int getAbsoluteX() {
        return _x + ((_parent == null) ? 0 : _parent.getAbsoluteX());
    }

    /**
     * Returns the y position of this component in absolute screen coordinates.
     */
    public int getAbsoluteY() {
        return _y + ((_parent == null) ? 0 : _parent.getAbsoluteY());
    }

    /**
     * Returns the bounds of this component in a new rectangle.
     */
    public Rectangle getBounds() {
        return new Rectangle(_x, _y, _width, _height);
    }

    /**
     * Returns the insets configured on this component. <code>null</code> will never be returned,
     * an {@link Insets} instance with all fields set to zero will be returned instead.
     */
    public Insets getInsets() {
        Insets insets = _insets[getState()];
        return (insets == null) ? Insets.ZERO_INSETS : insets;
    }

    /**
     * Returns the (foreground) color configured for this component.
     */
    public ColorRGBA getColor() {
        ColorRGBA color = _colors[getState()];
        return (color != null) ? color : _colors[DEFAULT];
    }

    /**
     * Returns our bounds as a nicely formatted string.
     */
    public String boundsToString() {
        return _width + "x" + _height + "+" + _x + "+" + _y;
    }

    /**
     * Returns the currently active border for this component.
     */
    public BBorder getBorder() {
        BBorder border = _borders[getState()];
        return (border != null) ? border : _borders[DEFAULT];
    }

    /**
     * Returns a reference to the background used by this component.
     */
    public BBackground getBackground() {
        BBackground background = _backgrounds[getState()];
        return (background != null) ? background : _backgrounds[DEFAULT];
    }

    /**
     * Configures the background for this component for the specified state.
     */
    public void setBackground(int state, BBackground background) {
        if (isAdded()) {
            if (_backgrounds[state] != null) {
                _backgrounds[state].wasRemoved();
            }
            if (background != null) {
                background.wasAdded();
            }
        }
        _backgrounds[state] = background;
    }

    /**
     * Returns a reference to the cursor used by this component.
     */
    public BCursor getCursor() {
        return _cursor;
    }

    /**
     * Configures the cursor for this component.  This must only be called after the component has
     * been added to the interface hierarchy or the value will be overridden by the stylesheet
     * associated with this component.
     */
    public void setCursor(BCursor cursor) {
        _cursor = cursor;
    }

    /**
     * Sets the alpha level for this component.
     */
    public void setAlpha(float alpha) {
        _alpha = alpha;
    }

    /**
     * Returns the alpha transparency of this component.
     */
    public float getAlpha() {
        return _alpha;
    }

    /**
     * Sets this components enabled state. A component that is not enabled should not respond to
     * user interaction and should render itself in such a way as not to afford user interaction.
     */
    public void setEnabled(boolean enabled) {
        if (enabled != _enabled) {
            _enabled = enabled;
            stateDidChange();
        }
    }

    /**
     * Returns true if this component is enabled and responding to user interaction, false if not.
     */
    public boolean isEnabled() {
        return _enabled;
    }

    /**
     * Sets this component's visibility state.  A component that is invisible is not rendered and
     * does not contribute to the layout.
     */
    public void setVisible(boolean visible) {
        if (visible != _visible) {
            _visible = visible;
            invalidate();
        }
    }

    /**
     * Returns true if this component is visible, false if it is not.
     */
    public boolean isVisible() {
        return _visible;
    }

    /**
     * Returns true if this component is both added to the interface hierarchy and visible, false
     * if not.
     */
    public boolean isShowing() {
        return isAdded() && isVisible();
    }

    /**
     * Returns the state of this component, either {@link #DEFAULT} or {@link #DISABLED}.
     */
    public int getState() {
        return _enabled ? (_hover ? HOVER : DEFAULT) : DISABLED;
    }

    /**
     * Sets a user defined property on this component. User defined properties allow the
     * association of arbitrary additional data with a component for application specific purposes.
     */
    public void setProperty(String key, Object value) {
        if (_properties == null) {
            _properties = new HashMap<String, Object>();
        }
        _properties.put(key, value);
    }

    /**
     * Returns the user defined property mapped to the specified key, or null.
     */
    public Object getProperty(String key) {
        return (_properties == null) ? null : _properties.get(key);
    }

    /**
     * Returns whether or not this component accepts the keyboard focus.
     */
    public boolean acceptsFocus() {
        return false;
    }

    /**
     * Returns true if this component has the focus.
     */
    public boolean hasFocus() {
        return isAdded() ? getWindow().getRootNode().getFocus() == this : false;
    }

    /**
     * Returns the component that should receive focus if this component is clicked. If this
     * component does not accept focus, its parent will be checked and so on.
     */
    public BComponent getFocusTarget() {
        if (acceptsFocus()) {
            return this;
        } else if (_parent != null) {
            return _parent.getFocusTarget();
        } else {
            return null;
        }
    }

    /**
     * Requests that this component be given the input focus.
     */
    public void requestFocus() {
        // sanity check
        if (!acceptsFocus()) {
            Log.log.warning("Unfocusable component requested focus: " + this);
            Thread.dumpStack();
            return;
        }

        BWindow window = getWindow();
        if (window == null) {
            Log.log.warning("Focus requested for un-added component: " + this);
            Thread.dumpStack();
        } else {
            window.requestFocus(this);
        }
    }

    /**
     * Sets the upper left position of this component in absolute screen coordinates.
     */
    public void setLocation(int x, int y) {
        setBounds(x, y, _width, _height);
    }

    public int[] getLocation() {
        return new int[] { _x, _y };
    }

    /**
     * Sets the width and height of this component in screen coordinates.
     */
    public void setSize(int width, int height) {
        setBounds(_x, _y, width, height);
    }

    /**
     * Sets the bounds of this component in screen coordinates.
     *
     * @see #setLocation
     * @see #setSize
     */
    public void setBounds(int x, int y, int width, int height) {
        if (_x != x || _y != y) {
            _x = x;
            _y = y;
        }
        if (_width != width || _height != height) {
            _width = width;
            _height = height;
            invalidate();
        }
    }

    /**
     * Adds a listener to this component. The listener will be notified when events of the appropriate type are
     * dispatched on this component.
     */
    public void addListener(ComponentListener listener) {
        if (_listeners == null) {
            _listeners = new ArrayList<ComponentListener>();
        }
        _listeners.add(listener);
    }

    /**
     * Removes a listener from this component. Returns true if the listener was in fact in the listener list for this
     * component, false if not.
     */
    public boolean removeListener(ComponentListener listener) {
        return _listeners != null && _listeners.remove(listener);
    }

    public void addListeners(List<ComponentListener> listener) {
        if (listener != null && listener.size() > 0) {
            _listeners.addAll(listener);
        }
    }

    public boolean removeAllListeners() {
        if (_listeners != null) {
            _listeners.clear();
            return true;
        }
        return false;
    }

    /**
     * Configures the tooltip text for this component. If the text starts with &lt;html&gt; then
     * the tooltip will be displayed with an @{link HTMLView} otherwise it will be displayed with a
     * {@link BLabel}.
     */
    public void setTooltipText(String text) {
        _tiptext = text;
    }

    /**
     * Returns the tooltip text configured for this component.
     */
    public String getTooltipText() {
        return _tiptext;
    }

    /**
     * Sets where to position the tooltip window.
     *
     * @param mouse if true, the window will appear relative to the mouse position, if false, the
     *              window will appear relative to the component bounds.
     */
    public void setTooltipRelativeToMouse(boolean mouse) {
        _tipmouse = mouse;
    }

    /**
     * Returns true if the tooltip window should be position relative to the mouse.
     */
    public boolean isTooltipRelativeToMouse() {
        return _tipmouse;
    }

    /**
     * Returns true if this component is added to a hierarchy of components that culminates in a
     * top-level window.
     */
    public boolean isAdded() {
        BWindow win = getWindow();
        return (win != null && win.isAdded());
    }

    /**
     * Returns true if this component has been validated and laid out.
     */
    public boolean isValid() {
        return _valid;
    }

    /**
     * Instructs this component to lay itself out and then mark itself as valid.
     */
    public void validate() {
        if (!_valid) {
            if (isVisible()) {
                layout();
            }
            _valid = true;
        }
    }

    /**
     * Marks this component as invalid and needing a relayout. If the component is valid, its
     * parent will also be marked as invalid.
     */
    public void invalidate() {
        if (_valid) {
            _valid = false;
            if (_parent != null) {
                _parent.invalidate();
            }
        }
    }

    /**
     * Translates into the component's coordinate space, renders the background and border and then
     * calls {@link #renderComponent} to allow the component to render itself.
     */
    public void render(Renderer renderer) {
        if (!_visible) {
            return;
        }
        GL11.glTranslatef(_x, _y, 0);

        try {
            // render our background
            renderBackground(renderer);

            // render any custom component bits
            renderComponent(renderer);

            // render our border
            renderBorder(renderer);
        } finally {
            GL11.glTranslatef(-_x, -_y, 0);
        }
    }

    /**
     * Returns the component "hit" by the specified mouse coordinates which might be this component
     * or any of its children. This method should return null if the supplied mouse coordinates are
     * outside the bounds of this component.
     */
    public BComponent getHitComponent(int mx, int my) {
        if (isVisible() && (mx >= _x) && (my >= _y) && (mx < _x + _width) && (my < _y + _height)) {
            return this;
        }
        return null;
    }

    //  // documentation inherited
    //  @Override
    //  public BComponent getHitComponent(int mx, int my) {
    //    BComponent component = super.getHitComponent(mx, my);
    //    if (component == this) {
    //      return null;
    //    }
    //    return component;
    //  }

    /**
     * Instructs this component to process the supplied event. If the event is not processed, it
     * will be passed up to its parent component for processing. Derived classes should thus only
     * call <code>super.dispatchEvent</code> for events that they did not "consume".
     *
     * @return true if this event was consumed, false if not.
     */
    public boolean dispatchEvent(BEvent event) {
        // events that should not be propagated up the hierarchy are marked as processed
        // immediately to avoid sending them to our parent or to other windows
        boolean processed = !event.propagateUpHierarchy();

        // handle focus traversal
        if (event instanceof KeyEvent) {
            KeyEvent kev = (KeyEvent) event;
            if (kev.getType() == KeyEvent.KEY_PRESSED) {
                int modifiers = kev.getModifiers(), keyCode = kev.getKeyCode();
                if (keyCode == KeyInput.KEY_TAB) {
                    if (modifiers == 0) {
                        getWindow().requestFocus(getNextFocus());
                        processed = true;
                    } else if (modifiers == KeyEvent.SHIFT_DOWN_MASK) {
                        getWindow().requestFocus(getPreviousFocus());
                        processed = true;
                    }
                }
            }
        }

        // handle mouse hover detection
        if (_enabled && event instanceof MouseEvent) {
            int ostate = getState();
            MouseEvent mev = (MouseEvent) event;
            if (consumeMouseEvents) {
                switch (mev.getType()) {
                case MouseEvent.MOUSE_MOVED:
                    return true;
                case MouseEvent.MOUSE_WHEELED:
                    return true;
                }
                processed = false;
            }

            if (hoverEnabled) {
                switch (mev.getType()) {
                case MouseEvent.MOUSE_ENTERED:
                    _hover = true;
                    processed = true;
                    break;
                case MouseEvent.MOUSE_EXITED:
                    _hover = false;
                    processed = true;
                    break;
                }
            }

            // update our component state if necessary
            if (getState() != ostate) {
                stateDidChange();
            }

            if (processed && changeCursor()) {
                updateCursor(_cursor);
            }
        }

        // dispatch this event to our listeners
        if (_listeners != null) {
            for (int ii = 0, ll = _listeners.size(); ii < ll; ii++) {
                event.dispatch(_listeners.get(ii));
            }
        }

        // if we didn't process the event, pass it up to our parent
        if (!processed && _parent != null) {
            return getParent().dispatchEvent(event);
        }

        return processed;
    }

    /**
     * Instructs this component to lay itself out. This is called as a result of the component
     * changing size.
     */
    protected void layout() {
        // we have nothing to do by default
    }

    /**
     * Computes and returns a preferred size for this component. This method is called if no
     * overriding preferred size has been supplied.
     *
     * @return the computed preferred size of this component <em>in a newly created Dimension</em>
     *         instance which will be adopted (and modified) by the caller.
     */
    protected Dimension computePreferredSize(int whint, int hhint) {
        return new Dimension(0, 0);
    }

    /**
     * This method is called when we are added to a hierarchy that is connected to a top-level
     * window (at which point we can rely on having a look and feel and can set ourselves up).
     */
    protected void wasAdded() {
        configureStyle(getWindow().getStyleSheet());

        // let our backgrounds and borders know we're added
        for (int ii = 0; ii < _backgrounds.length; ii++) {
            if (_backgrounds[ii] != null) {
                _backgrounds[ii].wasAdded();
            }
        }
        for (int ii = 0; ii < _borders.length; ii++) {
            if (_borders[ii] != null) {
                _borders[ii].wasAdded();
            }
        }
    }

    /**
     * Instructs this component to fetch its style configuration from the supplied style
     * sheet. This method is called when a component is added to the interface hierarchy.
     */
    protected void configureStyle(BStyleSheet style) {
        if (_preferredSize == null) {
            _preferredSize = style.getSize(this, null);
        }

        _cursor = style.getCursor(this, null);
        _tipStyle = style.getTooltipStyle(this, null);
        for (int ii = 0; ii < getStateCount(); ii++) {
            _colors[ii] = style.getColor(this, getStatePseudoClass(ii));
            _insets[ii] = style.getInsets(this, getStatePseudoClass(ii));
            _borders[ii] = style.getBorder(this, getStatePseudoClass(ii));
            if (_borders[ii] != null) {
                _insets[ii] = _borders[ii].adjustInsets(_insets[ii]);
            }
            if (_backgrounds[ii] == null) {
                _backgrounds[ii] = style.getBackground(this, getStatePseudoClass(ii));
            }
        }
    }

    /**
     * This method is called when we are removed from a hierarchy that is connected to a top-level
     * window. If we wish to clean up after things done in {@link #wasAdded}, this is a fine place
     * to do so.
     */
    protected void wasRemoved() {
        // mark ourselves as invalid so that if this component is again added to an interface
        // heirarchy it will revalidate at that time
        _valid = false;

        // let our backgrounds and borders know we're removed
        for (int ii = 0; ii < _backgrounds.length; ii++) {
            if (_backgrounds[ii] != null) {
                _backgrounds[ii].wasRemoved();
            }
        }
        for (int ii = 0; ii < _borders.length; ii++) {
            if (_borders[ii] != null) {
                _borders[ii].wasRemoved();
            }
        }
    }

    /**
     * Creates the component that will be used to display our tooltip. This method will only be
     * called if {@link #getTooltipText} returns non-null text.
     */
    protected BComponent createTooltipComponent(String tiptext) {
        if (tiptext.startsWith("<html>")) {
            return new HTMLView("", tiptext);
        } else {
            return new BLabel(tiptext, _tipStyle);
        }
    }

    /**
     * Renders the background for this component.
     */
    protected void renderBackground(Renderer renderer) {
        BBackground background = getBackground();
        if (background != null) {
            background.render(renderer, 0, 0, _width, _height, _alpha);
        }
    }

    /**
     * Renders the border for this component.
     */
    protected void renderBorder(Renderer renderer) {
        BBorder border = getBorder();
        if (border != null) {
            border.render(renderer, 0, 0, _width, _height, _alpha);
        }
    }

    /**
     * Renders any custom bits for this component. This is called with the graphics context
     * translated to (0, 0) relative to this component.
     */
    protected void renderComponent(Renderer renderer) {
    }

    /**
     * Returns the default stylesheet class to be used for all instances of this component. Derived
     * classes will likely want to override this method and set up a default class for their type
     * of component.
     */
    protected String getDefaultStyleClass() {
        return "component";
    }

    /**
     * Returns the number of different states that this component can take.  These states
     * correspond to stylesheet pseudoclasses that allow components to customize their
     * configuration based on whether they are enabled or disabled, or pressed if they are a
     * button, etc.
     */
    protected int getStateCount() {
        return STATE_COUNT;
    }

    /**
     * Returns the pseudoclass identifier for the specified component state.  This string will be
     * the way that the state is identified in the associated stylesheet. For example, the {@link
     * #DISABLED} state maps to <code>disabled</code> and is configured like so:
     * <p/>
     * <pre>
     * component:disabled {
     *    color: #CCCCCC; // etc.
     * }
     * </pre>
     */
    protected String getStatePseudoClass(int state) {
        return STATE_PCLASSES[state];
    }

    /**
     * Called when the component's state has changed.
     */
    protected void stateDidChange() {
        invalidate();
    }

    /**
     * Returns true if the component should update the mouse cursor.
     */
    protected boolean changeCursor() {
        return _enabled && _visible && _hover && hoverEnabled;
    }

    /**
     * Updates the mouse cursor with the supplied cursor.
     */
    protected void updateCursor(BCursor cursor) {
        if (cursor != null) {
            cursor.show();
        }
    }

    /**
     * Returns the window that defines the root of our component hierarchy.
     */
    public BWindow getWindow() {
        if (this instanceof BWindow) {
            return (BWindow) this;
        } else if (_parent != null) {
            return _parent.getWindow();
        } else {
            return null;
        }
    }

    /**
     * Searches for the next component that should receive the keyboard focus. If such a component
     * can be found, it will be returned. If no other focusable component can be found and this
     * component is focusable, this component will be returned. Otherwise, null will be returned.
     */
    protected BComponent getNextFocus() {
        if (_parent != null && _parent instanceof BContainer) {
            return _parent.getNextFocus(this);
        } else if (acceptsFocus()) {
            return this;
        } else {
            return null;
        }
    }

    /**
     * Searches for the previous component that should receive the keyboard focus. If such a
     * component can be found, it will be returned. If no other focusable component can be found
     * and this component is focusable, this component will be returned. Otherwise, null will be
     * returned.
     */
    protected BComponent getPreviousFocus() {
        if (_parent != null && _parent instanceof BContainer) {
            return _parent.getPreviousFocus(this);
        } else if (acceptsFocus()) {
            return this;
        } else {
            return null;
        }
    }

    /**
     * Dispatches an event emitted by this component. The event is given to the root node for
     * processing though in general it will result in an immediate call to {@link #dispatchEvent}
     * with the event.
     *
     * @return true if the event was emitted, false if it was dropped because we are not currently
     *         added to the interface hierarchy.
     */
    protected boolean emitEvent(BEvent event) {
        BWindow window;
        BRootNode node;
        if ((window = getWindow()) == null || (node = window.getRootNode()) == null) {
            return false;
        }
        node.dispatchEvent(this, event);
        return true;
    }

    /**
     * Activates scissoring and sets the scissor region to the intersection of the current region
     * (if any) and the specified rectangle.  After rendering the scissored region, call
     * {@link #restoreScissorState} to restore the previous state.
     *
     * @param store a rectangle to hold the previous scissor region for later restoration
     * @return <code>true</code> if scissoring was already enabled, false if it was not.
     */
    protected static boolean intersectScissorBox(Rectangle store, int x, int y, int width, int height) {
        boolean enabled = GL11.glIsEnabled(GL11.GL_SCISSOR_TEST);
        if (enabled) {
            GL11.glGetInteger(GL11.GL_SCISSOR_BOX, _bbuf);
            store.set(_bbuf.get(0), _bbuf.get(1), _bbuf.get(2), _bbuf.get(3));
            int x1 = Math.max(x, store.x), y1 = Math.max(y, store.y),
                    x2 = Math.min(x + width, store.x + store.width),
                    y2 = Math.min(y + height, store.y + store.height);
            GL11.glScissor(x, y, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
        } else {
            GL11.glEnable(GL11.GL_SCISSOR_TEST);
            GL11.glScissor(x, y, width, height);
        }
        return enabled;
    }

    /**
     * Restores the previous scissor state after a call to {@link #intersectScissorBox}.
     *
     * @param enabled the value returned by {@link #intersectScissorBox}, indicating whether or not
     *                scissoring was enabled
     * @param rect    the scissor box to restore
     */
    protected static void restoreScissorState(boolean enabled, Rectangle rect) {
        if (enabled) {
            GL11.glScissor(rect.x, rect.y, rect.width, rect.height);
        } else {
            GL11.glDisable(GL11.GL_SCISSOR_TEST);
        }
    }

    public String getName() {
        return name;
    }

    public void setName(final String _name) {
        name = _name;
    }

    public boolean isHoverEnabled() {
        return hoverEnabled;
    }

    public void setHoverEnabled(boolean hoverEnabled) {
        this.hoverEnabled = hoverEnabled;
    }

    protected BContainer _parent;
    protected String _styleClass;
    protected Dimension _preferredSize;
    protected int _x, _y, _width, _height;
    protected List<ComponentListener> _listeners;
    protected HashMap<String, Object> _properties;
    protected String _tiptext;
    protected String _tipStyle;
    protected boolean _tipmouse;

    protected boolean _valid, _enabled = true, _visible = true, _hover;
    protected float _alpha = 1f;

    protected ColorRGBA[] _colors = new ColorRGBA[getStateCount()];
    protected Insets[] _insets = new Insets[getStateCount()];
    protected BBorder[] _borders = new BBorder[getStateCount()];
    protected BBackground[] _backgrounds = new BBackground[getStateCount()];
    protected BCursor _cursor;

    /**
     * Temporary storage for scissor box queries.
     */
    protected static IntBuffer _bbuf = BufferUtils.createIntBuffer(16);

    protected static final int STATE_COUNT = 3;
    protected static final String[] STATE_PCLASSES = { null, "hover", "disabled" };
}