gt:                 (_hcomponent.getWidth() - _tipwin.getWidth()) / 2;
context: //
  // $Id$
  //
  // 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.util.ArrayList;
  import java.util.Collections;
  import java.util.logging.Level;
  
  import org.lwjgl.opengl.GL11;
  
  import com.jme.intersection.CollisionResults;
  import com.jme.renderer.ColorRGBA;
  import com.jme.renderer.Renderer;
  import com.jme.scene.Geometry;
  import com.jme.scene.Spatial;
  import com.jme.system.DisplaySystem;
  import com.jmex.bui.event.BEvent;
  import com.jmex.bui.event.EventListener;
  import com.jmex.bui.event.FocusEvent;
  import com.jmex.bui.event.MouseEvent;
  import com.jmex.bui.layout.BorderLayout;
  import java.util.List;
  
  /**
   * Connects the BUI system into the JME scene graph.
   */
  public abstract class BRootNode extends Geometry
  {
      public BRootNode ()
      {
          super("BUI Root Node");
  
          // we need to render in the ortho queue
          setRenderQueueMode(Renderer.QUEUE_ORTHO);
      }
      
      private boolean enabled = true;
      public void setEnabled(boolean enabled) {this.enabled = enabled;}
      public boolean isEnabled() {return enabled;}
  
      /**
       * Returns the current timestamp used to stamp event times.
       */
      public abstract long getTickStamp ();
  
      /**
       * Registers a top-level window with the input system.
       */
      public void addWindow (BWindow window)
      {
          addWindow(window, false);
      }
  
      /**
       * Registers a top-level window with the input system.
       *
       * @param topLayer if true, will set the window layer to the top most layer if it's current
       * layer is less than that.
       */
      public void addWindow (BWindow window, boolean topLayer)
      {
          // make a note of the current top window
          BWindow curtop = null;
          if (_windows.size() > 0) {
              curtop = _windows.get(_windows.size()-1);
          }
  
          if (topLayer && curtop != null) {
              window.setLayer(Math.max(window.getLayer(), curtop.getLayer()));
          }
  
          // add this window into the stack and resort
          _windows.add(window);
          resortWindows();
  
          // if this window is now the top window, we need to transfer the focus
          // to it (and save the previous top window's focus)
          BComponent pendfocus = null;
          if (_windows.get(_windows.size()-1) == window && !window.isOverlay()) {
              // store the previous top window's focus and clear it
              if (_focus != null && curtop != null) {
                  curtop._savedFocus = _focus;
                  setFocus(null);
              }
              // make a note of the window's previous saved focus
              pendfocus = window._savedFocus;
              window._savedFocus = null;
          }
  
          // add this window to the hierarchy (which may set a new focus)
          window.setRootNode(this);
  
          // if no new focus was set when we added the window, give the focus to
          // the previously pending focus component
          if (_focus == null && pendfocus != null) {
              setFocus(pendfocus);
          }
  
          // recompute the hover component; the window may be under the mouse
          updateHoverComponent(_mouseX, _mouseY);
      }
  
      /**
       * Returns true if the specified window is on top.
       */
      public boolean isOnTop (BWindow window)
      {
          return (_windows.size() > 0 &&
                  _windows.get(_windows.size()-1) == window);
      }
  
      /**
       * Called when an added window's layer is changed. Adjusts the ordering of
       * the windows in the stack.
       */
      public void resortWindows ()
      {
          Collections.sort(_windows);
      }
  
      /**
       * Removes all windows from the root node.
       */
      public void removeAllWindows ()
      {
          setFocus(null);
          for (int ii = _windows.size() - 1; ii >= 0; ii--) {
              BWindow window = _windows.remove(ii);
              // remove the window from the interface heirarchy
              window.setRootNode(null);
          }
  
          // then remove the hover component (which may result in a mouse exited
          // even being dispatched to the window or one of its children)
          updateHoverComponent(_mouseX, _mouseY);
      }
  
      /**
       * Removes a window from participation in the input system.
       */
      public void removeWindow (BWindow window)
      {
          // if our focus is in this window, clear it
          if (_focus != null && _focus.getWindow() == window) {
              setFocus(null);
          }
  
          // clear any saved focus reference
          window._savedFocus = null;
  
          // first remove the window from our list
          if (!_windows.remove(window)) {
              Log.log.warning("Requested to remove unmanaged window " +
                              "[window=" + window + "].");
              Thread.dumpStack();
              return;
          }
  
          // then remove the hover component (which may result in a mouse exited
          // even being dispatched to the window or one of its children)
          updateHoverComponent(_mouseX, _mouseY);
  
          // remove the window from the interface heirarchy
          window.setRootNode(null);
  
          // remove any associated popup windows
          for (BWindow bwindow : _windows.toArray(new BWindow[_windows.size()])) {
              if (bwindow.getParentWindow() == window) {
                  removeWindow(bwindow);
              }
          }
  
          // finally restore the focus to the new top-most window if it has a
          // saved focus
          if (_windows.size() > 0) {
              BWindow top = _windows.get(_windows.size()-1);
              top.gotFocus();
          }
      }
  
      /**
       * Configures the number of seconds that the mouse must rest over a
       * component to trigger a tooltip. If the value is set to zero, tips will
       * appear immediately.
       */
      public void setTooltipTimeout (float seconds)
      {
          _tipTime = seconds;
      }
  
      /**
       * Returns the tool tip timeout. See {@link #setTooltipTimeout} for
       * details.
       */
      public float getTooltipTimeout ()
      {
          return _tipTime;
      }
  
      /**
       * Sets the preferred width of tooltip windows. The default is to prefer a
       * width slightly less wide that the entire window.
       */
      public void setTooltipPreferredWidth (int width)
      {
          _tipWidth = width;
      }
  
      /**
       * Registers a listener that will be notified of all events prior to their
       * being dispatched normally.
       */
      public void addGlobalEventListener (EventListener listener)
      {
          _globals.add(listener);
      }
  
      /**
       * Removes a global event listener registration.
       */
      public void removeGlobalEventListener (EventListener listener)
      {
          _globals.remove(listener);
      }
  
      /**
       * This is called by a window or a scroll pane when it has become invalid.
       * The root node should schedule a revalidation of this component on the
       * next tick or the next time an event is processed.
       */
      public abstract void rootInvalidated (BComponent root);
  
      /**
       * Configures a component to receive all events that are not sent to some
       * other component. When an event is not consumed during normal processing,
       * it is sent to the default event targets, most recently registered to
       * least recently registered.
       */
      public void pushDefaultEventTarget (BComponent component)
      {
          _defaults.add(component);
      }
  
      /**
       * Pops the default event target off the stack.
       */
      public void popDefaultEventTarget (BComponent component)
      {
          _defaults.remove(component);
      }
  
      /**
       * Requests that the specified component be given the input focus.
       * Pass null to clear the focus.
       */
      public void requestFocus (BComponent component)
      {
          setFocus(component);
      }
  
      /**
       * Returns the component that currently has the focus, or null.
       */
      public BComponent getFocus ()
      {
          return _focus;
      }
  
      /**
       * Generates a string representation of this instance.
       */
      public String toString ()
      {
          return "BRootNode@" + hashCode();
      }
  
      /**
       * A large component that changes its tooltip while it is the hover
       * component in the normal course of events can call this method to force
       * an update to the tooltip window.
       */
      public void tipTextChanged (BComponent component)
      {
          if (component == _hcomponent) {
              clearTipWindow();
          }
      }
  
      // documentation inherited
      public void updateGeometricState (float time, boolean initiator)
      {
          super.updateGeometricState(time, initiator);
  
          // update our geometry views if we have any
          for (int ii = 0, ll = _geomviews.size(); ii < ll; ii++) {
              _geomviews.get(ii).update(time);
          }
  
          // check to see if we need to pop up a tooltip
          _lastMoveTime += time;
          _lastTipTime += time;
          String tiptext;
          if (_hcomponent == null || _tipwin != null ||
              (_lastMoveTime < getTooltipTimeout() &&
               _lastTipTime > TIP_MODE_RESET) ||
              (tiptext = _hcomponent.getTooltipText()) == null) {
              if (_tipwin != null) {
                  _lastTipTime = 0;
              }
              return;
          }
  
          // make sure the hover component is in a window and wants a tooltip
          BWindow hwin = _hcomponent.getWindow();
          BComponent tcomp = _hcomponent.createTooltipComponent(tiptext);
          if (hwin == null || tcomp == null) {
              return;
          }
  
          // create, set up and show the tooltip window
          _tipwin = new BWindow(hwin.getStyleSheet(), new BorderLayout()) {
              public boolean isOverlay () {
                  return true; // don't steal input focus
              }
          };
          _tipwin.setLayer(Integer.MAX_VALUE/2);
          _tipwin.setStyleClass("tooltip_window");
          _tipwin.add(tcomp, BorderLayout.CENTER);
          addWindow(_tipwin);
          // it's possible that adding the tip window will cause it to immediately be removed, so
          // make sure we don't NPE
          if (_tipwin == null) {
              return;
          }
          int width = DisplaySystem.getDisplaySystem().getWidth();
          int height = DisplaySystem.getDisplaySystem().getHeight();
          _tipwin.pack(_tipWidth == -1 ? width-10 : _tipWidth, height-10);
          int tx = 0, ty = 0;
          if (_hcomponent.isTooltipRelativeToMouse()) {
              tx = _mouseX - _tipwin.getWidth()/2;
              ty = _mouseY + 10;
          } else {
              tx = _hcomponent.getAbsoluteX() +
  