/** \file mswmenu.c
 * Pulldown menu creation and handling
 * \todo Code for accelerator keys was copied and pasted, replace with utility function
 */

/*  XTrkCad - Model Railroad CAD
 *  Copyright (C) (C) 2005 Dave Bullis
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU 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.
 */

#define OEMRESOURCE

#include <windows.h>
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <commdlg.h>
#include <math.h>
#include <ctype.h>
#include <assert.h>
#include "mswint.h"
#include "i18n.h"

/*
 *****************************************************************************
 *
 * Menus
 *
 *****************************************************************************
 */

typedef enum { M_MENU, M_SEPARATOR, M_PUSH, M_LIST, M_LISTITEM, M_TOGGLE, M_RADIO } mtype_e;
typedef enum { MM_BUTT, MM_MENU, MM_BAR, MM_POPUP } mmtype_e;

typedef struct wMenuItem_t * wMenuItem_p;

struct radioButtonGroup {
		int firstButton;	/* id of first button in group */
		int lastButton;		/* id of last button in group */	
};

/* NOTE: first field must be the same as WOBJ_COMMON */
#define MOBJ_COMMON \
		WOBJ_COMMON \
		int index; \
		mtype_e mtype; \
		wMenu_p parentMenu; \
		wMenuItem_p mnext;

struct wMenuItem_t {
		MOBJ_COMMON
		};

struct wMenu_t {
		MOBJ_COMMON
		mmtype_e mmtype;
		wMenuItem_p first, last;
		struct radioButtonGroup *radioGroup;
		HMENU menu;
		wButton_p button;
		wMenuTraceCallBack_p traceFunc;
		void * traceData;
		};

struct wMenuPush_t {
		MOBJ_COMMON
		wMenu_p mparent;
		wMenuCallBack_p action;
		long acclKey;
		wBool_t enabled;
		};

struct wMenuRadio_t {
		MOBJ_COMMON
		wMenu_p mparent;
		wMenuCallBack_p action;
		long acclKey;
		wBool_t enabled;
		};
struct wMenuToggle_t {
		MOBJ_COMMON
		wMenu_p mparent;
		wMenuCallBack_p action;
		long acclKey;
		wBool_t enabled;
		};

typedef struct wMenuListItem_t * wMenuListItem_p;
struct wMenuList_t {
		MOBJ_COMMON
		wMenuListItem_p left, right;
		wMenu_p mlparent;
		int max;
		int count;
		wMenuListCallBack_p action;
		};

struct wMenuListItem_t {
		MOBJ_COMMON
		wMenuListItem_p left, right;
		wMenuListCallBack_p action;
		};

#define UNCHECK (0)
#define CHECK	(1)
#define RADIOCHECK (2)
#define RADIOUNCHECK (3)

static 	HBITMAP checked;
static	HBITMAP unchecked;
static  HBITMAP checkedRadio;
static 	HBITMAP uncheckedRadio;


/*
 *****************************************************************************
 *
 * Internal Functions
 *
 *****************************************************************************
 */

char * mswStrdup( const char * str )
{
	char * ret;
	if (str) {
		ret = (char*)malloc( strlen(str)+1 );
		strcpy( ret, str );
	} else
		ret = NULL;
	return ret;
}

static LRESULT menuPush(
		wControl_p b,
		HWND hWnd,
		UINT message,
		WPARAM wParam,
		LPARAM lParam )
{
	wMenuItem_p m = (wMenuItem_p)b;
	wBool_t set;

	mswAllowBalloonHelp = TRUE;

	switch( message ) {

	case WM_COMMAND:

		switch (m->mtype) {
		default:
			mswFail( "pushMenu" );
			break;
		case M_PUSH:
			if (((wMenuPush_p)m)->action)
				((wMenuPush_p)m)->action(((wMenuPush_p)m)->data);
			break;
		case M_TOGGLE:
			set = wMenuToggleGet((wMenuToggle_p)m);
			set = !set;
			wMenuToggleSet((wMenuToggle_p)m,set);
			if (((wMenuToggle_p)m)->action)
				((wMenuToggle_p)m)->action(((wMenuPush_p)m)->data);
			break;
		case M_LISTITEM:
			if (((wMenuListItem_p)m)->action)
				((wMenuListItem_p)m)->action(0, "", ((wMenuListItem_p)m)->data);
			break;
		case M_RADIO:
			if (((wMenuRadio_p)m)->action)
				((wMenuRadio_p)m)->action(((wMenuRadio_p)m)->data);
			break;
		}
		return (LRESULT)0;
	}
	if ( (m->parentMenu)->traceFunc ) {
		(m->parentMenu)->traceFunc( m->parentMenu, m->labelStr, ((wMenu_p)m->parentMenu)->traceData );
	}
	return DefWindowProc( hWnd, message, wParam, lParam );
}

static void menuDone( wControl_p b )
{
	wMenuItem_p m = (wMenuItem_p)b;
	switch ( m->mtype ) {
	case M_MENU:
		if ( ((wMenu_p)m)->mmtype == MM_BUTT ||
			 ((wMenu_p)m)->mmtype == MM_POPUP )
			DestroyMenu( ((wMenu_p)m)->menu );
		break;
	}
}

static callBacks_t menuItemCallBacks = {
		NULL,
		menuDone,
		menuPush };


static wMenuItem_p createMenuItem(
		wMenu_p m,
		mtype_e mtype,
		const char * helpStr,
		const char * labelStr,
		int size )
{
	wMenuItem_p mi;

	mi = (wMenuItem_p)calloc( 1, size );
	mi->type = B_MENUITEM;
	/*mi->messageProc = menuPush;*/
	mi->index = mswRegister( (wControl_p)mi );
	mi->mtype = mtype;
	if (m) {
		if (m->last != NULL) {
			m->last->mnext = mi;
		} else {
			m->first = m->last = mi;
		}
		m->last = mi;
	}
	mi->mnext = NULL;
	mi->labelStr = mswStrdup( labelStr );
//	if (helpStr != NULL) {
//		char *string;
//		string = malloc( strlen(helpStr) + 1 );
//		strcpy( string, helpStr );
//		/*xv_set(mi->menu_item, XV_HELP_DATA, string, 0 );*/
//	}
	mswCallBacks[B_MENUITEM] = &menuItemCallBacks;
	return mi;
}

/*
 *****************************************************************************
 *
 * Accelerators
 *
 *****************************************************************************
 */


typedef struct {
		long acclKey;
		wMenuPush_p mp;
		wAccelKeyCallBack_p action;
		wAccelKey_e key;
		void * data;
		} acclTable_t, *acclTable_p;
static dynArr_t acclTable_da;
#define acclTable(N) DYNARR_N( acclTable_t, acclTable_da, N )


int mswMenuAccelerator(
		wWin_p win,
		long acclKey )
{
	acclTable_p at;
	if ( ((wControl_p)win)->type != W_MAIN &&
		 ((wControl_p)win)->type != W_POPUP )
		return 0;
	for ( at = &acclTable(0); at<&acclTable(acclTable_da.cnt); at++ ) {
		if (at->acclKey == acclKey) {
			if (at->mp) {
				if (at->mp->enabled && at->mp->action)
					at->mp->action(at->mp->data);
				return 1;
			} else if (at->action) {
				at->action( at->key, at->data );
				return 1;
			} else {
				return 0;
			}
		}
	}
	return 0;
}



static long acclKeyMap[] = {
			0,					/* wAccelKey_None, */
			VK_DELETE,			/* wAccelKey_Del, */
			VK_INSERT,			/* wAccelKey_Ins, */
			VK_HOME,			/* wAccelKey_Home, */
			VK_END,				/* wAccelKey_End, */
			VK_PRIOR,			/* wAccelKey_Pgup, */
			VK_NEXT,			/* wAccelKey_Pgdn, */
			VK_UP,				/* wAccelKey_Up, */
			VK_DOWN,			/* wAccelKey_Down, */
			VK_RIGHT,			/* wAccelKey_Right, */
			VK_LEFT,			/* wAccelKey_Left, */
			VK_BACK,			/* wAccelKey_Back, */
			VK_F1,				/* wAccelKey_F1, */
			VK_F2,				/* wAccelKey_F2, */
			VK_F3,				/* wAccelKey_F3, */
			VK_F4,				/* wAccelKey_F4, */
			VK_F5,				/* wAccelKey_F5, */
			VK_F6,				/* wAccelKey_F6, */
			VK_F7,				/* wAccelKey_F7, */
			VK_F8,				/* wAccelKey_F8, */
			VK_F9,				/* wAccelKey_F9, */
			VK_F10,				/* wAccelKey_F10, */
			VK_F11,				/* wAccelKey_F11, */
			VK_F12,				/* wAccelKey_F12, */
			VK_ADD,				/* wAccelKey_Numpad_Add, */
			VK_SUBTRACT			/* wAccelKey_Numpad_Subtract, */
		};


void wAttachAccelKey(
		wAccelKey_e key,
		int modifier,
		wAccelKeyCallBack_p action,
		void * data )
{
	acclTable_t * ad;
	if ( key < 1 || key > wAccelKey_Numpad_Subtract ) {
		mswFail( "wAttachAccelKey: key out of range" );
		return;
	}
	DYNARR_APPEND( acclTable_t, acclTable_da, 10 );
	ad = &acclTable(acclTable_da.cnt-1);
	ad->acclKey = acclKeyMap[key] | (modifier<<8);
	ad->key = key;
	ad->action = action;
	ad->data = data;
	ad->mp = NULL;
}

/*
 *****************************************************************************
 *
 * Menu Item Create
 *
 *****************************************************************************
 */

HBITMAP GetMyCheckBitmaps(UINT fuCheck) 
{ 
    COLORREF crBackground;  /* background color */                 
    HBRUSH hbrBackground;   /* background brush */                 
    HBRUSH hbrTargetOld;    /* original background brush */
    HDC hdcSource;          /* source device context */       
    HDC hdcTarget;          /* target device context */          
    HBITMAP hbmpCheckboxes; /* handle to check-box bitmap */
    BITMAP bmCheckbox;      /* structure for bitmap data */      
    HBITMAP hbmpSourceOld;  /* handle to original source bitmap */
    HBITMAP hbmpTargetOld;  /* handle to original target bitmap */
    HBITMAP hbmpCheck;      /* handle to check-mark bitmap */
    RECT rc;                /* rectangle for check-box bitmap */    
    WORD wBitmapX;          /* width of check-mark bitmap */       
    WORD wBitmapY;          /* height of check-mark bitmap */    
	WORD wMenuH;            /* height of menu line */
 
    /* Get the menu background color and create a solid brush 
       with that color. */
 
    crBackground = GetSysColor(COLOR_MENU); 
    hbrBackground = CreateSolidBrush(crBackground); 
 
    /* Create memory device contexts for the source and 
       destination bitmaps. */
 
    hdcSource = CreateCompatibleDC((HDC) NULL); 
    hdcTarget = CreateCompatibleDC(hdcSource); 
 
    /* Get the size of the system default check-mark bitmap and 
       create a compatible bitmap of the same size. */
 
    wBitmapX = GetSystemMetrics(SM_CXMENUCHECK); 
    wBitmapY = GetSystemMetrics(SM_CYMENUCHECK); 
	wMenuH = GetSystemMetrics(SM_CYMENU);
 
    hbmpCheck = CreateCompatibleBitmap(hdcSource, wBitmapX, 
        wBitmapY); 
 
    /* Select the background brush and bitmap into the target DC. */
 
    hbrTargetOld = SelectObject(hdcTarget, hbrBackground); 
    hbmpTargetOld = SelectObject(hdcTarget, hbmpCheck); 
 
    /* Use the selected brush to initialize the background color 
       of the bitmap in the target device context. */
 
    PatBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, PATCOPY); 
 
    /* Load the predefined check box bitmaps and select it 
       into the source DC. */
 
    hbmpCheckboxes = LoadBitmap((HINSTANCE) NULL, 
        (LPTSTR) OBM_CHECKBOXES); 
 
    hbmpSourceOld = SelectObject(hdcSource, hbmpCheckboxes); 
 
    /* Fill a BITMAP structure with information about the 
       check box bitmaps, and then find the upper-left corner of 
       the unchecked check box or the checked check box. */
 
    GetObject(hbmpCheckboxes, sizeof(BITMAP), &bmCheckbox); 
 
	switch( fuCheck ) {
		
	case UNCHECK:
        rc.left = 0; 
        rc.right = (bmCheckbox.bmWidth / 4); 
		rc.top = 0; 
		rc.bottom = (bmCheckbox.bmHeight / 3); 
		break;  
	case CHECK:  
        rc.left = (bmCheckbox.bmWidth / 4); 
        rc.right = (bmCheckbox.bmWidth / 4) * 2; 
	    rc.top = 0; 
	    rc.bottom = (bmCheckbox.bmHeight / 3); 
		break;
	case RADIOCHECK:
        rc.left = (bmCheckbox.bmWidth / 4); 
        rc.right = (bmCheckbox.bmWidth / 4) * 2; 
		rc.top = (bmCheckbox.bmHeight / 3);
	    rc.bottom = (bmCheckbox.bmHeight / 3) * 2; 
		break;
	case RADIOUNCHECK:
		rc.top = (bmCheckbox.bmHeight / 3);
	    rc.bottom = (bmCheckbox.bmHeight / 3) * 2; 
        rc.left = 0; 
        rc.right = (bmCheckbox.bmWidth / 4); 

		break;
	}
    
    /* Copy the appropriate bitmap into the target DC. If the 
       check-box bitmap is larger than the default check-mark 
       bitmap, use StretchBlt to make it fit; otherwise, just 
       copy it. */
    if (((rc.right - rc.left) > (int) wBitmapX) || 
            ((rc.bottom - rc.top) > (int) wBitmapY)) 
    {
        StretchBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, 
            hdcSource, rc.left, rc.top, rc.right - rc.left, 
            rc.bottom - rc.top, SRCCOPY); 
    }
 
    else 
    {
		// Center it vertically
		WORD dy = (wMenuH > wBitmapY) ? (wMenuH - wBitmapY) / 2 : 0;
		BitBlt(hdcTarget, 0, dy, rc.right - rc.left,
            rc.bottom - rc.top, 
            hdcSource, rc.left, rc.top, SRCCOPY); 
    }
 
    /* Select the old source and destination bitmaps into the 
       source and destination DCs, and then delete the DCs and 
       the background brush. */
 
    SelectObject(hdcSource, hbmpSourceOld); 
    SelectObject(hdcTarget, hbrTargetOld); 
    hbmpCheck = SelectObject(hdcTarget, hbmpTargetOld); 
 
    DeleteObject(hbrBackground); 
    DeleteObject(hdcSource); 
    DeleteObject(hdcTarget); 
 
    /* Return a handle to the new check-mark bitmap. */
 
    return hbmpCheck; 
} 

void mswCreateCheckBitmaps()
{
	checked = GetMyCheckBitmaps( CHECK );
	unchecked = GetMyCheckBitmaps( UNCHECK );
	checkedRadio = GetMyCheckBitmaps( RADIOCHECK );
	uncheckedRadio = GetMyCheckBitmaps( RADIOUNCHECK );

}

wMenuRadio_p wMenuRadioCreate(
		wMenu_p m, 
		const char * helpStr,
		const char * labelStr,
		long acclKey,
		wMenuCallBack_p action,
		void	*data )
{
	wMenuRadio_p mi;
	int rc;
	char label[80];
	char *cp;
	char ac;
	UINT vk;
	long modifier;

	mi = (wMenuRadio_p)createMenuItem( m, M_RADIO, helpStr, labelStr, sizeof *mi );
	mi->action = action;
	mi->data = data;
	mi->mparent = m;
	mi->acclKey = acclKey;
	mi->enabled = TRUE;
	strcpy( label, mi->labelStr );
	modifier = 0;
	if ( acclKey != 0 ) {
		DYNARR_APPEND( acclTable_t, acclTable_da, 10 );
		cp = label + strlen( label );
		*cp++ = '\t';
		if (acclKey & WCTL ) {
			strcpy( cp, "Ctrl+" );
			cp += 5;
			modifier |= WKEY_CTRL;
		}
		if (acclKey & WALT ) {
			strcpy( cp, "Alt+" );
			cp += 4;
			modifier |= WKEY_ALT;
		}
		if (acclKey & WSHIFT ) {
			strcpy( cp, "Shift+" );
			cp += 6;
			modifier |= WKEY_SHIFT;
		}
		*cp++ = toupper( (char)(acclKey & 0xFF) );
		*cp++ = '\0';
		ac = (char)(acclKey & 0xFF);
		if (isalpha(ac)) {
			ac = tolower( ac );
		}
		vk = VkKeyScan( ac );
		if ( vk & 0xFF00 )
			modifier |= WKEY_SHIFT;
		acclTable(acclTable_da.cnt-1).acclKey = (modifier<<8) | (vk&0x00FF);
		acclTable(acclTable_da.cnt-1).mp = (wMenuPush_p)mi;
	}
	rc = AppendMenu( m->menu, MF_STRING, mi->index, label );

	/* add the correct bitmaps for radio buttons */

    rc = SetMenuItemBitmaps(m->menu, mi->index, FALSE, uncheckedRadio, checkedRadio ); 

	if( m->radioGroup == NULL ) {
		m->radioGroup = malloc( sizeof( struct radioButtonGroup ));
		assert( m->radioGroup );
		m->radioGroup->firstButton = mi->index;
	} else {
		m->radioGroup->lastButton = mi->index;
	}

	return mi;
}

void wMenuRadioSetActive(wMenuRadio_p mi )
{
	BOOL rc;

	rc = CheckMenuRadioItem( mi->mparent->menu, 
							 mi->mparent->radioGroup->firstButton, 
							 mi->mparent->radioGroup->lastButton,
							 mi->index,
							 MF_BYCOMMAND );
} 


wMenuPush_p wMenuPushCreate(
		wMenu_p m, 
		const char * helpStr,
		const char * labelStr,
		long acclKey,
		wMenuCallBack_p action,
		void	*data )
{
	wMenuPush_p mi;
	int rc;
	char *label = malloc(strlen(labelStr) + 30 );	/**< The label and sufficient space for the keyboard shortcut */
	char *cp;
	char ac;
	UINT vk;
	long modifier;

	mi = (wMenuPush_p)createMenuItem( m, M_PUSH, helpStr, labelStr, sizeof *mi );
	mi->action = action;
	mi->data = data;
	mi->mparent = m;
	mi->acclKey = acclKey;
	mi->enabled = TRUE;
	strcpy(label, labelStr);
	modifier = 0;
	if ( acclKey != 0 && strlen(label ) < 60 ) {
		DYNARR_APPEND( acclTable_t, acclTable_da, 10 );
		cp = label + strlen( label );
		*cp++ = '\t';
		if (acclKey & WCTL ) {
			strcpy( cp, "Ctrl+" );
			cp += 5;
			modifier |= WKEY_CTRL;
		}
		if (acclKey & WALT ) {
			strcpy( cp, "Alt+" );
			cp += 4;
			modifier |= WKEY_ALT;
		}
		if (acclKey & WSHIFT ) {
			strcpy( cp, "Shift+" );
			cp += 6;
			modifier |= WKEY_SHIFT;
		}
		*cp++ = toupper( (char)(acclKey & 0xFF) );
		*cp++ = '\0';
		ac = (char)(acclKey & 0xFF);
		if (isalpha(ac)) {
			ac = tolower( ac );
		}
		vk = VkKeyScan( ac );
		if ( vk & 0xFF00 )
			modifier |= WKEY_SHIFT;
		acclTable(acclTable_da.cnt-1).acclKey = (modifier<<8) | (vk&0x00FF);
		acclTable(acclTable_da.cnt-1).mp = mi;
	}
	rc = AppendMenu( m->menu, MF_STRING, mi->index, label );
	free(label);
	return mi;
}


void wMenuPushEnable(
		wMenuPush_p mi,
		BOOL_T enable )
{
	EnableMenuItem( mi->mparent->menu, mi->index,
		MF_BYCOMMAND|(enable?MF_ENABLED:(MF_DISABLED|MF_GRAYED)) );
	mi->enabled = enable;
}


wMenu_p wMenuMenuCreate(
		wMenu_p m, 
		const char * helpStr,
		const char * labelStr )
{
	wMenu_p mm;
	int rc;

	mm = (wMenu_p)createMenuItem( NULL, M_MENU, NULL, labelStr, sizeof *mm );
	mm->menu = CreatePopupMenu();
	mm->mmtype = MM_MENU;
	/*mm->parent = (wControl_p)m;*/
	mm->first = mm->last = NULL;

	rc = AppendMenu( m->menu, MF_STRING|MF_ENABLED|MF_POPUP, (UINT_PTR)(mm->menu), mm->labelStr );

	return mm;
}

void wMenuSeparatorCreate(
		wMenu_p m ) 
{
	int rc;
	createMenuItem( m, M_SEPARATOR, NULL, NULL, sizeof *(wMenuItem_p)NULL );
	rc = AppendMenu( m->menu, MF_SEPARATOR, (UINT)0, NULL );
}

/*
 *****************************************************************************
 *
 * Menu List
 *
 *****************************************************************************
 */


static void appendItem(
		wMenuListItem_p ml,
		wMenuListItem_p mi )
{
	mi->right = ml->right;
	ml->right->left = mi;
	mi->left = ml;
	ml->right = mi;
}


static void removeItem(
		wMenuListItem_p mi )
{
	mi->left->right = mi->right;
	mi->right->left = mi->left;
	mi->right = mi->left = mi;
}


wMenuList_p wMenuListCreate(
		wMenu_p m, 
		const char * helpStr,
		int max,
		wMenuListCallBack_p action )
{
	wMenuList_p mi;
	mi = (wMenuList_p)createMenuItem( m, M_LIST, helpStr, NULL, sizeof *mi );
	mi->count = 0;
	mi->max = max;
	mi->mlparent = m;
	mi->action = action;
	mi->right = mi->left = (wMenuListItem_p)mi;
	return mi;
}


int getMlistOrigin( wMenu_p m, wMenuList_p ml )
{
	wMenuItem_p mi;
	int count;
	count = 0;
	for ( mi = m->first; mi != NULL; mi = mi->mnext ) {
		switch( mi->mtype ) {
		case M_SEPARATOR:
		case M_PUSH:
		case M_MENU:
			count++;
			break;
		case M_LIST:
			if (mi == (wMenuItem_p)ml)
				return count;
			count += ((wMenuList_p)mi)->count;
			break;
		default:
			mswFail( "getMlistOrigin" );
		}
	}
	return count;
}


void wMenuListAdd(
		wMenuList_p ml,
		int index,
		const char * labelStr,
		void * data )
{
	int origin;
	wMenuListItem_p wl_p;
	wMenuListItem_p mi;
	int count;
	int rc;

	origin = getMlistOrigin(ml->mlparent, ml);
	for ( count=0,wl_p=ml->right; wl_p!=(wMenuListItem_p)ml; count++,wl_p=wl_p->right ) {
		if (wl_p->labelStr != NULL && strcmp( labelStr, wl_p->labelStr ) == 0) {
			/* move item */
			if (count != index) {
				RemoveMenu( ml->mlparent->menu, origin+count, MF_BYPOSITION );
				removeItem( wl_p );
				goto add;
			}
			((wMenuListItem_p)wl_p)->data = data;
			return;
		}
	}
	if (ml->max > 0 && ml->count >= ml->max) {
		RemoveMenu( ml->mlparent->menu, origin+ml->count-1, MF_BYPOSITION );
		wl_p = ml->left;
		removeItem( ml->left );
add:
		ml->count--;
		if (wl_p->labelStr )
			free( CAST_AWAY_CONST wl_p->labelStr );
		wl_p->labelStr = mswStrdup( labelStr );
	} else {
		wl_p = (wMenuListItem_p)createMenuItem( NULL, M_LISTITEM, NULL,
								labelStr, sizeof *wl_p );
	}
	((wMenuListItem_p)wl_p)->data = data;
	((wMenuListItem_p)wl_p)->action = ml->action;
	if (index < 0 || index > ml->count)
		index = ml->count;
	for ( mi=(wMenuListItem_p)ml,count=0; count<index; mi=mi->right,count++);
	rc = InsertMenu( ml->mlparent->menu, origin+index,
					 MF_BYPOSITION|MF_STRING, wl_p->index, wl_p->labelStr );
	appendItem( mi, wl_p );
	ml->count++;
}


void wMenuListDelete(
		wMenuList_p ml,
		const char * labelStr )
{
	int origin, count;
	wMenuListItem_p wl_p;

	origin = getMlistOrigin(ml->mlparent, ml);
	for ( count=0,wl_p=ml->right; wl_p!=(wMenuListItem_p)ml; count++,wl_p=wl_p->right ) {
		if (wl_p->labelStr != NULL && strcmp( labelStr, wl_p->labelStr ) == 0) {
			/* delete item */
			mswUnregister( wl_p->index );
			RemoveMenu( ml->mlparent->menu, origin+count, MF_BYPOSITION );
			removeItem( wl_p );
			ml->count--;
			free( wl_p );
			return;
		}
	}
}


const char * wMenuListGet(
		wMenuList_p ml,
		int index,
		void ** data )
{
	int origin, count;
	wMenuListItem_p wl_p;

	if (index >= ml->count)
		return NULL;
	origin = getMlistOrigin(ml->mlparent, ml);
	for ( count=0,wl_p=ml->right; wl_p&&count<index; count++,wl_p=wl_p->right );
	if (wl_p==NULL)
		return NULL;
	if ( data )
		*data = wl_p->data;
	return wl_p->labelStr;
}


void wMenuListClear(
		wMenuList_p ml )
{
	int origin, count;
	wMenuListItem_p wl_p, wl_q;

	origin = getMlistOrigin(ml->mlparent, ml);
	for ( count=0,wl_p=ml->right; count<ml->count; count++,wl_p=wl_q ) {
		/* delete item */
		mswUnregister( wl_p->index );
		RemoveMenu( ml->mlparent->menu, origin, MF_BYPOSITION );
		wl_q = wl_p->right;
		free( wl_p );
	}
	ml->count = 0;
	ml->right = ml->left = (wMenuListItem_p)ml;
}



wMenuToggle_p wMenuToggleCreate(
		wMenu_p m, 
		const char * helpStr,
		const char * labelStr,
		long acclKey,
		wBool_t set,
		wMenuCallBack_p action,
		void * data )
{
	wMenuToggle_p mt;
	int rc;
	char label[80];
	char *cp;
	char ac;
	UINT vk;
	long modifier;

	mt = (wMenuToggle_p)createMenuItem( m, M_TOGGLE, helpStr, labelStr, sizeof *mt );
	/*setAcclKey( m->parent, m->menu, mt->menu_item, acclKey );*/
	mt->action = action;
	mt->data = data;
	mt->mparent = m;
	mt->enabled = TRUE;
	mt->parentMenu = m;
	strcpy( label, mt->labelStr );
	modifier = 0;

	if ( acclKey != 0 ) {
		DYNARR_APPEND( acclTable_t, acclTable_da, 10 );
		cp = label + strlen( label );
		*cp++ = '\t';
		if (acclKey & WCTL ) {
			strcpy( cp, _("Ctrl+") );
//			cp += 5;
			modifier |= WKEY_CTRL;
		}
		if (acclKey & WALT ) {
			strcpy( cp, _("Alt+") );
//			cp += 4;
			modifier |= WKEY_ALT;
		}
		if (acclKey & WSHIFT ) {
			strcpy( cp, _("Shift+") );
//			cp += 6;
			modifier |= WKEY_SHIFT;
		}
		cp = label + strlen( label );
		if( ((char)acclKey & 0xFF ) == ' ' ) {
			strcat( label, _("Space") );
		} else {
			*cp++ = toupper( (char)(acclKey & 0xFF) );
			*cp++ = '\0';
		}
		ac = (char)(acclKey & 0xFF);
		if (isalpha(ac)) {
			ac = tolower( ac );
		}
		vk = VkKeyScan( ac );
		if ( vk & 0xFF00 )
			modifier |= WKEY_SHIFT;
		acclTable(acclTable_da.cnt-1).acclKey = (modifier<<8) | (vk&0x00FF);
		acclTable(acclTable_da.cnt-1).mp = (wMenuPush_p)mt;
	}

	rc = AppendMenu( m->menu, MF_STRING, mt->index, label );
	wMenuToggleSet( mt, set );
	return mt;
}


wBool_t wMenuToggleGet(
		wMenuToggle_p mt )
{
	return (GetMenuState( mt->mparent->menu, mt->index, MF_BYCOMMAND ) & MF_CHECKED) != 0;
}


wBool_t wMenuToggleSet(
		wMenuToggle_p mt,
		wBool_t set )
{
	wBool_t rc;
	CheckMenuItem( mt->mparent->menu, mt->index, MF_BYCOMMAND|(set?MF_CHECKED:MF_UNCHECKED) );
	rc = (GetMenuState( mt->mparent->menu, mt->index, MF_BYCOMMAND ) & MF_CHECKED) != 0;
	return rc;
}

void wMenuToggleEnable(
		wMenuToggle_p mt,
		wBool_t enable )
{
	EnableMenuItem( mt->mparent->menu, mt->index,
		MF_BYCOMMAND|(enable?MF_ENABLED:(MF_DISABLED|MF_GRAYED)) );
	mt->enabled = enable;
}	  

/*
 *****************************************************************************
 *
 * Menu Create
 *
 *****************************************************************************
 */


void mswMenuMove(
		wMenu_p m,
		wWinPix_t x,
		wWinPix_t y )
{
	wControl_p b;
	b = (wControl_p)m->parent;
	if (b && b->hWnd)
		if (!SetWindowPos( b->hWnd, HWND_TOP, x, y,
				CW_USEDEFAULT, CW_USEDEFAULT,
				SWP_NOSIZE|SWP_NOZORDER))
				mswFail("mswMenuMove");
}


static void pushMenuButt(
		void * data )
{
	wMenu_p m = (wMenu_p)data;
	RECT rect;
	mswAllowBalloonHelp = FALSE;
	GetWindowRect( m->hWnd, &rect );
	TrackPopupMenu( m->menu, TPM_LEFTALIGN, rect.left, rect.bottom,
						0, ((wControl_p)(m->parent))->hWnd, NULL );
}


wMenu_p wMenuCreate(
		wWin_p	parent,
		wWinPix_t	x,
		wWinPix_t	y,
		const char	* helpStr,
		const char	* labelStr,
		long	option )
{
	wMenu_p m;
	wControl_p b;
	long buttOption = 0;
	const char * label = labelStr;

	if (option & BM_ICON) {
		buttOption = BO_ICON;
		label = "ICON";
	}
	m = (wMenu_p)createMenuItem( NULL, M_MENU, helpStr, label, sizeof *m );
	m->button = wButtonCreate( parent, x, y, helpStr, labelStr,
						buttOption, 0, pushMenuButt, (void*)m );
	b = (wControl_p)m->button;
	m->parent = b->parent;
	m->x = b->x;
	m->y = b->y;
	m->w = b->w;
	m->h = b->h;
	m->hWnd = b->hWnd;
	m->helpStr = b->helpStr;

	m->menu = CreatePopupMenu();
	m->mmtype = MM_BUTT;
	m->first = m->last = NULL;

	return m;
}


wMenu_p wMenuBarAdd(
		wWin_p w,
		const char * helpStr,
		const char * labelStr )
{
	HMENU menu;
	wMenu_p m;
	int rc;

	menu = GetMenu( ((wControl_p)w)->hWnd );
	if (menu == (HMENU)0) {
		menu = CreateMenu();
		SetMenu( ((wControl_p)w)->hWnd, menu );
	}

	m = (wMenu_p)createMenuItem( NULL, M_MENU, helpStr, labelStr, sizeof *m );
	m->menu = CreateMenu();
	m->parent = w;
	m->mmtype = MM_BAR;
	m->first = m->last = NULL;

	rc = AppendMenu( menu, MF_STRING|MF_POPUP|MF_ENABLED, (UINT_PTR)(m->menu), labelStr );

	DrawMenuBar( ((wControl_p)w)->hWnd );
	return m;
}



wMenu_p wMenuPopupCreate(
		wWin_p w,
		const char * labelStr )
{
	wMenu_p m;
	long buttOption = 0;
	const char * label = labelStr;

	m = (wMenu_p)createMenuItem( NULL, M_MENU, NULL, label, sizeof *m );
	m->button = NULL;
	m->parent = w;
	m->x = 0;
	m->y = 0;
	m->w = 0;
	m->h = 0;
	m->hWnd = ((wControl_p)w)->hWnd;
	m->helpStr = NULL;

	m->menu = CreatePopupMenu();
	m->mmtype = MM_POPUP;
	m->first = m->last = NULL;

	return m;
}


void wMenuPopupShow( wMenu_p mp )
{
	POINT pt;
	GetCursorPos( &pt );
	TrackPopupMenu( mp->menu, TPM_LEFTALIGN, pt.x, pt.y, 0, mp->hWnd, NULL );
}
		
/*-----------------------------------------------------------------*/

void wMenuSetTraceCallBack(
		wMenu_p m,
		wMenuTraceCallBack_p func,
		void * data )
{
	m->traceFunc = func;
	m->traceData = data;
}

wBool_t wMenuAction(
		wMenu_p m,
		const char * label )
{
	wMenuItem_p mi;
	wMenuToggle_p mt;
	wBool_t set;
	for ( mi = m->first; mi != NULL; mi = (wMenuItem_p)mi->mnext ) {
		if ( mi->labelStr != NULL && strcmp( mi->labelStr, label ) == 0 ) {
			switch( mi->mtype ) {
			case M_SEPARATOR:
				break;
			case M_PUSH:
				if ( ((wMenuPush_p)mi)->enabled == FALSE )
					wBeep();
				else
					((wMenuPush_p)mi)->action( ((wMenuPush_p)mi)->data );
				break;
			case M_TOGGLE:
				mt = (wMenuToggle_p)mi;
				if ( mt->enabled == FALSE ) {
					wBeep();
				} else {
					set = wMenuToggleGet( mt );
					wMenuToggleSet( mt, !set );
					mt->action( mt->data );
				}
				break;
			case M_MENU:
				break;
			case M_LIST:
				break;
			default:
				fprintf(stderr, "Oops: wMenuAction\n");
			}
			return TRUE;
		}
	}
	return FALSE;
}