/** \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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define OEMRESOURCE

#include <windows.h>
#include <string.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;
}
