#ifdef REV_INFO
#ifndef lint
static char SCCSID[] = "OSF/Motif: %W% %E%";
#endif /* lint */
#endif /* REV_INFO */
/******************************************************************************
*******************************************************************************
*
*  (c) Copyright 1989, OPEN SOFTWARE FOUNDATION, INC.
*  (c) Copyright 1987, 1988, 1989 HEWLETT-PACKARD COMPANY
*  ALL RIGHTS RESERVED
*  
*  	THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED
*  AND COPIED ONLY IN ACCORDANCE WITH THE TERMS OF SUCH LICENSE AND
*  WITH THE INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE OR
*  ANY OTHER COPIES THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE
*  AVAILABLE TO ANY OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF THE
*  SOFTWARE IS HEREBY TRANSFERRED.
*  
*  	THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT
*  NOTICE AND SHOULD NOT BE CONSTRUED AS A COMMITMENT BY OPEN SOFTWARE
*  FOUNDATION, INC. OR ITS THIRD PARTY SUPPLIERS  
*  
*  	OPEN SOFTWARE FOUNDATION, INC. AND ITS THIRD PARTY SUPPLIERS,
*  ASSUME NO RESPONSIBILITY FOR THE USE OR INABILITY TO USE ANY OF ITS
*  SOFTWARE .   OSF SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
*  KIND, AND OSF EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES, INCLUDING
*  BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
*  FITNESS FOR A PARTICULAR PURPOSE.
*  
*  Notice:  Notwithstanding any other lease or license that may pertain to,
*  or accompany the delivery of, this computer software, the rights of the
*  Government regarding its use, reproduction and disclosure are as set
*  forth in Section 52.227-19 of the FARS Computer Software-Restricted
*  Rights clause.
*  
*  (c) Copyright 1989, Open Software Foundation, Inc.  Unpublished - all
*  rights reserved under the Copyright laws of the United States.
*  
*  RESTRICTED RIGHTS NOTICE:  Use, duplication, or disclosure by the
*  Government is subject to the restrictions as set forth in subparagraph
*  (c)(1)(ii) of the Rights in Technical Data and Computer Software clause
*  at DFARS 52.227-7013.
*  
*  Open Software Foundation, Inc.
*  11 Cambridge Center
*  Cambridge, MA   02142
*  (617)621-8700
*  
*  RESTRICTED RIGHTS LEGEND:  This computer software is submitted with
*  "restricted rights."  Use, duplication or disclosure is subject to the
*  restrictions as set forth in NASA FAR SUP 18-52.227-79 (April 1985)
*  "Commercial Computer Software- Restricted Rights (April 1985)."  Open
*  Software Foundation, Inc., 11 Cambridge Center, Cambridge, MA  02142.  If
*  the contract contains the Clause at 18-52.227-74 "Rights in Data General"
*  then the "Alternate III" clause applies.
*  
*  (c) Copyright 1989, Open Software Foundation, Inc.
*  ALL RIGHTS RESERVED 
*  
*  
* Open Software Foundation is a trademark of The Open Software Foundation, Inc.
* OSF is a trademark of Open Software Foundation, Inc.
* OSF/Motif is a trademark of Open Software Foundation, Inc.
* Motif is a trademark of Open Software Foundation, Inc.
* DEC is a registered trademark of Digital Equipment Corporation
* DIGITAL is a registered trademark of Digital Equipment Corporation
* X Window System is a trademark of the Massachusetts Institute of Technology
*
*******************************************************************************
******************************************************************************/
#include <X11/IntrinsicP.h>
#include <X11/Intrinsic.h>
#include <X11/Xutil.h>
#include <X11/Shell.h>
#include <X11/ShellP.h>
#include <X11/Vendor.h>
#include <X11/VendorP.h>
#include <Xm/MenuShell.h>
#include <Xm/MenuShellP.h>
#include <Xm/Xm.h>
#include <Xm/XmP.h>
#include <Xm/RowColumn.h>
#include <Xm/RowColumnP.h>


static void MoveNext();
static void MovePrev();
static void Start();
static void Home();
static void TraverseToChild ();

void    _XmMgrTraversal();
void    _XmProcessTraversal();
void    _XmClearKbdFocus();
void    _XmClearFocusPath();
void    _XmMoveFocus();
void    _XmClearTabGroup();
static Boolean _XmTabGroupIsTraversable();
Boolean _XmFindTraversablePrim();
Boolean _XmTestTraversability();
Boolean _XmFocusIsHere();
Widget  _XmGetTabGroup();
Widget  _XmFindTabGroup();
Widget  _XmFindNextTabGroup();
Widget  _XmFindPrevTabGroup();
unsigned char _XmGetFocusPolicy();
ShellWidget _XmFindTopMostShell();
void    _XmFocusModelChanged();
XmFocusData * _XmGetFocusData();
Boolean _XmCreateVisibilityRect();



void _XmMgrTraversal (w, direction)

   XmManagerWidget w;
   int direction;

{
   Widget tabGroup;
   Widget currentTabGroup;
   XRectangle visRect;

   /*
    * Traversal only works in VendorShells or MenuShells; any other
    * shell class does not have the focus data information.
    */
   if (_XmGetFocusData(_XmFindTopMostShell(w)) == NULL)
      return;


   if (direction == XmTRAVERSE_NEXT_TAB_GROUP)
   {
      if ((tabGroup = _XmFindNextTabGroup(w)) == NULL)
         return;

      direction = XmTRAVERSE_CURRENT;
   }
   else if (direction == XmTRAVERSE_PREV_TAB_GROUP)
   {
      if ((tabGroup = _XmFindPrevTabGroup(w)) == NULL)
         return;

      direction = XmTRAVERSE_CURRENT;
   }
   else
   {
      /* Find the tab group in which this widget resides */
      if (currentTabGroup = _XmGetTabGroup(w))
         tabGroup = _XmFindTabGroup(w);
      else
         tabGroup = NULL;

      if ((_XmGetFocusPolicy(w) != XmEXPLICIT) ||
         (tabGroup && currentTabGroup && (tabGroup != currentTabGroup)))
      {
         return;
      }

      if (tabGroup)
      {
         /* Verify the widget is still accessible */
         if ((_XmFindTraversablePrim (tabGroup) == FALSE) ||
             (!_XmTabGroupIsTraversable(tabGroup)))
         {
            /*
             * The widget is no longer accessible; so we need to
             * find the next tab group which is, and move to it.
             */
            if (XmIsManager(tabGroup) &&
               (((XmManagerWidget)tabGroup)->manager.active_child != NULL))
            {
  	        ((XmManagerWidget)tabGroup)->manager.active_child = NULL;
            }

            /*
             * Special case for shared menupanes; if the visible menupane 
             * isn't traversable, we don't want to set the focus to one
             * of the other shared panes; we simply want to return.
             */
            if (XmIsRowColumn(tabGroup) &&
                ((RC_Type(tabGroup) == XmMENU_POPUP) ||
                 (RC_Type(tabGroup) == XmMENU_PULLDOWN)))
            {
               return;
            }

            if ((tabGroup = _XmFindNextTabGroup(tabGroup)) == NULL)
  	       return;

            direction = XmTRAVERSE_CURRENT;
         }
      }
      else
      {
         /* 
          * We got here either because the outermost manager got a
          * focusIn event, and it is not a tab group, or because
          * some widget has grabbed the focus from us.
          * If the widget is no longer accessible, then we had better
          * try to move the focus back to a tab group, if there is one.
          */
         if (XmIsPrimitive(w) || XmIsGadget(w))
         {
            if (!_XmCreateVisibilityRect(w, &visRect) ||
                !_XmTestTraversability(w, &visRect))
            {
               /* See if we can find another tab group */
               if ((tabGroup = _XmFindNextTabGroup(w)) == NULL)
                  return;

               direction = XmTRAVERSE_CURRENT;
            }
            else
               tabGroup = (Widget)w;
         }
         else
         {
            /* See if we can find another tab group */
            if (((tabGroup = _XmGetTabGroup(w)) == NULL) &&
              ((tabGroup = _XmFindNextTabGroup(w)) == NULL))
               return;

            direction = XmTRAVERSE_CURRENT;
         }
      }
   }
  
   /* There is a child to traverse to */
   switch(direction)
   {
      case XmTRAVERSE_NEXT:
      {
	  MoveNext(tabGroup); 
          break;
      }

      case XmTRAVERSE_PREV:
      {
	  MovePrev(tabGroup); 
          break;
      }

      case XmTRAVERSE_CURRENT:
      {
	  Start(tabGroup);  
          break;
      }

      case XmTRAVERSE_HOME:
      {
	  Home(tabGroup);  
          break;
      }
   }
}


/* We're guaranteed that there is a traversable item in the tab group */

static void Start (tabGroup)

  XmManagerWidget tabGroup;

{
  int i=0;
  Widget child;
  XRectangle visRect;

  if (XmIsPrimitive(tabGroup) || XmIsGadget(tabGroup))
  {
     TraverseToChild(tabGroup, tabGroup);
     return;
  }

  /*
   * If an active child is already defined, attempt to move to it.  If it is
   * no longer accessible, look for another child.
   */
  _XmCreateVisibilityRect(tabGroup, &visRect);
  if (tabGroup->manager.active_child != NULL) 
  {
      if (_XmTestTraversability(tabGroup->manager.active_child, &visRect))
      {
         TraverseToChild (tabGroup, tabGroup->manager.active_child);
         return;
      }
   }

   while (i < tabGroup->composite.num_children)
   {
       child = tabGroup->composite.children[i];
       if ((XmIsPrimitive(child) || XmIsGadget(child)) &&
           (_XmFindTabGroup(child) != child) &&
          _XmTestTraversability(child, &visRect))
       {
          TraverseToChild(tabGroup, child);
          break;
       }

       i++;
   }
}



/* We are guaranteed that there is a child to traverse to! */

static void MoveNext (tabGroup)

  XmManagerWidget tabGroup;

{
  int i=0;
  XRectangle visRect;

  /* Primitive widgets don't have a next or a previous */
  if(!XmIsManager(tabGroup))
     return;

  _XmCreateVisibilityRect(tabGroup, &visRect);

  if (tabGroup->manager.active_child != NULL)
  {
      /* FIND INDEX OF CURRENTLY ACTIVE CHILD */
      while ((tabGroup->composite.children[i] != 
              tabGroup->manager.active_child) &&
              (i < tabGroup->composite.num_children))i++;

      i++;
  }
  else
     i = 0;

   /* Determine if there are any traversable children after us */
   while ((i < tabGroup->composite.num_children) &&
     ((XmIsManager(tabGroup->composite.children[i])) ||
     (_XmFindTabGroup(tabGroup->composite.children[i]) ==
          tabGroup->composite.children[i]) ||
     (!_XmTestTraversability(tabGroup->composite.children[i], &visRect))))
   {
      i++;
   }

   /* Wrap to end of list, if necessary */
   if (i >= tabGroup->composite.num_children) 
      i=0; 

   /* Keep searching if we had been forced to wrap */
   if (i == 0)
   {
      while ((XmIsManager(tabGroup->composite.children[i])) || 
             (_XmFindTabGroup(tabGroup->composite.children[i]) ==
                  tabGroup->composite.children[i]) ||
           (!_XmTestTraversability(tabGroup->composite.children[i], &visRect)))
      {
          i++;
      }
   }

   TraverseToChild(tabGroup, tabGroup->composite.children[i]);
}




/* We are guaranteed that there is a child to traverse to! */

static void MovePrev (tabGroup)

  XmManagerWidget tabGroup;

{
  int i;
  XRectangle visRect;

   /* Primitive widgets don't have a next or a previous */
   if(!XmIsManager(tabGroup))
      return;

   _XmCreateVisibilityRect(tabGroup, &visRect);

   if (tabGroup->manager.active_child != NULL)
   {
      /* FIND INDEX OF CURRENTLY ACTIVE CHILD */
      i = tabGroup->composite.num_children -1;
      while ((tabGroup->composite.children[i] != 
              tabGroup->manager.active_child) && (i > 0))i--;

      i--;
   }
   else
     i = tabGroup->composite.num_children - 1;

   /* Determine if there are any traversable children before us */
   while ((i >= 0) &&
      ((XmIsManager(tabGroup->composite.children[i])) ||
       (_XmFindTabGroup(tabGroup->composite.children[i]) ==
            tabGroup->composite.children[i]) ||
      (!_XmTestTraversability(tabGroup->composite.children[i], &visRect))))
      i--;

   /* Wrap to the start of the list, if necessary */
   if (i < 0)
      i = (tabGroup->composite.num_children)-1;

   /* If we had wrapped, then keep searching */
   if (i == (tabGroup->composite.num_children - 1))
   {
      while ((XmIsManager(tabGroup->composite.children[i])) ||
             (_XmFindTabGroup(tabGroup->composite.children[i]) ==
                  tabGroup->composite.children[i]) ||
          (!_XmTestTraversability(tabGroup->composite.children[i], &visRect)))
      {
         i--;
      }
   }

   TraverseToChild (tabGroup, tabGroup->composite.children[i]);
}



static void Home (tabGroup)

  XmManagerWidget tabGroup;

{
  Widget candidate, chosen;
  int i=(-1);
  XRectangle visRect;

   /* If this is a primitive widget, then simply traverse to it */
   if (XmIsPrimitive(tabGroup) || XmIsGadget(tabGroup))
   {
      TraverseToChild(tabGroup, tabGroup);
      return;
   }
  
   chosen = NULL;
   _XmCreateVisibilityRect(tabGroup, &visRect);

   /* search thru list of children to find the first traversable child */
   while (++i < tabGroup->composite.num_children)
   {
      candidate = tabGroup->composite.children[i];
      if ((_XmFindTabGroup(candidate) != candidate) &&
          (_XmTestTraversability(candidate, &visRect)))
      {
         chosen = candidate;
         break;
      }
   }

   if (tabGroup->manager.active_child != chosen)
   {
      _XmClearFocusPath(tabGroup->manager.active_child);
      tabGroup->manager.active_child = chosen;

      TraverseToChild(tabGroup, chosen);
   }
}



static void TraverseToChild (tabGroup, chosen)

  XmManagerWidget tabGroup;
  Widget chosen;
  
{ 
   ShellWidget topmost_shell;

   if (XmIsManager(tabGroup))
      tabGroup->manager.active_child = chosen;
   else if (XmIsManager(XtParent(tabGroup)))
      ((XmManagerWidget)XtParent(tabGroup))->manager.active_child = chosen;

   /*
    * We need to find our shell widget ancestor, and reset
    * its keyboard focus to point to the new chosen child.
    */
   topmost_shell = _XmFindTopMostShell(tabGroup);
   XtSetKeyboardFocus(topmost_shell, chosen);
}



/*************************************<->*************************************
 *
 *  _XmMoveFocus(w)
 *
 *   Description:
 *   -----------
 *    Useful when an application has created multiple top level
 *    hierarchies of widgets, is using keyboard traversal and
 *    wishes to move between them.
 *
 *
 *   Inputs:  w  = widget which is the new top level widget.
 *   ------
 *
 *************************************<->***********************************/


void _XmMoveFocus(w)

 Widget w;

{
 /* MOVE MOUSE INTO NEW WINDOW HIERARCHY */
  XWarpPointer(XtDisplay(w), None, XtWindow(w),0,0,0,0,1,1);

 /*
  * We only need to force the focus if the toolkit says we are
  * running under a Focus[In,Out] model.  The Enter/Leave model
  * will take care of the focus itself.
  */
 if (_XtFocusMode != EnterNotifyActivated)
    XSetInputFocus (XtDisplay(w), XtWindow(w), RevertToParent, CurrentTime);


}


/*
 * This function verifies that all of the ancestors of a manager
 * are traversable.
 */

static Boolean _XmTabGroupIsTraversable (tabGroup)

   XmManagerWidget tabGroup;

{
   XRectangle visRect;

   if (XmIsPrimitive(tabGroup) || XmIsGadget(tabGroup))
      tabGroup = (XmManagerWidget)XtParent(tabGroup);

   if (!_XmCreateVisibilityRect(tabGroup, &visRect))
      return (False);

   while (tabGroup && XmIsManager(tabGroup))
   {
      if (_XmTestTraversability(tabGroup, NULL) == False)
         return (False);

      tabGroup = (XmManagerWidget)XtParent(tabGroup);
   }

   return (True);
}


void _XmClearKbdFocus (tabGroup)

   Widget tabGroup;

{
   ShellWidget topmost_shell;

   /* 
    * To clear the keyoard focus, we need to reset the shell's focus
    * widget back to the child of the shell.
    */
   topmost_shell = _XmFindTopMostShell(tabGroup);

   /* Clear active tab group field in the shell */
   _XmClearFocusPath(tabGroup);
   _XmClearTabGroup(tabGroup);

   _XmSetFocusResetFlag(topmost_shell, True);
   if (!topmost_shell->core.being_destroyed)
   {
      if (topmost_shell->composite.num_children > 0)
        XtSetKeyboardFocus(topmost_shell, topmost_shell->composite.children[0]);
      else
        XtSetKeyboardFocus (topmost_shell, NULL);
    }
   _XmSetFocusResetFlag(topmost_shell, False);
}


/*
 * Starting with the specified widget, clear the active_child field
 * in all manager widgets; then clear the have_traversal field in
 * the primitive widget.
 */

void _XmClearFocusPath (w)

   Widget w;

{
   XmManagerWidget mw;
   Boolean isPrimitive = False;
   Boolean isGadget = False;

   if (w == NULL)
      return;

   if (XmIsPrimitive(w))
   {
      isPrimitive = True;
      mw = (XmManagerWidget)XtParent(w);
   }
   else if (XmIsGadget(w))
   {
      isGadget = True;
      mw = (XmManagerWidget)XtParent(w);
   }
   else
      mw = (XmManagerWidget)w;

   if (mw && XmIsManager(mw))
   {
      if (mw->manager.active_child)
      {
         if (XmIsPrimitive(mw->manager.active_child))
         {
            ((XmPrimitiveWidget)mw->manager.active_child)->
                      primitive.have_traversal = False;
         }
         else if (XmIsGadget(mw->manager.active_child))
         {
            ((XmGadget)mw->manager.active_child)->
                      gadget.have_traversal = False;
         }
         mw->manager.active_child = NULL;
      }
   }
   else if (isPrimitive)
      ((XmPrimitiveWidget)w)->primitive.have_traversal = False;
   else if (isGadget)
      ((XmGadget)w)->gadget.have_traversal = False;
}


/************************************************************************
 *
 *  Find a Traversable Primitive
 *     This functions is used to determine if there is a primitive in 
 *     tab group which can be traversed to.
 *
 ************************************************************************/

Boolean _XmFindTraversablePrim (tabGroup)

   CompositeWidget tabGroup;

{
   register int i;
   WidgetList   wList;
   Widget  w;
   Boolean returnVal;
   XRectangle visRect;

   if (tabGroup->core.being_destroyed)
      return(False);

   /* If the tab group is a primitive widget, simply check it */
   if (XmIsPrimitive(tabGroup) || XmIsGadget(tabGroup))
   {
      if (!_XmCreateVisibilityRect(tabGroup, &visRect) || 
          !_XmTestTraversability(tabGroup, &visRect))
      {
         returnVal = False;
      }
      else
         returnVal = True;

      return(returnVal);
   }

   _XmCreateVisibilityRect(tabGroup, &visRect);
   wList = tabGroup->composite.children;

   for (i = 0; i < tabGroup->composite.num_children; i++)
   {
      w = *wList++;

      if (XmIsManager(w))
         continue;

      if (XmIsPrimitive (w) || XmIsGadget (w))
      {
         if ((_XmFindTabGroup(w) != w) && (_XmTestTraversability(w, &visRect)))
	    return (True);
      }
   }

   return (False);
}





/************************************************************************
 *
 *  _XmTestTraversability
 *
 ************************************************************************/

Boolean _XmTestTraversability (widget, visRect)

   Widget widget;
   XRectangle * visRect;

{
   Boolean mapped_when_managed;
   XRectangle tmpRect;

   if (XmIsPrimitive (widget))
   {
      if (!((XmPrimitiveWidget) widget)->primitive.traversal_on)
         return(False);

      mapped_when_managed = widget->core.mapped_when_managed;
   }
   else if (XmIsGadget (widget))
   {
      if (!((XmGadget) widget)->gadget.traversal_on)
         return(False);

      mapped_when_managed = True;
   }
   else
   {
      if (XmIsRowColumn(widget))
      {
         /* Transient menupanes don't have mapped_when_managed set */
         if ((RC_Type(widget) == XmMENU_POPUP) || 
             (RC_Type(widget) == XmMENU_PULLDOWN))
         {
            mapped_when_managed = True;
         }
         else
            mapped_when_managed = widget->core.mapped_when_managed;
      }
      else
         mapped_when_managed = widget->core.mapped_when_managed;
   }


   if (widget -> core.sensitive != True           ||
       widget -> core.ancestor_sensitive != True  ||
       widget -> core.managed != True             ||
       mapped_when_managed != True                ||
       widget -> core.being_destroyed == True     ||
       (visRect && !_XmIntersectRect(visRect, widget, &tmpRect)))
   {
      return(False);
   }

   return(True);
}




Boolean _XmFocusIsHere (mw, fullCheck)

   Widget mw;
   Boolean fullCheck;

{
   Widget tabGroup;

   /* Get the current tab group, if any */
   tabGroup = _XmGetTabGroup(mw);

   if (tabGroup && fullCheck)
   {
      if (XmIsManager(tabGroup))
      {
         XmManagerWidget mgr = (XmManagerWidget)tabGroup;
         XmPrimitiveWidget pw = (XmPrimitiveWidget)mgr->manager.active_child;
         XmGadget gw = (XmGadget)mgr->manager.active_child;

         if (pw == NULL)
            return(False);

         if (XmIsPrimitive(pw) && !pw->primitive.have_traversal)
            return(False);
         else if (XmIsGadget(gw) && !gw->gadget.have_traversal)
            return(False);
      }
      else if (XmIsPrimitive(tabGroup))
      {
         XmPrimitiveWidget pw = (XmPrimitiveWidget)tabGroup;

         if (!pw->primitive.have_traversal)
            return(False);
      }
      else if (XmIsGadget(tabGroup))
      {
         XmGadget gw = (XmGadget)tabGroup;

         if (!gw->gadget.have_traversal)
            return(False);
      }
      else
        return (False);
   }

   /* See if the tab group is one of our descendents */
   while (tabGroup != NULL)
   {
      if (tabGroup == mw)
         return(True);

      tabGroup = XtParent(tabGroup);
   }

   return(False);
}





/************************************************************************
 *
 *  _XmProcessTraversal
 *     This function handles all of the directional traversal conditions.
 *     It first verifies that traversal is active, and then invokes the
 *     traversal code.
 *
 ************************************************************************/

void _XmProcessTraversal (w, dir, check)
Widget w;
int    dir;
Boolean check;    

{
   if (check && (_XmGetFocusPolicy(w) != XmEXPLICIT))
      return;

   _XmMgrTraversal(w, dir);
}



/*
 * Functions for accessing and manipulating the tab group lists.
 */


/*
 * Given a primitive widget, find the tab group (if any) that this widget 
 * is in.  If it is in a tab group, then either it, or its parent will be
 * on the tab group list.  If both the parent and the child are tab groups,
 * then we will return the parents tab group.
 */
Widget _XmFindTabGroup (w)

   Widget w;

{
   Widget match = NULL;
   int i;
   ShellWidget topmost_shell;
   XmFocusData * focus_data;

   if (w == NULL)
      return (NULL);

   if (_XmGetFocusPolicy(w) != XmEXPLICIT)
   {
      _XmClearKbdFocus(w);
      return(NULL);
   }

   /* Find the topmost shell widget */
   topmost_shell = _XmFindTopMostShell(w);
   focus_data = _XmGetFocusData(topmost_shell);

   for (i = 0; i < focus_data->num_tab_groups; i++)
   {
      if (w->core.self == focus_data->tab_groups[i])
         return (focus_data->tab_groups[i]);
      else if (XtParent(w) == focus_data->tab_groups[i])
      {
         /*
          * Record the fact that the parent is a tab group, but keep looking
          * to see if the widget itself is one also.
          */
         match = focus_data->tab_groups[i];
      }
   }

   return(match);
}


/* Find the next tab group which is accessible; return NULL if none found */

Widget _XmFindNextTabGroup (w)

   Widget w;

{
   ShellWidget topmost_shell;
   XmFocusData * focus_data;
   int i, j, upper_limit;
   Widget oldActiveGroup;

   if (w == NULL)
      return(NULL);

   if (_XmGetFocusPolicy(w) != XmEXPLICIT)
   {
      _XmClearKbdFocus(w);
      return(NULL);
   }

   /* Find the topmost shell widget */
   topmost_shell = _XmFindTopMostShell(w);
   focus_data = _XmGetFocusData(topmost_shell);

   /* Determine where to start our search */
   oldActiveGroup = focus_data->active_tab_group;
   upper_limit = focus_data->num_tab_groups;

   if (oldActiveGroup)
   {
      for (i = 0; i < upper_limit; i++)
      {
         if (oldActiveGroup == focus_data->tab_groups[i])
         {
            i++;
            break;
         }
      }
   }
   else
      i = 0;

   /* Now, find the next available group */
   for (j = 0; j < upper_limit; j++)
   {
      /* Wrap, if necessary */
      if (i >= upper_limit)  
         i = 0;

      /* Test accessibility */
      if (_XmFindTraversablePrim(focus_data->tab_groups[i]))
      {
         /* Clear old focus path */
         if (oldActiveGroup &&
             (oldActiveGroup != focus_data->tab_groups[i]))
         {
            _XmClearFocusPath(oldActiveGroup);
         }

         focus_data->active_tab_group = focus_data->tab_groups[i];

         return(focus_data->active_tab_group);
      }
      i++;
   }

   /* Nothing is accessible */
   _XmClearKbdFocus(w);
   return (NULL);
}


/* Find the previous tab group which is accessible; return NULL if none found */

Widget _XmFindPrevTabGroup (w)

   Widget w;

{
   ShellWidget topmost_shell;
   XmFocusData * focus_data;
   int i, j, upper_limit;
   Widget oldActiveGroup;

   if (w == NULL)
      return(NULL);

   if (_XmGetFocusPolicy(w) != XmEXPLICIT)
   {
      _XmClearKbdFocus(w);
      return(NULL);
   }

   /* Find the topmost shell widget */
   topmost_shell = _XmFindTopMostShell(w);
   focus_data = _XmGetFocusData(topmost_shell);

   /* Determine where to start our search */
   oldActiveGroup = focus_data->active_tab_group;
   upper_limit = focus_data->num_tab_groups;

   if (oldActiveGroup)
   {
      for (i = 0; i < upper_limit; i++)
      {
         if (oldActiveGroup == focus_data->tab_groups[i])
         {
            i--;
            break;
         }
      }
   }
   else
      i = 0;

   /* In case the old group is not in the list */
   if (i >= upper_limit)
      i = upper_limit - 1;

   /* Now, find the next available group */
   for (j = 0; j < upper_limit; j++)
   {
      /* Wrap, if necessary */
      if (i < 0)  
         i = upper_limit -1;

      /* Test accessibility */
      if (_XmFindTraversablePrim(focus_data->tab_groups[i]))
      {
         /* Clear old focus path */
         if (oldActiveGroup &&
             (oldActiveGroup != focus_data->tab_groups[i]))
         {
            _XmClearFocusPath(oldActiveGroup);
         }

         focus_data->active_tab_group = focus_data->tab_groups[i];

         return(focus_data->active_tab_group);
      }
      i--;
   }

   /* Nothing is accessible */
   _XmClearKbdFocus(w);
   return (NULL);
}


/* Function to query the current focus policy for this widget hierarchy */

unsigned char _XmGetFocusPolicy (w)

   Widget w;

{
   ShellWidget topmost_shell;
   XmFocusData * focus_data;

   /* Find the topmost shell widget */
   topmost_shell = _XmFindTopMostShell(w);
   focus_data = _XmGetFocusData(topmost_shell);

   if (focus_data)
      return (focus_data->focus_policy);
   else
      return(XmPOINTER);
}


/* Clear the active tab group for the specified widget hierarchy */

void _XmClearTabGroup (w)

   Widget w;

{
   ShellWidget topmost_shell;
   XmFocusData * focus_data;
   Widget tabGroup;

   if (w == NULL)
      return;

   topmost_shell = _XmFindTopMostShell(w);
   if ((focus_data = _XmGetFocusData(topmost_shell)) == NULL)
      return;

   tabGroup = focus_data->active_tab_group;
   if (tabGroup && XmIsManager(tabGroup))
      ((XmManagerWidget)tabGroup)->manager.active_child = NULL;

   focus_data->active_tab_group = NULL;
}


/* Return the active tab group for the specified widget hierarchy */

Widget _XmGetTabGroup (w)

   Widget w;

{
   ShellWidget topmost_shell;
   XmFocusData * focus_data;

   if (w == NULL)
      return (NULL);

   topmost_shell = _XmFindTopMostShell(w);
   if ((focus_data = _XmGetFocusData(topmost_shell)) == NULL)
      return(NULL);

   /*
    * MenuShell widgets ALWAYS have one tab group: their menu pane.
    */
   if (XmIsMenuShell(topmost_shell))
   {
      if ((topmost_shell->composite.num_children > 0) &&
          (!topmost_shell->core.being_destroyed))
      {
         focus_data->active_tab_group = topmost_shell->composite.children[0];
      }
   }

   return(focus_data->active_tab_group);
}


/* Find the topmost shell widget associated with the specified widget */

ShellWidget _XmFindTopMostShell (w)

   Widget w;

{
   ShellWidget topmost_shell = (ShellWidget)w;

   if (w == NULL)
      return(NULL);

   while (!XtIsShell(topmost_shell) && XtParent(topmost_shell))
      topmost_shell = (ShellWidget)XtParent(topmost_shell);

   return(topmost_shell);
}


/*******************
 * Public procedures
 *******************/


void XmAddTabGroup (tabGroup)

   Widget tabGroup;

{
   ShellWidget topmost_shell = _XmFindTopMostShell(tabGroup);
   XmFocusData * focus_data;

   if ((focus_data = _XmGetFocusData(topmost_shell)) == NULL)
      return;

   /* Grow the list, if necessary */
   if (++focus_data->num_tab_groups >= focus_data->size_tab_group_list)
   {
      focus_data->size_tab_group_list += 10;
      focus_data->tab_groups = (Widget *)XtRealloc(focus_data->tab_groups,
           focus_data->size_tab_group_list * sizeof(Widget));
   }

   focus_data->tab_groups[focus_data->num_tab_groups-1] = tabGroup;
}


void XmRemoveTabGroup (tabGroup)

   Widget tabGroup;

{
   ShellWidget topmost_shell = _XmFindTopMostShell(tabGroup);
   XmFocusData * focus_data;
   int i, j;
   Boolean focusIsHere;

   focusIsHere = _XmFocusIsHere(tabGroup, True);

   if ((focus_data = _XmGetFocusData(topmost_shell)) == NULL)
      return;

   for (i = 0; i < focus_data->num_tab_groups; i++)
   {
      if (focus_data->tab_groups[i] == tabGroup)
      {
         for (j = i; j < focus_data->num_tab_groups-1; j++)
            focus_data->tab_groups[j] = focus_data->tab_groups[j+1];

         focus_data->num_tab_groups--;

         break;
      }
   }

   if (focusIsHere)
   {
      focus_data->active_tab_group = NULL;
      if (!XmIsManager(tabGroup))
         tabGroup = XtParent(tabGroup);

      if (focus_data->num_tab_groups == 0)
      {
         if (!topmost_shell->core.being_destroyed)
         {
            _XmSetFocusResetFlag(topmost_shell, True);
            XtSetKeyboardFocus (topmost_shell, NULL);

            if (topmost_shell->composite.num_children > 0)
            {
               XtSetKeyboardFocus(topmost_shell, 
                                  topmost_shell->composite.children[0]);
            }

            _XmSetFocusResetFlag(topmost_shell, False);
          }
      }
      else
         _XmProcessTraversal(tabGroup, XmTRAVERSE_HOME, False);

      if (!XmIsManager(tabGroup))
      {
         if (XmIsManager(XtParent(tabGroup)))
            ((XmManagerWidget)XtParent(tabGroup))->manager.active_child = NULL;
      }
      else
         ((XmManagerWidget)tabGroup)->manager.active_child = NULL;
   }
}


/*
 * Invoked by the VendorShell widget, when the focus_policy changes.
 * Registered as a callback by both the Manager and Primitive classes,
 * when the parent is a VendorShell widget.
 */

void _XmFocusModelChanged (topmost_shell, client_data, call_data)

   ShellWidget topmost_shell;
   caddr_t client_data;
   caddr_t call_data;

{
   unsigned char focus_policy = (unsigned char)call_data;
   XmFocusData * focus_data;

   if (focus_policy == XmPOINTER)
   {
      focus_data = _XmGetFocusData(topmost_shell);

      if (focus_data->active_tab_group)
      {
         if (!topmost_shell->core.being_destroyed)
         {
            _XmSetFocusResetFlag(topmost_shell, True);
            XtSetKeyboardFocus (topmost_shell, NULL);

            if (topmost_shell->composite.num_children > 0)
            {
               XtSetKeyboardFocus(topmost_shell, 
                                  topmost_shell->composite.children[0]);
            }

            _XmSetFocusResetFlag(topmost_shell, False);
          }

         _XmClearKbdFocus(focus_data->active_tab_group);
      }
   }
}


/*
 * Function used by TextEdit widgets to grab the focus; the widget must
 * be both traversable AND in a Tab Group to do this.  The request will
 * fail if either of these requirements are not met.
 */

Boolean _XmGrabTheFocus (w)

   Widget w;

{
   Widget tabGroup;
   ShellWidget topmost_shell;
   XmFocusData * focus_data;
   XmManagerWidget mw; 
   Widget active_child = NULL;
   XRectangle visRect;

   if (_XmGetFocusPolicy(w) != XmEXPLICIT)
      return(True);

   /*
    * The following check will be duplicated in the traversal code, but
    * we need to prevent getting that far if the requesting widget can't
    * be travered to; this prevents the focus from getting moved when it
    * shouldn't.
    */

   _XmCreateVisibilityRect(w, &visRect);
   if (XmIsManager(w) || !_XmTestTraversability(w, &visRect))
      return(False);

   tabGroup = _XmGetTabGroup(w);

   if (tabGroup)
   {
      if (XmIsManager(tabGroup))
      {
         XmGadget oldActive = (XmGadget)((XmManagerWidget)tabGroup)->
                                        manager.active_child;
         /*
          * If the old active child is a gadget, then we need to clean
          * it up manually, since it will never see a focus event, and
          * by the time its parent does, this information is lost.
          */
         if (oldActive && XmIsGadget(oldActive) && (oldActive != (XmGadget)w))
         {
            _XmDispatchGadgetInput(oldActive, NULL, XmFOCUS_OUT_EVENT);
            oldActive->gadget.have_traversal = False;
         }

         ((XmManagerWidget)tabGroup)->manager.active_child = NULL;
      }
      else if (XmIsGadget(tabGroup))
      {
         /*
          * If the old tab group is a gadget, then we need to clean
          * it up manually, since it will never see a focus event, and
          * by the time its parent does, this information is lost.
          */
         if (tabGroup != w)
         {
            _XmDispatchGadgetInput(tabGroup, NULL, XmFOCUS_OUT_EVENT);
            ((XmGadget)tabGroup)->gadget.have_traversal = False;
         }

         ((XmManagerWidget)XtParent(tabGroup))->manager.active_child = NULL;
      }
   }

   /* Get the tabgroup info associated with the requesting widget */
   if ((tabGroup = _XmFindTabGroup(w)) == NULL)
      tabGroup = w;
   else if (XmIsManager(tabGroup))
       ((XmManagerWidget)tabGroup)->manager.active_child = w;
   else
       ((XmManagerWidget)XtParent(tabGroup))->manager.active_child = w;

   topmost_shell = _XmFindTopMostShell(w);
   focus_data = _XmGetFocusData(topmost_shell);

   if (focus_data->active_tab_group && 
       (focus_data->active_tab_group != tabGroup))
   {
      /*
       * The following function call clears the active child.  If it was
       * a gadget, then the gadget will not remove its highlight as a result
       * of the upcoming _XmProcessTraversal() call.  So ... we'll save
       * the active child, and reset it, if its a gadget.
       */
      mw = (XmManagerWidget)focus_data->active_tab_group;
      if (XmIsManager(mw) && (mw->manager.active_child) && 
          XmIsGadget(mw->manager.active_child))
      {
          active_child = mw->manager.active_child;
      }

      _XmClearFocusPath (focus_data->active_tab_group);
      focus_data->active_tab_group = NULL;
 
      if (active_child)
         mw->manager.active_child = active_child;
   }

   focus_data->active_tab_group = tabGroup;
   _XmProcessTraversal(w, XmTRAVERSE_CURRENT, False);

   /* More clean up */
   if (active_child)
      mw->manager.active_child = NULL;

   return(True);
}


/*
 * This function returns a pointer to the focus data associated with the
 * topmost shell.  This allows us to treat the location opaquely.
 */

XmFocusData * _XmGetFocusData (topmost_shell)

   ShellWidget topmost_shell;

{
   if (topmost_shell == NULL)
      return(NULL);

   if (XmIsVendorShell(topmost_shell))
   {
      VendorShellWidget vs = (VendorShellWidget)topmost_shell;

      return (&(vs->vendor.focus_data));
   }
   else if (XmIsMenuShell(topmost_shell))
   {
      XmMenuShellWidget ms = (XmMenuShellWidget)topmost_shell;

      return (&(ms->menu_shell.focus_data));
   }
   else
      return(NULL);
}


/*
 * Depending upon the widget coming in, extract its mapped_when_managed flag
 * and its managed flag.
 */

Boolean _XmGetManagedInfo (w)

   Widget w;

{
   if (XmIsPrimitive (w))
      return (w->core.managed && w->core.mapped_when_managed);
   else if (XmIsGadget (w))
      return (w->core.managed);
   else
   {
      /* Treat menupanes specially */
      if (XmIsRowColumn(w) &&
          ((RC_Type(w) == XmMENU_POPUP) || (RC_Type(w) == XmMENU_PULLDOWN)))
      {
         return (True);
      }
      else
         return (w->core.managed && w->core.mapped_when_managed);
   }
}

/*
 * This function will generate a rectangle describing the portion of the
 * specified widget which is not clipped by any of its ancestors.  It also
 * verifies that the ancestors are both managed and mapped_when_managed.
 * It will return True if a non-empty intersection exists; it will return
 * False if no intersection occurs or one of the ancestors is not managed 
 * or mapped_when_managed.
 *
 * The passed-in widget must be a manager widget.
 */

Boolean _XmCreateVisibilityRect (w, rectPtr)

   Widget w;
   XRectangle * rectPtr;

{
   if (!_XmGetManagedInfo(w))
   {
       _XmClearRect(rectPtr);
       return(False);
   }

   _XmSetRect(rectPtr, w);
   w = XtParent(w);

   /* Process all widgets, excluding the shell widget */
   while (!XtIsShell(w))
   {
      if (!_XmGetManagedInfo(w) ||
          !_XmIntersectRect(rectPtr, w, rectPtr))
      {
         _XmClearRect(rectPtr);
         return (False);
      }

      w = XtParent(w);
   }

   return (True);
}

/*
 * Initialize the rectangle structure to the specified values.
 */

_XmSetRect (rect, w)

   register XRectangle * rect;
   Widget w;

{
   Position x, y;

   XtTranslateCoords(XtParent(w), w->core.x, w->core.y, &x, &y);
   rect->x = x;
   rect->y = y;
   rect->width = w->core.width;
   rect->height = w->core.height;
}

/*
 * Intersects the specified rectangle with the rectangle describing the
 * passed-in widget.  Returns True if they intersect, or False if they
 * do not.
 */

_XmIntersectRect (srcRectA, widget, dstRect)

   register XRectangle * srcRectA;
   register Widget  widget;
   register XRectangle * dstRect;

{
   register int srcABot, widgetBot;
   register int srcARight, widgetRight;
   Position x, y;
   int newHeight, newWidth;

   XtTranslateCoords(XtParent(widget), widget->core.x, widget->core.y, &x, &y);

   srcABot = srcRectA->y + srcRectA->height - 1;
   widgetBot = y + widget->core.height - 1;
   srcARight = srcRectA->x + srcRectA->width - 1;
   widgetRight = x + widget->core.width - 1;

   if (srcRectA->x >= x) 
       dstRect->x = srcRectA->x;
   else 
       dstRect->x = x;

   if (srcRectA->y > y) 
       dstRect->y = srcRectA->y;
   else 
       dstRect->y = y;

   if (srcARight >= widgetRight) 
   {
       newWidth = widgetRight - dstRect->x + 1;
       dstRect->width = (newWidth > 0) ? newWidth : 0;
   }
   else 
   {
       newWidth = srcARight - dstRect->x + 1;
       dstRect->width = (newWidth > 0) ? newWidth : 0;
   }

   if (srcABot > widgetBot) 
   {
       newHeight = widgetBot - dstRect->y + 1; 
       dstRect->height = (newHeight > 0) ? newHeight : 0;
   }
   else 
   {
       newHeight = srcABot - dstRect->y + 1;
       dstRect->height = (newHeight > 0) ? newHeight : 0;
   }

   if (_XmEmptyRect(dstRect))
      return(FALSE);

   return(TRUE);
}



_XmEmptyRect (r)

   register XRectangle * r;

{
   if (r->width <= 0 || r->height <= 0)
      return (TRUE);

   return (FALSE);
}

_XmClearRect (r)

   register XRectangle * r;

{
   r->x = 0;
   r->y = 0;
   r->width = 0;
   r->height = 0;
}
