//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Library General Public
//  License as published by the Free Software Foundation; either
//  version 2 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
//  Library General Public License for more details.
//
//  You should have received a copy of the GNU Library General Public
//  License along with this program; if not, write to the Free
//  Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307, USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  GCUI: Golded+ Character-oriented User Interface.
//  Pseudo graphical menu.
//  Based on CXL by Mike Smedley.
//  ------------------------------------------------------------------
//  wmenubeg()  starts a menu definition
//  wmenuitem() defines a menu item
//  wmenuend()  ends a menu definition
//  wmenuget()  processes the defined menu
//  ------------------------------------------------------------------

#include <gctype.h>
#include <cstdio>
#include <cstdlib>
#include <gmemdbg.h>
#include <gkbdcode.h>
#include <gkbdbase.h>
#include <gmoubase.h>
#include <gvidall.h>
#include <gwinall.h>
#include <gwinhelp.h>

#if defined(__USE_ALLOCA__)
    #include <malloc.h>
#endif


//  ------------------------------------------------------------------

int _overtagid;
int _finaltagid;

int gmnudropthrough = NO;


//  ------------------------------------------------------------------
//  prototypes for local functions

static _item_t* down_item(_item_t *curr);
static _item_t* left_item(_item_t *curr);
static _item_t* right_item(_item_t *curr);
static _item_t* up_item(_item_t *curr);


//  ------------------------------------------------------------------
//  menu item movement definitions

#define ITM_LT  0
#define ITM_RT  1
#define ITM_UP  2
#define ITM_DN  3
#define ITM_FR  4
#define ITM_LS  5


//  ------------------------------------------------------------------
//  array of function pointers to some of the item movement functions

static _item_t *(*mnu_funcs[4])(_item_t *) =
{
    left_item,
    right_item,
    up_item,
    down_item
};

//  ------------------------------------------------------------------
//  flag used for initial display of menu selections

static int dispdesc=YES;


//  ------------------------------------------------------------------
// this function will calculate the width of the selection bar

static int calc_bar_width(_menu_t *wmenu,_item_t *witem)
{
    int width;

    width=strlen(witem->str);
    if(wmenu->barwidth) width=wmenu->barwidth;
    return(width);
}


//  ------------------------------------------------------------------
// this function will calculate the window column
// at the center of the given menu item

static int calc_center_item(_item_t *item)
{
    return( ((int)item->wcol) + (strlen(item->str)/2) );
}


//  ------------------------------------------------------------------
//  this function will hide the mouse cursor
//  if mouse cursor mode is on

static void hide_mouse_cursor_mnu(void)
{

#ifdef GOLD_MOUSE
    if(gmou.FreeCursor())
        gmou.HideCursor();
#endif
}


//  ------------------------------------------------------------------
//  this function will display the mouse
//  cursor if mouse cursor mode is on

static void show_mouse_cursor_mnu(void)
{

#ifdef GOLD_MOUSE
    if(gmou.FreeCursor())
    {
        gmou.ShowCursor();
        gmou.SetCursor(0,0xFFFF,((LGREY_|_LGREY)<<8));
    }
#endif
}


//  ------------------------------------------------------------------
//  this function calls the given function

static void mnu_call_func(VfvCP func)
{

    _menu_t *menu;
    int w;
    int err;

    hide_mouse_cursor_mnu();
    menu=gwin.cmenu;
    w=whandle();
    err=whelpush();
    (*func)();
    wactiv(w);
    if(!err) whelpop();
    gwin.cmenu=menu;
    show_mouse_cursor_mnu();
}


//  ------------------------------------------------------------------
// this function will call a menu item's "after" function if it exists

static void call_after(_item_t *citem)
{
    if(citem->after!=NULL)
        mnu_call_func(citem->after);
}


//  ------------------------------------------------------------------
// this function will call a menu item's "before" function if it exists

static void call_before(_item_t *citem)
{
    if(citem->before!=NULL)
        mnu_call_func(citem->before);
}


//  ------------------------------------------------------------------
// this function closes the current menu's window and reactivates
// the menu window open prior to opening the current menu window

static void close_window(int w)
{
    if(!gwin.cmenu->usecurr)
    {
        hide_mouse_cursor_mnu();
        wclose();
        wactiv(w);
        show_mouse_cursor_mnu();
    }
}


//  ------------------------------------------------------------------
// this function displays a menu selection, using selection bar if specified

static void disp_item(_item_t *witem,int bar)
{
#if defined(__USE_ALLOCA__)
    char *buf = (char *)alloca(sizeof(vatch)*gvid->numcols);
#else
    __extension__ char buf[sizeof(vatch)*gvid->numcols];
#endif
    char ch;
    vattr chattr;
    _wrec_t* whp;
    const char* p;
    vatch* ptr=(vatch*)buf;
    int i, textend,width,wcol,found=NO;

    // if mouse cursor is on, temporarily hide it
    hide_mouse_cursor_mnu();

    // initialize width of output and end of text
    p = witem->str;
    width = calc_bar_width(gwin.cmenu,witem);
    textend = gwin.cmenu->textpos+strlen(p)-1;
    wgotoxy(witem->wrow,wcol=witem->wcol);
    if (width > (gvid->numcols-2)) width = gvid->numcols - 2;

    // display separators
    if (witem->fmask & M_SEPAR)
    {
        const int &border = gwin.active->border;
        const int &btype = gwin.active->btype;
        const vattr &attr = gwin.active->loattr;
        vatch line = vcatch(_box_table(btype, 1), attr);

        if (border) *ptr++ = vcatch(_box_table(btype, 9), attr);
        for (i = 0; i < width; i++) *ptr++ = line;
        if (border) *ptr = vcatch(_box_table(btype, 10), attr);

        int row = gwin.active->srow + witem->wrow + border;
        int col = gwin.active->scol + wcol;
        vputws(row, col, (vatch*)buf, width+2);
    }
    else
    {
        // display menu item including selection bar
        for(i=0; i<width; i++)
        {

            // see if currently in bar region.  if so, then use
            // a space for the character. otherwise use the
            // character from the current position in the string
            ch = ((i<gwin.cmenu->textpos) or (i>textend)) ? ' ' : (*p++);

            // select attribute of character to be displayed based upon if
            // selection bar was specified, if the menu item is non-selectable,
            // if the character is a tag character, or none of the above.
            if(bar)
                chattr = gwin.cmenu->barattr;
            else if(witem->fmask&M_NOSEL)
                chattr = gwin.cmenu->noselattr;
            else if((ch==witem->schar) and not found)
            {
                found = YES;
                chattr = gwin.cmenu->scharattr;
            }
            else
                chattr = gwin.cmenu->textattr;

            // display character in selected attribute
            //wprintc(witem->wrow,wcol++,chattr,ch);

            // Build menu line buffer

            *ptr++ = vcatch(ch,chattr);
        }

        // Display complete buffer
        wprintws(witem->wrow, wcol, (vatch*)buf, width);
    }

    // display text description, if one exists
    if((witem->desc!=NULL) and dispdesc)
    {
        whp = wfindrec(witem->dwhdl);
        i = (1 + whp->ecol - whp->scol) - (whp->border ? 2 : 0);
        sprintf(buf, "%-*.*s", i, i, witem->desc);
        wwprints(witem->dwhdl, witem->dwrow, witem->dwcol, witem->dattr, buf);
    }

    // if mouse cursor is hidden, unhide it
    show_mouse_cursor_mnu();
}


//  ------------------------------------------------------------------
// this function finds the next menu selection down

static _item_t * down_item(_item_t *curr)
{
    _item_t *best,*temp;
    int brow,bcol,tcol,trow,crow,ccol,tdist,bdist;

    // initialize best record to NULL
    best=NULL;
    brow=bcol=32767;

    // calculate window column at center of current item
    crow=(int)curr->wrow;
    ccol=calc_center_item(curr);

    // search backwards through linked list, testing each item
    for(temp=gwin.cmenu->item; temp!=NULL; temp=temp->prev)
    {

        // calculate window column at center of test item
        trow=(int)temp->wrow;
        tcol=calc_center_item(temp);

        if(trow>crow)
        {
            tdist=abs((ccol-tcol));
            bdist=abs((ccol-bcol));
            if((trow<brow) or ((trow==brow) and (tdist<bdist)))
            {
                best=temp;
                brow=trow;
                bcol=tcol;
            }
        }
    }

    // if there wasn't an item downwards, then wrap around
    if(best==NULL)
    {
        if((temp=(_item_t*)throw_malloc(sizeof(_item_t)))==NULL)
        {
            best=curr;
        }
        else
        {
            *temp=*curr;
            temp->wrow=-1;
            best=down_item(temp);
            throw_free(temp);
        }
    }
    else
        // see if menu selection is non-selectable
        if(best->fmask&M_NOSEL)
            best=down_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------
// this function finds the upper-leftmost menu selection

static _item_t * first_item(void)
{
    _item_t *best,*temp;

    // initialize best record to highest record in linked list
    best=gwin.cmenu->item;

    // search backwards through linked list, testing each item
    for(temp=best->prev; temp!=NULL; temp=temp->prev)
    {
        if((temp->wrow<best->wrow) or ((temp->wrow==best->wrow) and (temp->wcol<best->wcol)))
            best=temp;
    }

    // see if menu selection is non-selectable
    if(best->fmask&M_NOSEL) best=right_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------
// this is a recursive function that frees a menu and all of its submenus

static void free_menu(_menu_t *wmenu)
{
    _item_t *witem;

    // free all items in menu, including sub-menus
    while(wmenu->item!=NULL)
    {
        if(wmenu->item->child!=NULL) free_menu((_menu_t*)wmenu->item->child);
        witem=wmenu->item->prev;
        throw_free(wmenu->item);
        wmenu->item=witem;
        if(wmenu->item!=NULL) wmenu->item->next=NULL;
    }

    // free the menu itself
    throw_free(wmenu);
}


//  ------------------------------------------------------------------
// this function will find the bottom-rightmost menu selection

static _item_t * last_item(void)
{
    _item_t *best,*temp;
    int bcol;

    // initialize best record to highest record in linked list
    best=gwin.cmenu->item;
    bcol=best->wcol;

    // search backwards through linked list, testing each item
    for(temp=best->prev; temp!=NULL; temp=temp->prev)
    {
        if((temp->wrow>best->wrow) or ((temp->wrow==best->wrow) and (temp->wcol>bcol)))
        {
            best=temp;
            bcol=best->wcol;
        }
    }

    // see if menu selection is non-selectable
    if(best->fmask&M_NOSEL) best=left_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------
// this function is called after a menu bar move

static void post_move(_item_t *citem)
{
    gwin.cmenu->citem=citem;
    gwin.help=citem->help;
    disp_item(citem,1);
    call_before(citem);
}


//  ------------------------------------------------------------------
// this function prepares for a menu bar move

static void pre_move(_item_t *citem)
{
    disp_item(citem,0);
    call_after(citem);
}


//  ------------------------------------------------------------------
// this function moves the selection bar to another menu item,
// automatically taking care of necessary pre/post move actions

static _item_t * goto_item(_item_t *citem,int which)
{
    _item_t *item;

    if(which==ITM_FR)       item=first_item();
    else if(which==ITM_LS)  item=last_item();
    else                    item=(*mnu_funcs[which])(citem);
    if(item!=citem)
    {
        pre_move(citem);
        post_move(citem=item);
    }
    return(citem);
}


//  ------------------------------------------------------------------
// this function will find the menu selection to the left of current

static _item_t * left_item(_item_t *curr)
{
    _item_t *best,*temp;
    int wwidth,bpos,tpos,cpos;

    // calculate window width and current position
    wwidth=gwin.cmenu->ecol - gwin.cmenu->scol + 1;
    cpos=(curr->wrow * wwidth) + curr->wcol;

    // initialize best record to NULL, and best position to -1
    best=NULL;
    bpos=-1;

    // search backwards through linked list, testing each item
    for(temp=gwin.cmenu->item; temp!=NULL; temp=temp->prev)
    {

        // calculate position of test each item
        tpos=(temp->wrow*wwidth) + temp->wcol;

        // compare position of test item with best item, current item
        if((tpos>bpos) and (tpos<cpos))
        {
            best=temp;
            bpos=tpos;
        }
    }

    // if there wasn't a item to the left, then wrap around
    if(best==NULL)
        best=last_item();
    else
        // see if menu selection is non-selectable
        if(best->fmask&M_NOSEL)
            best=left_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------
// this function determines if the mouse cursor is on a menu item

#ifdef GOLD_MOUSE
static _item_t *mouse_on_item(_menu_t *menu,int mcrow,int mccol)
{
    int srow,scol,border,start,end;
    _item_t *item,*found;

    found  = NULL;
    srow   = menu->srow;
    scol   = menu->scol;
    border = menu->btype==5?0:1;
    for(item=menu->item; item!=NULL; item=item->prev)
    {
        if(mcrow==(srow+border+item->wrow))
        {
            start = scol+border+item->wcol;
            end   = start+calc_bar_width(menu,item)-1;
            if((mccol>=start) and (mccol<=end))
            {
                found=item;
                break;
            }
        }
    }
    return(found);
}
#endif


//  ------------------------------------------------------------------

static void pre_exit(int w,int close)
{
    _menu_t *wmenu;

    hide_mouse_cursor_mnu();

    // if not using current window for menu, then close it
    if(close) close_window(w);

    // if at highest menu then free the whole menu structure
    if(gwin.cmenu==gwin.menu)
    {
        wmenu=gwin.menu->prev;
        if(gwin.cmenu!=NULL)
            free_menu(gwin.cmenu);
        gwin.menu=wmenu;
        if(gwin.menu!=NULL)
            gwin.menu->next=NULL;
        gwin.cmenu=gwin.menu;
    }
}


//  ------------------------------------------------------------------
// this function reads the mouse for input

static gkey read_mouse(_item_t* citem)
{
(void)(citem);
#ifdef GOLD_MOUSE
    _item_t *item;

    // if free-floating mouse cursor support is on
    if(gmou.FreeCursor())
    {

        // clear mouse button queue
        gmou.ClearEvents();

        // loop until a key is pressed
        while(!kbxhit() and gkbd.kbuf==NULL)
        {

            // call the keyboard loop function
            //if(gkbd.kbloop!=NULL)
            //(*gkbd.kbloop)();

            // if left button was pressed, and mouse cursor is on
            // a selectable menu item, then move selection bar to
            // that item, and select it.  If mouse cursor is on
            // a main menu item of a pull-down menu system, then
            // stuff that item's selection character into the CXL
            // keyboard buffer and return Esc to close the current
            // pull-down menu.
            gmou.GetLeftRelease();
            if(gmou.Count())
            {
                item = mouse_on_item(gwin.cmenu,gmou.Row(),gmou.Column());
                if(item == NULL)
                {
                    if(gwin.cmenu->menutype&M_PD)
                    {
                        item = mouse_on_item(gwin.cmenu->parent,gmou.Row(),gmou.Column());
                        if(item and (!(item->fmask&M_NOSEL)))
                        {
                            kbput(item->schar);
                            return Key_Esc;
                        }
                    }
                }
                else
                {
                    if(!(item->fmask&M_NOSEL))
                    {
                        if(citem != item)
                        {
                            pre_move(citem);
                            gwin.cmenu->citem = citem = item;
                            post_move(item);
                        }
                        return Key_Ent;
                    }
                }
            }

            // if right button was pressed, simulate pressing the Esc key
            gmou.GetRightRelease();
            if(gmou.Count())
                return Key_Esc;
        }
    }
#endif

    // return zero - it means a key was pressed
    return 0;
}


//  ------------------------------------------------------------------
// this function will find the menu selection to the right of current

static _item_t * right_item(_item_t *curr)
{
    _item_t *best,*temp;
    int wwidth,bpos,tpos,cpos;

    // calculate window width and current position
    wwidth=gwin.cmenu->ecol - gwin.cmenu->scol + 1;
    cpos=(curr->wrow * wwidth) + curr->wcol;

    // initialize best record to NULL, and best position to 32767
    best=NULL;
    bpos=32767;

    // search backwards through linked list, testing items
    for(temp=gwin.cmenu->item; temp!=NULL; temp=temp->prev)
    {

        // calculate position of test item
        tpos=(temp->wrow*wwidth) + temp->wcol;

        // compare position of test item with best item, current item
        if((tpos<bpos) and (tpos>cpos))
        {
            best=temp;
            bpos=tpos;
        }
    }

    // if there wasn't a item to the right, then wrap around
    if(best==NULL)
        best=first_item();
    else
        // see if menu selection is non-selectable
        if(best->fmask&M_NOSEL)
            best=right_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------
// this function finds the previous menu selection upwards

static _item_t * up_item(_item_t *curr)
{
    _item_t *best,*temp;
    int brow,bcol,tcol,crow,trow,ccol,tdist,bdist;

    // initialize best record to NULL
    best = NULL;
    brow = -1;
    bcol = 32767;

    // calculate window column at center of current item
    crow=(int)curr->wrow;
    ccol=calc_center_item(curr);

    // search backwards through linked list, testing items
    for(temp=gwin.cmenu->item; temp!=NULL; temp=temp->prev)
    {

        // calculate window column at center of test item
        trow=(int)temp->wrow;
        tcol=calc_center_item(temp);

        if(trow<crow)
        {
            tdist=abs(ccol-tcol);
            bdist=abs(ccol-bcol);
            if((trow>brow) or ((trow==brow) and (tdist<bdist)))
            {
                best=temp;
                brow=trow;
                bcol=tcol;
            }
        }
    }

    // if there wasn't a item to the left, then wrap around
    if(best==NULL)
    {
        if((temp=(_item_t*)throw_malloc(sizeof(_item_t)))==NULL)
        {
            best=curr;
        }
        else
        {
            *temp=*curr;
            temp->wrow=255;
            best=up_item(temp);
            throw_free(temp);
        }
    }
    else
        // see if menu selection is non-selectable
        if(best->fmask&M_NOSEL)
            best=up_item(best);

    // return best record
    return(best);
}


//  ------------------------------------------------------------------

int wmenubeg(int srow, int scol, int erow, int ecol, int btype, vattr battr, vattr wattr, VfvCP open, int menutype)
{

    _menu_t* wmenu;

    // if this is a submenu, then make sure that it is under a menu item
    if(gwin.mlevel>gwin.ilevel) return(gwin.werrno=W_NOITMDEF);

    // allocate memory for new menu record
    if((wmenu=(_menu_t*)throw_malloc(sizeof(_menu_t)))==NULL)
        return(gwin.werrno=W_ALLOCERR);

    // if menu is not a submenu, make it a new base menu record
    if(!gwin.mlevel)
    {
        if(gwin.menu!=NULL) gwin.menu->next=wmenu;
        wmenu->prev=gwin.menu;
        wmenu->next=NULL;
        wmenu->parent=NULL;
        gwin.menu=gwin.cmenu=wmenu;
    }
    else
    {
        if(menutype & M_PD)
            gwin.cmenu->item->fmask |= M_HASPD;   // Has a pull-down menu
        gwin.cmenu->item->fmask &= ~M_CLOSE;    // Don't close parent menu
        wmenu->parent=gwin.cmenu;
        gwin.cmenu = (_menu_t*)(gwin.cmenu->item->child = wmenu);
    }

    // save info in menu record
    wmenu->srow=srow;
    wmenu->scol=scol;
    wmenu->erow=erow;
    wmenu->ecol=ecol;
    wmenu->btype=btype;
    wmenu->battr=battr;
    wmenu->loattr=battr;
    wmenu->sbattr=battr;
    wmenu->wattr=wattr;
    wmenu->menutype=menutype;
    wmenu->open=open;
    wmenu->usecurr=NO;
    wmenu->item=NULL;
    wmenu->title = "";
    wmenu->titlepos = -1;
    wmenu->titleattr = BLACK_|_BLACK;
    wmenu->shadattr = DEFATTR;
    wmenu->items = 0;

    // increment menu level
    gwin.mlevel++;

    // return normally
    return(gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuitem(int wrow, int wcol, const char* str, char schar, int tagid, int fmask, VfvCP select, gkey hotkey, int help)
{

    _item_t* witem;

    // make sure that wmenubeg() or wmenubegc() has been called
    if(!gwin.mlevel)
        return (gwin.werrno=W_NOMNUBEG);

    // allocate memory for new item record
    witem = (_item_t*)throw_malloc(sizeof(_item_t));
    if(witem==NULL)
        return (gwin.werrno=W_ALLOCERR);

    // add new menu item record to linked list
    if(gwin.cmenu->item)
        gwin.cmenu->item->next = witem;
    witem->prev = gwin.cmenu->item;
    witem->next = NULL;
    gwin.cmenu->item = witem;

    // save info in item record
    witem->wrow   = wrow;
    witem->wcol   = wcol;
    witem->str    = str;
    witem->schar  = schar;
    witem->tagid  = tagid;
    witem->fmask  = fmask;
    witem->select = select;
    witem->hotkey = hotkey;
    witem->desc   = NULL;
    witem->dwhdl  = -1;
    witem->dwrow  = 0;
    witem->dwcol  = 0;
    witem->dattr  = BLACK_|_BLACK;
    witem->redisp = NO;
    witem->help   = help;
    witem->child  = NULL;
    witem->before = NULL;
    witem->after  = NULL;

    gwin.cmenu->items++;

    int border = (gwin.cmenu->btype == 5) ? 0 : 1;

    int width = 1 + (gwin.cmenu->ecol-border) - (gwin.cmenu->scol+border);
    size_t _titlen = gwin.cmenu->title ? strlen(gwin.cmenu->title) : 0;
    size_t _strlen = strlen(str);
    size_t length = maximum_of_two(_strlen, _titlen);
    if((int)length > width)
        gwin.cmenu->ecol += length - width;

    if(gwin.cmenu->menutype & M_VERT)
    {
        int height = 1 + (gwin.cmenu->erow-border) - (gwin.cmenu->srow+border);
        if(gwin.cmenu->items > height)
            gwin.cmenu->erow += gwin.cmenu->items - height;
    }

    // set item level == menu level
    gwin.ilevel = gwin.mlevel;

    // return normally
    return (gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuend(int taginit, int menutype, int barwidth, int textpos, vattr textattr, vattr scharattr, vattr noselattr, vattr barattr)
{

    _item_t* item;
    int w_width, border, found;

    // make sure at least 1 menu item has been defined
    if(not gwin.mlevel or (gwin.mlevel>gwin.ilevel))
        return(gwin.werrno=W_NOITMDEF);

    // make sure that the specified initial tagid matches the
    // tagid of one of the defined menu items in this menu
    for(found=NO,item=gwin.cmenu->item; item!=NULL; item=item->prev)
    {
        if(item->tagid==taginit)
        {
            found=YES;
            break;
        }
    }
    if(!found) return(gwin.werrno=W_INVTAGID);

    // if bar width > window width, then bar width = window width
    border=(gwin.cmenu->btype==5)?0:1;
    w_width=(gwin.cmenu->ecol-border)-(gwin.cmenu->scol+border)+1;
    if(barwidth>w_width) barwidth=w_width;

    // add rest of menu information to menu record
    gwin.cmenu->citem     = NULL;
    gwin.cmenu->tagcurr   = taginit;
    gwin.cmenu->menutype |= menutype;
    gwin.cmenu->barwidth  = barwidth;
    gwin.cmenu->textpos   = barwidth?textpos:0;
    gwin.cmenu->textattr  = textattr;
    gwin.cmenu->scharattr = scharattr;
    gwin.cmenu->noselattr = noselattr;
    gwin.cmenu->barattr   = barattr;

    // set current menu pointer to parent menu
    if((gwin.cmenu=gwin.cmenu->parent)==NULL) gwin.cmenu=gwin.menu;

    // decrement menu and item levels
    gwin.mlevel--;
    gwin.ilevel--;

    // return with no error
    return(gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuget()
{

    int valid, pulldown=NO, menuerr, winerr, err;
    _item_t* citem;
    _item_t* item;
    int w = -1;
    gkey xch;
    char ch;
    static int _depth = 0;

    if (_depth == 0)
    {
        _overtagid = -1;
        _finaltagid = -1;
    }

    // make sure we have a defined menu
    if(gwin.cmenu==NULL)
    {
        gwin.werrno=W_NOMNUDEF;
        return(-1);
    }

    // make sure that wmenuend() was called
    if(gwin.mlevel or gwin.ilevel)
    {
        gwin.werrno=W_NOMNUEND;
        return(-1);
    }

    // open menu's window (unless menu is to use current window)
    if(!gwin.cmenu->usecurr)
    {
        w=whandle();
        hide_mouse_cursor_mnu();
        if(!wopen(gwin.cmenu->srow,gwin.cmenu->scol,gwin.cmenu->erow,gwin.cmenu->ecol,gwin.cmenu->btype,gwin.cmenu->battr,gwin.cmenu->wattr,gwin.cmenu->sbattr,gwin.cmenu->loattr))
            return -1;
        if(gwin.cmenu->shadattr != DEFATTR)
            wshadow(gwin.cmenu->shadattr);
        if(gwin.cmenu->title and *gwin.cmenu->title)
            wtitle(gwin.cmenu->title, gwin.cmenu->titlepos, gwin.cmenu->titleattr);
        show_mouse_cursor_mnu();

        // save environment info, call menu open
        // function, then restore environment info
        if(gwin.cmenu->open!=NULL)
            mnu_call_func(gwin.cmenu->open);
    }

    // if mouse cursor mode is on, show cursor
    show_mouse_cursor_mnu();

    // find first item in linked list
    if((item=gwin.cmenu->item)!=NULL) for(; item->prev!=NULL; item=item->prev);

    // display all menu items
    dispdesc=NO;
    valid=NO;
    while(item!=NULL)
    {
        disp_item(item,0);
        if(!(item->fmask&M_NOSEL)) valid=YES;
        item=item->next;
    }
    dispdesc=YES;

    // make sure there is at least 1 selectable menu item
    if(!valid)
    {
        pre_exit(w,YES);
        gwin.werrno=W_NOSELECT;
        return(-1);
    }

    // search linked list of item records for the item matching
    // the last item.  If there was no last item, search for
    // the initial tag ID position.  If no initial tag ID position
    // was specified, then find the upper-leftmost menu item.
    citem=NULL;
    if(gwin.cmenu->menutype&M_SAVE)
    {
        for(citem=gwin.cmenu->item; citem!=NULL; citem=citem->prev)
        {
            if((gwin.cmenu->citem==citem) and not (citem->fmask&M_NOSEL))
                break;
        }
    }
    if(citem==NULL)
    {
        citem=wmenuifind(gwin.cmenu->tagcurr);
        if((citem==NULL) or (citem->fmask&M_NOSEL)) citem=first_item();
    }

    // call the current menu item's 'before'
    // function and display current menu item
    post_move(citem);

    // main process of function

    for(;;)
    {

        // update current menu item and help category
        gwin.cmenu->citem=citem;
        gwin.help=citem->help;

        // read mouse/keyboard for keypress, then test the keypress
        gwin.menu->hotkey = false;
        gkbd.inmenu=true;
        xch = read_mouse(citem);
        citem=gwin.cmenu->citem;
        if(!xch)
        {
            xch = getxch();
        }
        gkbd.inmenu=false;

        _overtagid = citem->tagid;

        switch(xch)
        {

        case Key_Esc:

            // if Esc checking is on, erase selection bar,
            // free menu records and return to caller
            if(gwin.esc or (gwin.cmenu!=gwin.menu))
            {
ESCAPE_KEY:
                pre_move(citem);
                pre_exit(w,YES);
                gwin.werrno=W_ESCPRESS;
                return(-1);
            }
            break;

        case Key_Home:

            // hide selection bar and find upper-leftmost menu item
            citem=goto_item(citem,ITM_FR);
            break;

        case Key_Lft:

            // if current menu is a pull-down menu,
            // then erase selection bar, free menu records,
            // and return special code to caller
            if(gwin.cmenu->menutype&M_PD)
            {
                pre_move(citem);
                pre_exit(w,YES);
                gwin.werrno=W_NOERROR;
                return(M_PREVPD);
            }

            // if current menu is a horizontal menu, then hide
            // selection bar and find menu item to the left
            if(gwin.cmenu->menutype&M_HORZ)
                citem=goto_item(citem,ITM_LT);

            // if pull-down menu flag is set, select this item
            if(pulldown and (citem->child!=NULL))
                goto ENTER;
            break;

        case Key_Up:

            // if current menu is a vertical menu, then hide
            // selection bar and find menu item upwards
            if(gwin.cmenu->menutype&(M_VERT|M_PD))
                citem=goto_item(citem,ITM_UP);
            break;

        case Key_Rgt:

            // if current menu is a pull-down menu,
            // then erase selection bar, free menu records,
            // and return special code to caller
            if(gwin.cmenu->menutype&M_PD)
            {
                pre_move(citem);
                pre_exit(w,YES);
                gwin.werrno=W_NOERROR;
                return(M_NEXTPD);
            }

            // if current menu is a horizontal menu, then hide
            // selection bar and find menu item to the right
            if(gwin.cmenu->menutype&M_HORZ)
                citem=goto_item(citem,ITM_RT);

            // if pulldown flag is set, then select this item
            if(pulldown and (citem->child!=NULL))
                goto ENTER;
            break;

        case Key_Dwn:

            // if current item has a pull-down menu, select it
            if(citem->fmask&M_HASPD)
                goto ENTER;

            // if current menu is a vertical menu, then hide
            // selection bar and find menu item downwards
            if(gwin.cmenu->menutype&(M_VERT|M_PD))
                citem=goto_item(citem,ITM_DN);
            break;

        case Key_End:

            // hide selection bar and find
            // lower-rightmost menu item
            citem=goto_item(citem,ITM_LS);
            break;

        case Key_Ent:

ENTER:

            // if current menu item has a pull-down menu
            // attached, then set the pulldown flag
            if(citem->fmask&M_HASPD)
                pulldown=YES;

            // display menu item with selection bar
            disp_item(citem,1);

            menuerr=0;

            // if current menu item has a child menu
            if(citem->child!=NULL)
            {

                // save environment info, process child
                // menu, then restore environment info
                gwin.cmenu=(_menu_t*)citem->child;
                hide_mouse_cursor_mnu();
                err=whelpush();
                _depth++;
                menuerr=wmenuget();
                _depth--;
                winerr=gwin.werrno;
                if(!err) whelpop();
                show_mouse_cursor_mnu();
                gwin.cmenu=gwin.cmenu->parent;

                // if an error was returned by
                // child menu, free menu records
                // and pass error code to caller
                if((menuerr==-1) and (winerr!=W_ESCPRESS))
                {
                    call_after(citem);
                    menuerr=winerr;
                    pre_exit(w,YES);
                    gwin.werrno=menuerr;
                    return(-1);
                }
            }

            // if the M_CLOSB feature is on, then close the menu's
            // window before the selection function is called.
            if(citem->fmask&M_CLOSB) close_window(w);

            // this is used as a flag to see if the selection bar's
            // position has been changed by the select function.
            gwin.cmenu->tagcurr=-1;

            // if current menu item has a select function, call it
            if(citem->select!=NULL)
                mnu_call_func(citem->select);

            // if the M_CLOSB feature is on, then free the current
            // menu a before the selection function is called.
            if(citem->fmask&M_CLOSB)
            {
                call_after(citem);
                _finaltagid = citem->tagid;
                pre_exit(w,NO);
                gwin.werrno=W_NOERROR;
                return _finaltagid;
            }

            // check all menu items in current menu to
            // see if their redisplay flag has been set
            for(item=gwin.cmenu->item; item!=NULL; item=item->prev)
            {
                if(item->redisp)
                {
                    disp_item(item,(item==citem));
                    item->redisp=NO;
                }
            }

            // see if selection bar position was changed by select
            // function.  if so, then move selection bar to new item
            if(gwin.cmenu->tagcurr!=-1)
            {
                item=wmenuifind(gwin.cmenu->tagcurr);
                if((item!=NULL) and not (item->fmask&M_NOSEL))
                {
                    pre_move(citem);
                    post_move(gwin.cmenu->citem=citem=item);
                    break;
                }
            }

            // if current menu item has a pull-down attached
            if(citem->fmask&M_HASPD)
            {

                // if child menu returned previous pull-down menu
                // flag, find menu item to the left and select it
                if(menuerr==M_PREVPD)
                {
                    citem=goto_item(citem,ITM_LT);
                    if(citem->fmask&M_HASPD) goto ENTER;
                    break;
                }

                // if child menu returned next pull-down menu
                // flag, find menu item to the right and select it
                if(menuerr==M_NEXTPD)
                {
                    citem=goto_item(citem,ITM_RT);
                    if(citem->fmask&M_HASPD) goto ENTER;
                    break;
                }
            }

            // turn off pulldown flag
            pulldown=NO;

            // if child menu returned an exit-all-menus flag,
            // then free menu records and pass exit-all-menus
            // flag onto caller
            if((menuerr==M_EXITALL) or (citem->fmask&M_CLALL))
            {
                disp_item(citem,1);
                call_after(citem);
                if(citem->fmask&M_CLALL)
                    if(_finaltagid == -1)
                        _finaltagid = citem->tagid;
                pre_exit(w,YES);
                gwin.werrno=W_NOERROR;
                return M_EXITALL;
            }

            // unless an exit-this-menu flag was returned by the
            // child menu, or current item has close-menu
            // specified, free menu records, and return tag
            // identifier of current menu item
            if((citem->child!=NULL) or (citem->select!=NULL))
                if((menuerr!=M_EXIT) and (not (citem->fmask&M_CLOSE)))
                    break;
            call_after(citem);
            _finaltagid = citem->tagid;
            pre_exit(w,YES);
            gwin.werrno=W_NOERROR;
            return _finaltagid;

        default:

            // separate ASCII code from keypress code, if ASCII
            // code is zero, then it must be an extended key
            ch = char(xch & 0xFF);
            if (!ch and gmnudropthrough)
            {
                if((xch != Key_PgDn) and (xch != Key_PgUp))
                    kbput(xch);
                goto ESCAPE_KEY;
            }

            // scan through list of items for one that
            // has a tag identifier matching keypress
            valid=YES;
            item=citem->next;
            for(;;)
            {
                while(item!=NULL)
                {
                    if ((g_toupper(ch)==g_toupper(item->schar)) && !(item->fmask & M_NOSEL))
                    {
                        if (!gwin.menu->hotkey) _overtagid = item->tagid;
                        goto FARBREAK;
                    }
                    if(citem==item)
                    {
                        valid=NO;
                        goto FARBREAK;
                    }
                    item=item->next;
                }
                for(item=gwin.cmenu->item; item->prev!=NULL; item=item->prev) {}
            }

FARBREAK:

            // if a matching tag identifier was found,
            // then hide selection bar, and if quick-key
            // selection is allowed, select the found menu item
            if(valid)
            {
                if(item!=citem)
                {
                    pre_move(citem);
                    post_move(gwin.cmenu->citem=citem=item);
                }
                if(!(gwin.cmenu->menutype&M_NOQS)) goto ENTER;
            }
        }
    }
}


//  ------------------------------------------------------------------
//  Starts a menu definition in active window

int wmenubegc()
{

    // call wmenubeg() using current window parameters
    if(wmenubeg(gwin.active->srow,
                gwin.active->scol,
                gwin.active->erow,
                gwin.active->ecol,
                gwin.active->btype,
                gwin.active->battr,
                gwin.active->wattr,
                NULL))
    {
        return gwin.werrno;
    }

    // turn on use-current-window flag
    gwin.cmenu->usecurr=YES;

    // return normally
    return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------

static _item_t* search_menu(_menu_t* menu, int tagid)
{

    _item_t *witem, *item;

    // do while more items in this menu
    for(witem=menu->item; witem!=NULL; witem=witem->prev)
    {

        // if tagid of found item matches item we're
        // searching for, then return that item's address
        if(witem->tagid==tagid)
            return witem;

        // if current item has a child menu, search it
        if(witem->child!=NULL)
        {
            item = search_menu((_menu_t*)witem->child,tagid);
            if(item!=NULL)
                return item;
        }
    }

    // return address of item found
    return witem;
}


//  ------------------------------------------------------------------
//  Finds item record in a menu structure

_item_t* wmenuifind(int tagid)
{

    _item_t* item;

    // check for existance of a menu
    if(gwin.cmenu==NULL)
    {
        gwin.werrno=W_NOMNUDEF;
        return NULL;
    }

    // start search process at root of menu structure
    item = search_menu(gwin.menu,tagid);

    // return to caller
    gwin.werrno = (item==NULL) ? W_NOTFOUND : W_NOERROR;
    return item;
}


//  ------------------------------------------------------------------
//  Adds a text description to menu item

int wmenuitxt(int whdl, int wrow, int wcol, vattr attr, const char* str)
{

    // make sure at least 1 menu item has been defined
    if(!gwin.mlevel or gwin.mlevel>gwin.ilevel)
        return gwin.werrno=W_NOITMDEF;

    // add description info to menu record
    _item_t* citem = gwin.cmenu->item;
    citem->dwhdl = whdl;
    citem->dwrow = wrow;
    citem->dwcol = wcol;
    citem->dattr = attr;
    citem->desc  = str;

    // return normally
    return gwin.werrno=W_NOERROR;
}


//  ------------------------------------------------------------------

int wmenuiba(VfvCP before, VfvCP after)
{

    // make sure at least 1 menu item has been defined
    if(!gwin.mlevel or gwin.mlevel > gwin.ilevel)
        return(gwin.werrno=W_NOITMDEF);

    // update menu item record
    gwin.cmenu->item->before = before;
    gwin.cmenu->item->after = after;

    // return normally
    return (gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuidsab(int tagid)
{

    struct _item_t *item;

    // check for existance of a menu
    if(gwin.cmenu==NULL)
        return (gwin.werrno=W_NOMNUDEF);

    // find address of requested menu item
    if((item=wmenuifind(tagid))==NULL)
        return gwin.werrno;

    // disable menu item by making it nonselectable
    item->fmask |= M_NOSEL;

    // set menu item's redisplay flag
    item->redisp = true;

    // return normally
    return (gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuienab(int tagid)
{

    struct _item_t *item;

    // check for existance of a menu
    if(gwin.cmenu==NULL)
        return (gwin.werrno=W_NOMNUDEF);

    // find address of requested menu item
    if((item=wmenuifind(tagid))==NULL)
        return gwin.werrno;

    // enable menu item by making it selectable
    item->fmask &= (~M_NOSEL);

    // set menu item's redisplay flag
    item->redisp = true;

    // return normally
    return (gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------

int wmenuinext(int tagid)
{

    //  see if tagid is valid
    if(wmenuifind(tagid)==NULL)
        return gwin.werrno;

    // set current menu's current tagid to input tagid
    wmenumcurr()->tagcurr = tagid;

    // return normally
    return (gwin.werrno=W_NOERROR);
}


//  ------------------------------------------------------------------
