/* 
 *  @OPENGROUP_COPYRIGHT@
 *  COPYRIGHT NOTICE
 *  Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
 *  Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
 *  ALL RIGHTS RESERVED (MOTIF). See the file named COPYRIGHT.MOTIF for
 *  the full copyright text.
 *  
 *  This software is subject to an open license. It may only be
 *  used on, with or for operating systems which are themselves open
 *  source systems. You must contact The Open Group for a license
 *  allowing distribution and sublicensing of this software on, with,
 *  or for operating systems which are not Open Source programs.
 *  
 *  See http://www.opengroup.org/openmotif/license for full
 *  details of the license agreement. Any use, reproduction, or
 *  distribution of the program constitutes recipient's acceptance of
 *  this agreement.
 *  
 *  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
 *  PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
 *  WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
 *  OR FITNESS FOR A PARTICULAR PURPOSE
 *  
 *  EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
 *  NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
 *  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
 *  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *  ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
 *  EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGES.
*/ 
/* 
 * HISTORY
*/ 
#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$TOG: MenuUtil.c /main/16 1999/05/13 15:57:21 mgreess $"
#endif
#endif
/* (c) Copyright 1989, DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS. */
/* (c) Copyright 1987, 1988, 1989, 1990, 1991, 1992 HEWLETT-PACKARD COMPANY */

#include <stdio.h>
#include <ctype.h>
#include <X11/IntrinsicP.h>
#include <X11/ShellP.h>
#include <Xm/CascadeBGP.h>
#include <Xm/CascadeBP.h>
#include <Xm/MenuShellP.h>
#include <Xm/RowColumnP.h>
#include <Xm/ScreenP.h>
#include <Xm/XmosP.h>
#include "GadgetUtiI.h"
#include "MenuStateI.h"
#include "MenuUtilI.h"
#include "MessagesI.h"
#include "RCMenuI.h"
#include "TravActI.h"
#include "TraversalI.h"
#include "UniqueEvnI.h"
#include "XmI.h"

#define GRABPTRERROR    _XmMMsgCascadeB_0003
#define GRABKBDERROR    _XmMMsgRowColText_0024

#define EVENTS              ((unsigned int) (ButtonPressMask | \
                              ButtonReleaseMask | EnterWindowMask | \
                              LeaveWindowMask))

/********    Static Function Declarations    ********/

static void MenuTraverse( 
                        Widget w,
                        XEvent *event,
                        XmTraversalDirection direction) ;
static void GadgetCleanup( 
                        XmRowColumnWidget rc,
                        XmGadget oldActiveChild) ;
static Boolean WrapRight( 
                        XmRowColumnWidget rc) ;
static Boolean WrapLeft( 
                        XmRowColumnWidget rc) ;
static void LocateChild( 
                        XmRowColumnWidget rc,
                        Widget wid,
                        XmTraversalDirection direction) ;
static void MoveDownInMenuBar( 
                        XmRowColumnWidget rc,
                        Widget pw) ;
static void MoveLeftInMenuBar( 
                        XmRowColumnWidget rc,
                        Widget pw) ;
static void MoveRightInMenuBar( 
                        XmRowColumnWidget rc,
                        Widget pw) ;
static void FindNextMenuBarItem( 
                        XmRowColumnWidget menubar) ;
static void FindPrevMenuBarItem( 
                        XmRowColumnWidget menubar) ;
static Boolean ValidateMenuBarItem( 
                        Widget oldActiveChild,
                        Widget newActiveChild) ;
static Boolean FindNextMenuBarCascade( 
                        XmRowColumnWidget menubar) ;
static Boolean FindPrevMenuBarCascade( 
                        XmRowColumnWidget menubar) ;
static Boolean ValidateMenuBarCascade( 
                        Widget oldActiveChild,
                        Widget newMenuChild) ;

/********    End Static Function Declarations    ********/


Boolean
_XmIsActiveTearOff (
       Widget widget)
{
    XmRowColumnWidget menu = (XmRowColumnWidget) widget;

    if (RC_TearOffActive(menu))
        return (True);
    else
        return (False);
}


/*
 * Call XtGrabPointer with retry
 */
int 
_XmGrabPointer(
	Widget widget,
	Bool owner_events, 
	unsigned int event_mask, 
	int pointer_mode, 
	int keyboard_mode, 
	Window confine_to, 
	Cursor cursor, 
	Time time )
{
   register int status, retry;

   for (retry=0; retry < 5; retry++)
   {
      if ((status = XtGrabPointer(widget, owner_events, event_mask, 
         			  pointer_mode, keyboard_mode, confine_to, 
				  cursor, time)) == GrabSuccess)
	 break;

      XmeMicroSleep(1000);
   }
   if (status != GrabSuccess)
      XmeWarning((Widget) widget, GRABPTRERROR);

   return(status);
}

/*
 * Call XtGrabKeyboard with retry
 */
int 
_XmGrabKeyboard(
	Widget widget,
	Bool owner_events, 
	int pointer_mode,
	int keyboard_mode, 
	Time time )
{
   register int status, retry;

   for (retry=0; retry < 5; retry++)
   {
      if ((status = XtGrabKeyboard(widget, owner_events, 
         pointer_mode, keyboard_mode, time)) == GrabSuccess)
	 break;
      XmeMicroSleep(1000);
   }
   if (status != GrabSuccess)
      XmeWarning(widget, GRABKBDERROR);

   return(status);
}


void 
_XmMenuSetInPMMode (
	Widget wid,
#if NeedWidePrototypes
	int flag )
#else
	Boolean flag )
#endif /* NeedWidePrototypes */
{
   _XmGetMenuState((Widget)wid)->MU_InPMMode = flag;
}

/*
 * This menuprocs procedure allows an external object to turn on and off menu
 * traversal.
 */
void
_XmSetMenuTraversal(
        Widget wid,
#if NeedWidePrototypes
        int traversalOn )
#else
        Boolean traversalOn )
#endif /* NeedWidePrototypes */
{
   if (traversalOn)
   {
      _XmSetInDragMode(wid, False);
      if (!XmProcessTraversal(wid , XmTRAVERSE_CURRENT))
         XtSetKeyboardFocus(XtParent(wid), wid);
   }
   else
   {
     _XmSetInDragMode(wid, True);
     if(    XmIsMenuShell( XtParent( wid))    )
       {
	 /* Must be careful not to trash the traversal environment
	  * for RowColumns which are not using menu-specific traversal.
	  */
	 _XmLeafPaneFocusOut(wid);
       }
   }
}


void
_XmLeafPaneFocusOut( 
	Widget wid )
{
   XEvent fo_event;
   Widget widget;
   XmRowColumnWidget rc = (XmRowColumnWidget)wid;

   /* find the leaf pane */
   while (RC_PopupPosted(rc))
     rc = (XmRowColumnWidget) 
       ((XmMenuShellWidget)RC_PopupPosted(rc))->composite.children[0];

   fo_event.type = FocusOut;
   fo_event.xfocus.send_event = True;
   if ((widget = rc->manager.active_child) && XmIsCascadeButtonGadget(widget))
   {
      /* clear the internal focus path; also active_child = NULL which happens
       * to make cascadebutton focus out work correctly.
       */
      _XmClearFocusPath((Widget)rc);
      _XmDispatchGadgetInput(widget, NULL, XmFOCUS_OUT_EVENT);
      ((XmGadget)widget)->gadget.have_traversal = False;
   }
   else
   {
      if (widget && XmIsPrimitive(widget) &&
          (((XmPrimitiveWidgetClass)(widget->core.widget_class))->
            primitive_class.border_unhighlight != (XtWidgetProc)NULL))
         (*(((XmPrimitiveWidgetClass)(widget->core.widget_class))->
            primitive_class.border_unhighlight))((Widget) widget);
      else
	 _XmManagerFocusOut( (Widget) rc, &fo_event, NULL, NULL);

      /* clears the focus_item so that next TraverseToChild() will work */
      _XmClearFocusPath((Widget)rc);
   }
}

/*ARGSUSED*/
void
_XmMenuHelp(
        Widget wid,
        XEvent *event,
        String *params,		/* unused */
        Cardinal *num_params )	/* unused */
{
   XmRowColumnWidget rc = (XmRowColumnWidget) wid;
   XmGadget gadget;

   if (!_XmIsEventUnique(event) ||
       (!RC_IsArmed(rc) && !((RC_Type(rc) == XmMENU_OPTION) ||
			     (RC_Type(rc) == XmMENU_PULLDOWN)))) 
     return;

   if (!_XmGetInDragMode ((Widget)rc))
   {
     if ((gadget = (XmGadget) rc->manager.active_child) != NULL)
	_XmDispatchGadgetInput( (Widget) gadget, event, XmHELP_EVENT);
     else
     {
	_XmSocorro( (Widget) rc, event, NULL, NULL);
	_XmMenuPopDown((Widget)rc, event, NULL);
     }
   }
   else
   {
     if ((gadget = (XmGadget) 
	  XmObjectAtPoint((Widget) rc, event->xkey.x, event->xkey.y)) != NULL)
        _XmDispatchGadgetInput( (Widget) gadget, event, XmHELP_EVENT);
     else
     {
	_XmSocorro( (Widget) rc, event, NULL, NULL);
	_XmMenuPopDown((Widget)rc, event, NULL);
     }
   }
   _XmRecordEvent(event);
}

static void 
MenuTraverse(
        Widget w,
        XEvent *event,
        XmTraversalDirection direction )
{
   Widget parent;

   /*
    * The case may occur where the reporting widget is in fact the
    * RowColumn widget, and not a child.  This will occur if the
    * RowColumn has not traversable children.
    */
   if (XmIsRowColumn(w))
      parent = w;
   else if (XmIsRowColumn(XtParent(w)))
      parent = XtParent(w);
   else
      return;

   if ((RC_Type(parent) == XmMENU_POPUP) || 
       (RC_Type(parent) == XmMENU_PULLDOWN) ||
       (RC_Type(parent) == XmMENU_BAR))
   {
      _XmRecordEvent(event);
      (*(((XmRowColumnWidgetClass)XtClass(parent))->row_column_class.
          traversalHandler))( (Widget) parent, (Widget) w, direction);
   }
}

/* ARGSUSED */
void 
_XmMenuTraverseLeft(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
    if (_XmIsEventUnique(event))
   {
	 MenuTraverse(wid, event, XmTRAVERSE_LEFT);
   }
}

/* ARGSUSED */
void 
_XmMenuTraverseRight(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
   if (_XmIsEventUnique(event))
   {
	 MenuTraverse(wid, event, XmTRAVERSE_RIGHT);
   }
}

/* ARGSUSED */
void 
_XmMenuTraverseUp(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
   if (_XmIsEventUnique(event))
   {
	 MenuTraverse(wid, event, XmTRAVERSE_UP);
   }
}

/* ARGSUSED */
void 
_XmMenuTraverseDown(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
   if (_XmIsEventUnique(event))
   {
	 MenuTraverse(wid, event, XmTRAVERSE_DOWN);
   }
}

/* ARGSUSED */
void 
_XmMenuEscape(
        Widget w,
        XEvent *event,
        String *params,
        Cardinal *num_params )
{
   Widget parent = XtParent(w);

   /* Process the event only if not already processed */
   if (!_XmIsEventUnique(event))
      return;

   /*
    * Catch case where its a menubar w/ no submenus up - can't call
    *   menushell's popdown, call rowcolumn's instead.
    */
   if ((XmIsCascadeButton(w) || XmIsCascadeButtonGadget(w)) &&
	XmIsRowColumn(parent) && (RC_Type(parent) == XmMENU_BAR) &&
	!RC_PopupPosted(parent))
   {
      (*(((XmRowColumnClassRec *)XtClass(parent))->row_column_class.
	 menuProcedures)) (XmMENU_POPDOWN, parent, NULL, event, NULL);
   }
   else
       /* Let the menushell widget clean things up */
       (*(((XmMenuShellClassRec *)xmMenuShellWidgetClass)->
	  menu_shell_class.popdownOne))(w, event, NULL, NULL);
}

void 
_XmRC_GadgetTraverseDown(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
        XmRowColumnWidget rc = (XmRowColumnWidget) wid ;
   XmGadget gadget = (XmGadget)rc->manager.active_child;

   if (gadget && XmIsGadget(gadget))
      _XmMenuTraverseDown((Widget) gadget, event, param, num_param);
}

void 
_XmRC_GadgetTraverseUp(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
        XmRowColumnWidget rc = (XmRowColumnWidget) wid ;
   XmGadget gadget = (XmGadget)rc->manager.active_child;

   if (gadget && XmIsGadget(gadget))
      _XmMenuTraverseUp((Widget) gadget, event, param, num_param);
}

void 
_XmRC_GadgetTraverseLeft(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
        XmRowColumnWidget rc = (XmRowColumnWidget) wid ;
   XmGadget gadget = (XmGadget)rc->manager.active_child;

   /*
    * If there is not active child, then this RowColumn has
    * no traversable children, so it's fielding traversal
    * requests itself.
    */
   if (gadget)
      _XmMenuTraverseLeft((Widget) gadget, event, param, num_param);
   else 
      _XmMenuTraverseLeft((Widget) rc, event, param, num_param);
}

void 
_XmRC_GadgetTraverseRight(
        Widget wid,
        XEvent *event,
        String *param,
        Cardinal *num_param )
{
        XmRowColumnWidget rc = (XmRowColumnWidget) wid ;
   XmGadget gadget = (XmGadget)rc->manager.active_child;

   /*
    * If there is not active child, then this RowColumn has
    * no traversable children, so it's fielding traversal
    * requests itself.
    */
   if (gadget)
      _XmMenuTraverseRight((Widget) gadget, event, param, num_param);
   else 
      _XmMenuTraverseRight((Widget) rc, event, param, num_param);
}


/*
 * In case we've moved into our out of a gadget, we need to take care
 * of the highlighting ourselves, since the gadget will not get a focus
 * event.
 */
static void 
GadgetCleanup(
        XmRowColumnWidget rc,
        XmGadget oldActiveChild )
{
    XmGadget newActiveChild = (XmGadget)rc->manager.active_child;

    if (oldActiveChild != newActiveChild)
    {
        if (oldActiveChild && XmIsGadget(oldActiveChild))
        {
            _XmDispatchGadgetInput( (Widget) oldActiveChild, NULL,
                                                            XmFOCUS_OUT_EVENT);
            oldActiveChild->gadget.have_traversal = False;
        }
    }
}


/*
 * At the edge of the menu, decide what to do in this case
 */
static Boolean
WrapRight (
        XmRowColumnWidget rc )
{
   Widget topLevel;
   Widget oldActiveChild = rc->manager.active_child;
   Boolean done = False;

   _XmGetActiveTopLevelMenu ((Widget) rc, (Widget *) &topLevel);

   /* if in a menubar system, try to move to next menubar item cascade */
   if (XmIsMenuShell(XtParent(rc)) && (RC_Type(topLevel) == XmMENU_BAR) &&
       (FindNextMenuBarCascade((XmRowColumnWidget) topLevel)))
   {
      GadgetCleanup(rc, (XmGadget) oldActiveChild);
      done = True;
   }

   return (done);
}

/*
 * At the edge of the menu, decide what to do in this case
 */
static Boolean
WrapLeft (
        XmRowColumnWidget rc )
{
   Widget oldActiveChild = rc->manager.active_child;
   Boolean done = False;

   /* 
    * If we're the topmost pulldown menupane from a menubar, then unpost 
    * and move to the next available item in the menubar, and post its 
    * submenu.
    */
   if (XmIsMenuShell(XtParent(rc)) &&
       (RC_Type (rc) != XmMENU_POPUP) && RC_CascadeBtn(rc) && 
       (RC_Type (XtParent(RC_CascadeBtn(rc))) == XmMENU_BAR) &&
       (FindPrevMenuBarCascade((XmRowColumnWidget) 
                                      XtParent(RC_CascadeBtn(rc)))))
   {
      GadgetCleanup(rc, (XmGadget) oldActiveChild);
      done = True;
   }

   /*
    * if we are in a pulldown from another posted menupane, unpost this one
    */
   else if ((RC_Type(rc) == XmMENU_PULLDOWN) && RC_CascadeBtn(rc) &&
            (RC_Type(XtParent(RC_CascadeBtn(rc))) != XmMENU_OPTION) &&
            XmIsMenuShell(XtParent(rc)))
   {
      (*(((XmMenuShellClassRec *)xmMenuShellWidgetClass)->
                  menu_shell_class.popdownOne)) (XtParent(rc), NULL, NULL, 
                                                 NULL);
      done = True;
   }

   return (done);
}

/*
 * Search for the next menu item according to the direction
 */
static void
LocateChild (
        XmRowColumnWidget rc,
        Widget wid,
        XmTraversalDirection direction )
{
   Boolean done = False;
   Widget nextWidget;

   /* special case a popped up submenu with no traversable items */
   if (XmIsRowColumn(wid) && 
       ((XmManagerWidget) wid)->manager.active_child == 0)
   {
     if (direction == XmTRAVERSE_LEFT)
       WrapLeft (rc);
     else if (direction == XmTRAVERSE_RIGHT)
       WrapRight (rc);
   }
   else
   {
     nextWidget = _XmNavigate(wid, direction);

     if (direction == XmTRAVERSE_LEFT)
     {
       /* watch for left wrap */
       if ((wid->core.x <= nextWidget->core.x) ||
	   (nextWidget->core.y + nextWidget->core.height <= wid->core.y) ||
	   (nextWidget->core.y >= wid->core.y + wid->core.height))
	 done = WrapLeft(rc);
     }
     else if (direction == XmTRAVERSE_RIGHT)
     {
       /* watch for right wrap */
       if ((wid->core.x >= nextWidget->core.x) ||
	   (wid->core.y + wid->core.height <= nextWidget->core.y) ||
	   (wid->core.y >= nextWidget->core.y + nextWidget->core.height))
	 done = WrapRight(rc);
     }
     
     if (!done)
       _XmMgrTraversal (nextWidget, XmTRAVERSE_CURRENT);
   }

}

 void 
_XmMenuTraversalHandler(
        Widget w,
        Widget pw,
        XmTraversalDirection direction )
{
   XmRowColumnWidget rc = (XmRowColumnWidget) w;

   if (_XmGetInDragMode((Widget) rc))
      return;

   if ( LayoutIsRtoLM(rc) ) {
     if (direction == XmTRAVERSE_RIGHT)
       direction = XmTRAVERSE_LEFT;
     else if (direction == XmTRAVERSE_LEFT)
       direction = XmTRAVERSE_RIGHT;
   }
   if (RC_Type(rc) != XmMENU_BAR)
   {
      /* check for cascading into a submenu */
      if (direction == XmTRAVERSE_RIGHT && 
          XmIsCascadeButtonGadget(pw) && CBG_Submenu(pw)) 
      {
         (*(((XmGadgetClassRec *)XtClass(pw))->gadget_class.
            arm_and_activate))( (Widget) pw, NULL, NULL, NULL);
      }
      else if (direction == XmTRAVERSE_RIGHT && 
               XmIsCascadeButton(pw) && CB_Submenu(pw))
      {
         (*(((XmPrimitiveClassRec *)XtClass(pw))->primitive_class.
             arm_and_activate)) ((Widget) pw, NULL, NULL, NULL);
      }
      
      else
         LocateChild (rc, pw, direction);
   }

   else
   {
       switch (direction)
       {
          case XmTRAVERSE_DOWN:
          {
             MoveDownInMenuBar (rc, pw);
             break;
          }

          case XmTRAVERSE_LEFT:
          {	
	    MoveLeftInMenuBar(rc, pw);
	    break;
          }

          case XmTRAVERSE_RIGHT:
          {
	    MoveRightInMenuBar(rc, pw);
	    break;
          }
	  
          case XmTRAVERSE_UP:
	  default:
	     break;
       }
    }
}


/*
 * When the PM menubar mode is active, down arrow will
 * cause us to post the menupane associated with the active cascade button
 * in the menubar.
 */
static void 
MoveDownInMenuBar(
        XmRowColumnWidget rc,
        Widget pw )
{
    if (rc->manager.active_child == NULL)
        return;

    if (XmIsPrimitive(pw))
    {
        XmPrimitiveClassRec * prim;

	CB_SetTraverse (pw, TRUE);
        prim = (XmPrimitiveClassRec *)XtClass(pw);
        (*(prim->primitive_class.arm_and_activate)) ((Widget) pw, NULL,
						     NULL, NULL);
	CB_SetTraverse (pw, FALSE);
    }

    else if (XmIsGadget(pw))
    {
        XmGadgetClassRec * gad;
      
	CBG_SetTraverse (pw, TRUE);
        gad = (XmGadgetClassRec *)XtClass(pw);
        (*(gad->gadget_class.arm_and_activate)) ((Widget) pw, NULL,
						 NULL, NULL);
	CBG_SetTraverse (pw, FALSE);
    }
}

/* ARGSUSED */
static void 
MoveLeftInMenuBar(
        XmRowColumnWidget rc,
        Widget pw )
{
   XmMenuState mst = _XmGetMenuState((Widget)rc);

   if ((mst->MU_CurrentMenuChild != NULL) &&
       (RC_PopupPosted(rc) != NULL) &&
       ((XmIsCascadeButtonGadget(pw) && !CBG_Submenu(pw)) ||
       (XmIsCascadeButton(pw) && !CB_Submenu(pw))))
   {
      /* Move to the previous item in the menubar */
      FindPrevMenuBarItem(rc);
   }
   else 
   {
      /* Move to the previous item in the menubar */
      mst->MU_CurrentMenuChild = NULL;
      FindPrevMenuBarItem(rc);
   }
}

static void 
MoveRightInMenuBar(
        XmRowColumnWidget rc,
        Widget pw )
{
   XmMenuState mst = _XmGetMenuState((Widget)rc);
   
   if ((rc->manager.active_child == NULL) &&
        ((XmIsCascadeButtonGadget(pw) && !CBG_Submenu(pw)) ||
        (XmIsCascadeButton(pw) && !CB_Submenu(pw))))
   {
      FindNextMenuBarCascade(rc);
   }
   else
   {
      /* Move to the next item in the menubar */
      mst->MU_CurrentMenuChild = NULL;
      FindNextMenuBarItem(rc);
   }
}


/*
 * Find the next cascade button in the menubar which can be traversed to.
 */
static void 
FindNextMenuBarItem(
        XmRowColumnWidget menubar )
{
   register int i, j;
   int upper_limit;
   Widget active_child;

   /*
    * We're not in the PM menubar mode if we don't have an active child.
    */
   if (menubar->manager.active_child == NULL)
       return;

   upper_limit = menubar->composite.num_children;
   active_child = menubar->manager.active_child;

   /* Find the index of the currently active item */
   for (i = 0; i < upper_limit; i++)
   {
      if (menubar->composite.children[i] == active_child)
	 break;
   }

   /* Start looking at the next child */
   for (j = 0, i++; j < upper_limit - 1; j++, i++)
   {
       /* Wrap, if necessary */
       if (i >= upper_limit)
	  i = 0;

	if (ValidateMenuBarItem(active_child, menubar->composite.children[i]))
	  return;
   }
}


/*
 * Find the previous cascade button in the menubar which can be traversed to.
 */
static void 
FindPrevMenuBarItem(
        XmRowColumnWidget menubar )
{
   register int i, j;
   int upper_limit;
   Widget active_child;

   /* We're not in the PM menubar mode if we don't have an active child */
   if (menubar->manager.active_child == NULL)
       return;

   upper_limit = menubar->composite.num_children;
   active_child = menubar->manager.active_child;

   /* Find the index of the currently active item */
   for (i = 0; i < upper_limit; i++)
   {
       if (menubar->composite.children[i] == active_child)
	   break;
   }

   /* Start looking at the previous child */
   for (j = 0, --i; j < upper_limit - 1; j++, --i)
   {
       /* Wrap, if necessary */
       if (i < 0)
	  i = upper_limit - 1;

       if (ValidateMenuBarItem(active_child, menubar->composite.children[i]))
	  return;
   }
}

static Boolean 
ValidateMenuBarItem (
	Widget oldActiveChild,
        Widget newActiveChild)
{
   XmMenuState mst = _XmGetMenuState((Widget)oldActiveChild);

   if (XmIsTraversable(newActiveChild))
   {
      (void) XmProcessTraversal (newActiveChild, XmTRAVERSE_CURRENT);

      if (XmIsPrimitive(newActiveChild))
      {
         XmPrimitiveClassRec * prim;

         prim = (XmPrimitiveClassRec *)XtClass(newActiveChild);

         if (!mst->MU_InPMMode && CB_Submenu(newActiveChild))
            (*(prim->primitive_class.arm_and_activate)) (newActiveChild, NULL,
                                                                   NULL, NULL);
     }
      else if (XmIsGadget(newActiveChild))
      {
         XmGadgetClassRec * gadget;

         gadget = (XmGadgetClassRec *)XtClass(newActiveChild);

         if (!mst->MU_InPMMode && CBG_Submenu(newActiveChild))
            (*(gadget->gadget_class.arm_and_activate)) (newActiveChild, NULL,
                                                                   NULL, NULL);
      }
      return True;
   }
   else
      return False;
}

/*
 * Find the next hierarchy in the menubar which can be traversed to.
 */
static Boolean 
FindNextMenuBarCascade(
        XmRowColumnWidget menubar )
{
   Widget active_child = NULL;
   register int i, j;
   int upper_limit;
   ShellWidget shell;
   XmMenuState mst = _XmGetMenuState((Widget)menubar);

   upper_limit = menubar->composite.num_children;

   /*
    * Determine which child is popped up.
    */
   shell = (ShellWidget) RC_PopupPosted(menubar);
   if (shell != NULL)
      active_child = mst->MU_CurrentMenuChild =
         RC_CascadeBtn(shell->composite.children[0]);

   /* Find the index of the currently active item */
   for (i = 0; i < upper_limit; i++)
   {
      if (menubar->composite.children[i] == mst->MU_CurrentMenuChild)
          break;
   }

   /* Start looking at the next child */
   for (j = 0, i++; j < upper_limit - 1; j++, i++)
   {
      /* Wrap, if necessary */
      if (i >= upper_limit)
          i = 0;

      mst->MU_CurrentMenuChild = menubar->composite.children[i];
      if (ValidateMenuBarCascade(active_child, mst->MU_CurrentMenuChild))
         return True;
   }
   return False;
}


/*
 * Find the previous hierarchy in the menubar which can be traversed to.
 */
static Boolean 
FindPrevMenuBarCascade(
        XmRowColumnWidget menubar )
{
    Widget active_child = NULL;
    register int i, j;
    int upper_limit;
    ShellWidget shell;
    XmMenuState mst = _XmGetMenuState((Widget)menubar);

    upper_limit = menubar->composite.num_children;

    /* Determine which child is popped up */
    shell = (ShellWidget) RC_PopupPosted(menubar);
    if (shell != NULL)
       active_child = mst->MU_CurrentMenuChild =
          RC_CascadeBtn(shell->composite.children[0]);

    /* Find the index of the currently active item */
    for (i = 0; i < upper_limit; i++)
    {
        if (menubar->composite.children[i] == mst->MU_CurrentMenuChild)
           break;
    }

    /* Start looking at the previous child */
    for (j = 0, --i; j < upper_limit - 1; j++, --i)
    {
        /* Wrap, if necessary */
        if (i < 0)
           i = upper_limit - 1;

        mst->MU_CurrentMenuChild = menubar->composite.children[i];
        if (ValidateMenuBarCascade(active_child, mst->MU_CurrentMenuChild))
           return True;
    }
    return False;
}

/*ARGSUSED*/
static Boolean 
ValidateMenuBarCascade (Widget oldActiveChild, /* unused */
			Widget newMenuChild)
{
   XmRowColumnWidget menubar = (XmRowColumnWidget)XtParent(newMenuChild);
   Time _time = XtLastTimestampProcessed(XtDisplay(menubar));

   if (XmIsTraversable(newMenuChild))
   {
      if (XmIsCascadeButtonGadget(newMenuChild))
      {
         XmGadgetClassRec * gadget;

         gadget = (XmGadgetClassRec *)XtClass(newMenuChild);

         if (RC_PopupPosted(menubar) && !CBG_Submenu(newMenuChild))
         {
	     (*(((XmMenuShellClassRec *)xmMenuShellWidgetClass)->
		menu_shell_class.popdownEveryone))
		 (RC_PopupPosted(menubar),NULL, NULL, NULL);

            /* Return the X focus to the Menubar hierarchy from the menushell.
             * Set the Xt focus to the cascade
             */
            _XmMenuFocus((Widget) menubar, XmMENU_MIDDLE, _time);
            (void)XmProcessTraversal(newMenuChild, XmTRAVERSE_CURRENT);
         }
         else
         {
            (*(gadget->gadget_class.arm_and_activate)) (newMenuChild, NULL,
                                                                   NULL, NULL);
         }
         return True;
      }
      else if (XmIsCascadeButton (newMenuChild))
      {
         XmPrimitiveClassRec * prim;

         prim = (XmPrimitiveClassRec *)XtClass(newMenuChild);

         /* No submenu means PM mode */
         if (RC_PopupPosted(menubar) && !CB_Submenu(newMenuChild))
         {
	     (*(((XmMenuShellClassRec *)xmMenuShellWidgetClass)->
		menu_shell_class.popdownEveryone))
		 (RC_PopupPosted(menubar),NULL, NULL, NULL);

            /* Update X and Xt focus */
            _XmMenuFocus((Widget) menubar, XmMENU_MIDDLE, _time);
            (void)XmProcessTraversal(newMenuChild, XmTRAVERSE_CURRENT);
         }
         else
         {
            (*(prim->primitive_class.arm_and_activate)) (newMenuChild, NULL,
                                                                   NULL, NULL);
         }
         return True;
      }
   }
   return False;
}


/* (New) Grab strategy for menus requires grab before posting. If not possible
  * don't post the menu! This will ensure grabs active during a posted menu.
  * This will help consistency by preventing simultaneously posted menus.
  */
int 
_XmMenuGrabKeyboardAndPointer(
      Widget widget,
      Time time )
{
   register int status;

   if (status = _XmGrabKeyboard(widget, True, GrabModeSync, GrabModeAsync,
       time) != GrabSuccess)
      return(status);

   if (status = _XmGrabPointer(widget, True, EVENTS, GrabModeSync,
       GrabModeAsync, None, XmGetMenuCursor(XtDisplay(widget)), time) !=
         GrabSuccess)
      XtUngrabKeyboard(widget, CurrentTime);

   return(status);
}
