
--proc/controls/menu: standard menu control
--Written by Cosmin Apreutesei. Public Domain.

setfenv(1, require'winapi')
require'winapi.winuser'

ffi.cdef[[
HMENU CreateMenu(void);
HMENU CreatePopupMenu(void);
BOOL DestroyMenu(HMENU hMenu);

HMENU GetMenu(HWND hWnd);
BOOL SetMenu(HWND hWnd, HMENU hMenu);
BOOL DrawMenuBar(HWND hWnd);

typedef struct tagMENUITEMINFOW
{
    UINT     cbSize;
    UINT     fMask;
    UINT     fType;
    UINT     fState;
    UINT     wID;
    HMENU    hSubMenu;
    HBITMAP  hbmpChecked;
    HBITMAP  hbmpUnchecked;
    ULONG_PTR dwItemData;
    LPWSTR   dwTypeData;
    UINT     cch;
    HBITMAP  hbmpItem;
}   MENUITEMINFOW,  *LPMENUITEMINFOW;

typedef MENUITEMINFOW const  *LPCMENUITEMINFOW;

BOOL InsertMenuItemW(
     HMENU hmenu,
     UINT item,
     BOOL fByPosition,
     LPCMENUITEMINFOW lpmi);

BOOL SetMenuItemInfoW(
     HMENU hmenu,
     UINT item,
     BOOL fByPositon,
     LPCMENUITEMINFOW lpmii);

BOOL GetMenuItemInfoW(
     HMENU hmenu,
     UINT item,
     BOOL fByPosition,
     LPMENUITEMINFOW lpmii);

DWORD CheckMenuItem(
      HMENU hmenu,
      UINT uIDCheckItem,
      UINT uCheck
);

UINT GetMenuState(
     HMENU hMenu,
     UINT uId,
     UINT uFlags
);

BOOL EnableMenuItem(
     HMENU hMenu,
     UINT uIDEnableItem,
     UINT uEnable
);

BOOL RemoveMenu(
     HMENU hMenu,
     UINT uPosition,
     UINT uFlags
);

HMENU GetSubMenu(
     HMENU hMenu,
     int nPos
);

int GetMenuItemCount(HMENU hMenu);

typedef struct tagTPMPARAMS
{
    UINT    cbSize;
    RECT    exclude_rect;
}   TPMPARAMS;
typedef TPMPARAMS  *LPTPMPARAMS;

BOOL TrackPopupMenuEx(HMENU, UINT, int, int, HWND, LPTPMPARAMS);
]]

function CreateMenuBar()
	return own(checkh(C.CreateMenu()), DestroyMenu)
end

function CreateMenu()
	return own(checkh(C.CreatePopupMenu()), DestroyMenu)
end

function DestroyMenu(menu)
	checknz(C.DestroyMenu(menu))
	disown(menu)
end

function GetMenu(hwnd)
	return ptr(C.GetMenu(hwnd))
end

function SetMenu(hwnd, menu)
	local oldmenu = own(GetMenu(hwnd), DestroyMenu)
	checknz(C.SetMenu(hwnd, menu))
	disown(menu)
	return oldmenu
end

function DrawMenuBar(hwnd)
	checknz(C.DrawMenuBar(hwnd))
end

MF_INSERT            = 0x00000000
MF_CHANGE            = 0x00000080
MF_APPEND            = 0x00000100
MF_DELETE            = 0x00000200
MF_REMOVE            = 0x00001000
MF_BYCOMMAND         = 0x00000000
MF_BYPOSITION        = 0x00000400
MF_SEPARATOR         = 0x00000800
MF_ENABLED           = 0x00000000
MF_GRAYED            = 0x00000001
MF_DISABLED          = 0x00000002
MF_UNCHECKED         = 0x00000000
MF_CHECKED           = 0x00000008
MF_USECHECKBITMAPS   = 0x00000200
MF_STRING            = 0x00000000
MF_BITMAP            = 0x00000004
MF_OWNERDRAW         = 0x00000100
MF_POPUP             = 0x00000010
MF_MENUBARBREAK      = 0x00000020
MF_MENUBREAK         = 0x00000040
MF_UNHILITE          = 0x00000000
MF_HILITE            = 0x00000080
MF_DEFAULT           = 0x00001000
MF_SYSMENU           = 0x00002000
MF_HELP              = 0x00004000
MF_RIGHTJUSTIFY      = 0x00004000
MF_MOUSESELECT       = 0x00008000

MIIM_STATE       = 0x00000001
MIIM_ID          = 0x00000002
MIIM_SUBMENU     = 0x00000004
MIIM_CHECKMARKS  = 0x00000008
MIIM_TYPE        = 0x00000010
MIIM_DATA        = 0x00000020
MIIM_STRING      = 0x00000040
MIIM_BITMAP      = 0x00000080
MIIM_FTYPE       = 0x00000100

MFT_STRING           = MF_STRING
MFT_BITMAP           = MF_BITMAP
MFT_MENUBARBREAK     = MF_MENUBARBREAK
MFT_MENUBREAK        = MF_MENUBREAK
MFT_OWNERDRAW        = MF_OWNERDRAW
MFT_RADIOCHECK       = 0x00000200
MFT_SEPARATOR        = MF_SEPARATOR
MFT_RIGHTORDER       = 0x00002000
MFT_RIGHTJUSTIFY     = MF_RIGHTJUSTIFY

MFS_GRAYED           = 0x00000003
MFS_DISABLED         = MFS_GRAYED
MFS_CHECKED          = MF_CHECKED
MFS_HILITE           = MF_HILITE
MFS_ENABLED          = MF_ENABLED
MFS_UNCHECKED        = MF_UNCHECKED
MFS_UNHILITE         = MF_UNHILITE
MFS_DEFAULT          = MF_DEFAULT

HBMMENU_CALLBACK            = -1
HBMMENU_SYSTEM              = ffi.cast('HBITMAP', 1)
HBMMENU_MBAR_RESTORE        = ffi.cast('HBITMAP', 2)
HBMMENU_MBAR_MINIMIZE       = ffi.cast('HBITMAP', 3)
HBMMENU_MBAR_CLOSE          = ffi.cast('HBITMAP', 5)
HBMMENU_MBAR_CLOSE_D        = ffi.cast('HBITMAP', 6)
HBMMENU_MBAR_MINIMIZE_D     = ffi.cast('HBITMAP', 7)
HBMMENU_POPUP_CLOSE         = ffi.cast('HBITMAP', 8)
HBMMENU_POPUP_RESTORE       = ffi.cast('HBITMAP', 9)
HBMMENU_POPUP_MAXIMIZE      = ffi.cast('HBITMAP', 10)
HBMMENU_POPUP_MINIMIZE      = ffi.cast('HBITMAP', 11)

MENUITEMINFO = struct{
	ctype = 'MENUITEMINFOW', size = 'cbSize', mask = 'fMask',
	fields = mfields{
		'id',             'wID',               MIIM_ID, pass, pass,
		'text',           'dwTypeData',        MIIM_STRING, wcs, mbs,
		'submenu',        'hSubMenu',          MIIM_SUBMENU, ptr, ptr,
		'bitmap',         'hbmpItem',          MIIM_BITMAP, ptr, ptr,
		'checked_bitmap', 'hbmpChecked',       MIIM_CHECKMARKS, ptr, ptr,
		'unchecked_bitmap','hbmpUnchecked',    MIIM_CHECKMARKS, ptr, ptr,
		'type',           'fType',             MIIM_FTYPE, flags, pass,
		'state',          'fState',            MIIM_STATE, flags, pass,
	},
}

function GetSubMenu(menu, i)
	return ptr(C.GetSubMenu(menu, countfrom0(i)))
end

function InsertMenuItem(menu, i, info, byposition)
	if not info then i,info = nil,i end --i is optional
	checknz(C.InsertMenuItemW(menu, countfrom0(i), byposition, MENUITEMINFO(info)))
	disown(info.submenu)
end

function SetMenuItemInfo(menu, i, info, byposition)
	local oldsubmenu = GetSubMenu(menu, i)
	checknz(C.SetMenuItemInfoW(menu, countfrom0(i), byposition, MENUITEMINFO(info)))
	disown(info.submenu)
	own(oldsubmenu, DestroyMenu)
end

function GetMenuItemInfo(menu, i, byposition, info)
	info = MENUITEMINFO:setmask(info)
	checknz(C.GetMenuItemInfoW(menu, countfrom0(i), byposition, info))
	return info
end

function RemoveMenuItem(menu, i, byposition)
	local oldsubmenu = GetSubMenu(menu, i)
	checknz(C.RemoveMenu(menu, countfrom0(i), byposition and MF_BYPOSITION or MF_BYCOMMAND))
	return own(oldsubmenu, DestroyMenu)
end

function GetMenuItemCount(menu)
	return checkpoz(C.GetMenuItemCount(menu))
end

TPM_LEFTBUTTON      = 0x0000
TPM_RIGHTBUTTON     = 0x0002
TPM_LEFTALIGN       = 0x0000
TPM_CENTERALIGN     = 0x0004
TPM_RIGHTALIGN      = 0x0008
TPM_TOPALIGN        = 0x0000
TPM_VCENTERALIGN    = 0x0010
TPM_BOTTOMALIGN     = 0x0020
TPM_HORIZONTAL      = 0x0000 -- horz alignment matters more
TPM_VERTICAL        = 0x0040 -- vert alignment matters more
TPM_NONOTIFY        = 0x0080 -- don't send any notification msgs
TPM_RETURNCMD       = 0x0100
TPM_RECURSE         = 0x0001
TPM_HORPOSANIMATION = 0x0400
TPM_HORNEGANIMATION = 0x0800
TPM_VERPOSANIMATION = 0x1000
TPM_VERNEGANIMATION = 0x2000
TPM_NOANIMATION     = 0x4000
TPM_LAYOUTRTL       = 0x8000
TPM_WORKAREA        = 0x10000

TPMPARAMS = struct{
	ctype = 'TPMPARAMS', size = 'cbSize', fields = {}
}

function TrackPopupMenu(menu, hwnd, x, y, TPM, tpmp)
	if tpmp then tpmp = TPMPARAMS(tpmp) end
	return C.TrackPopupMenuEx(menu, flags(TPM), x, y, hwnd, tpmp)
end

--get/set menu info

ffi.cdef[[
typedef struct tagMENUINFO
{
    DWORD   cbSize;
    DWORD   fMask;
    DWORD   dwStyle;
    UINT    cyMax;
    HBRUSH  hbrBack;
    DWORD   dwContextHelpID;
    ULONG_PTR dwMenuData;
}   MENUINFO,  *LPMENUINFO;
typedef MENUINFO const  *LPCMENUINFO;

BOOL GetMenuInfo(HMENU, LPMENUINFO);
BOOL SetMenuInfo(HMENU, LPCMENUINFO);
]]

MNS_NOCHECK         = 0x80000000
MNS_MODELESS        = 0x40000000
MNS_DRAGDROP        = 0x20000000
MNS_AUTODISMISS     = 0x10000000
MNS_NOTIFYBYPOS     = 0x08000000
MNS_CHECKORBMP      = 0x04000000

MIM_MAXHEIGHT               = 0x00000001
MIM_BACKGROUND              = 0x00000002
MIM_HELPID                  = 0x00000004
MIM_MENUDATA                = 0x00000008
MIM_STYLE                   = 0x00000010
MIM_APPLYTOSUBMENUS         = 0x80000000

MENUINFO = struct{
	ctype = 'MENUINFO', size = 'cbSize', mask = 'fMask',
	fields = mfields{
		'max_height', 'cyMax', MIM_MAXHEIGHT, pass, pass,
		'background', 'hbrBack', MIM_BACKGROUND, pass, ptr,
		'help_id', 'dwContextHelpID', MIM_HELPID, pass, pass,
		'user_data', 'dwMenuData', MIM_MENUDATA, pass, pass,
		'style', 'dwStyle', MIM_STYLE, flags, pass, --MNS_*
		'apply_to_submenus', '', MIM_APPLYTOSUBMENUS, pass, pass,
	},
}

function GetMenuInfo(menu, info)
	info = MENUINFO:setmask(info)
	checknz(C.GetMenuInfo(menu, info))
	return info
end

function SetMenuInfo(menu, info)
	checknz(C.SetMenuInfo(menu, MENUINFO(info)))
end

function CheckMenuItem(menu, i, byposition, checked)
	return checkpoz(C.CheckMenuItem(menu, countfrom0(i),
		bit.bor(byposition and MF_BYPOSITION or MF_BYCOMMAND,
					checked and MF_CHECKED or MF_UNCHECKED))) == MF_CHECKED
end

function GetMenuState(menu, i, byposition) --there's no SetMenuState.
	return checkpoz(C.GetMenuState(menu, countfrom0(i), byposition and MF_BYPOSITION or MF_BYCOMMAND))
end

function EnableMenuItem(menu, i, byposition, enabled)
	return checkpoz(C.EnableMenuItem(menu, countfrom0(i),
		bit.bor(byposition and MF_BYPOSITION or MF_BYCOMMAND,
					enabled == true and MFS_ENABLED or
					enabled == false and MFS_DISABLED or
					flags(enabled))))
end

