Lua: add menu functions, sample http://pastebin.com/VD9quJRS

This commit is contained in:
gocha 2010-11-06 14:59:44 +00:00
parent 2e7f6bd52a
commit e7dc68e5ce
4 changed files with 470 additions and 3 deletions

View File

@ -13,9 +13,10 @@
#include "saves.h" #include "saves.h"
#include "emufile.h" #include "emufile.h"
#if defined(WIN32) && !defined(WXPORT) #if defined(WIN32) && !defined(WXPORT)
#include <windows.h>
#include "main.h" #include "main.h"
#include "windows.h"
#include "video.h" #include "video.h"
#include "resource.h"
#endif #endif
#ifdef WIN32 #ifdef WIN32
#include <direct.h> #include <direct.h>
@ -60,6 +61,27 @@ struct LuaGUIData
int xMin, yMin, xMax, yMax; int xMin, yMin, xMax, yMax;
}; };
struct LuaSubMenuData
{
PlatformMenu menu;
PlatformMenu subMenu;
PlatformMenuItem menuItem;
LuaSubMenuData(PlatformMenu _menu, PlatformMenu _subMenu, PlatformMenuItem _menuItem)
{
menu = _menu;
subMenu = _subMenu;
menuItem = _menuItem;
}
};
struct LuaMenuData
{
std::vector<LuaSubMenuData> subMenuData;
std::map<PlatformMenuItem, PlatformMenu> menuItemMap;
};
static const char* menuCallbackIDString = "menuhandlers";
struct LuaContextInfo { struct LuaContextInfo {
lua_State* L; // the Lua state lua_State* L; // the Lua state
bool started; // script has been started and hasn't yet been terminated, although it may not be currently running bool started; // script has been started and hasn't yet been terminated, although it may not be currently running
@ -88,6 +110,7 @@ struct LuaContextInfo {
LuaSaveData newDefaultData; // data about the default state of persisted global variables, which we save on script exit so we can detect when the default value has changed to make it easier to reset persisted variables LuaSaveData newDefaultData; // data about the default state of persisted global variables, which we save on script exit so we can detect when the default value has changed to make it easier to reset persisted variables
unsigned int numMemHooks; // number of registered memory functions (1 per hooked byte) unsigned int numMemHooks; // number of registered memory functions (1 per hooked byte)
LuaGUIData guiData; LuaGUIData guiData;
LuaMenuData menuData;
// callbacks into the lua window... these don't need to exist per context the way I'm using them, but whatever // callbacks into the lua window... these don't need to exist per context the way I'm using them, but whatever
void(*print)(int uid, const char* str); void(*print)(int uid, const char* str);
void(*onstart)(int uid); void(*onstart)(int uid);
@ -143,6 +166,7 @@ static const char* luaCallIDStrings [] =
"CALL_BEFORESAVE", "CALL_BEFORESAVE",
"CALL_AFTERLOAD", "CALL_AFTERLOAD",
"CALL_ONSTART", "CALL_ONSTART",
"CALL_ONINITMENU",
"CALL_HOTKEY_1", "CALL_HOTKEY_1",
"CALL_HOTKEY_2", "CALL_HOTKEY_2",
@ -3406,6 +3430,332 @@ DEFINE_LUA_FUNCTION(emu_reset, "")
return 0; return 0;
} }
static bool IsLuaMenuItem(PlatformMenuItem menuItem)
{
#if defined(WIN32) && !defined(WXPORT)
return (menuItem >= IDC_LUAMENU_RESERVE_START && menuItem <= IDC_LUAMENU_RESERVE_END);
#else
return false;
#endif
}
static bool SearchFreeMenuItem(PlatformMenu menu, PlatformMenuItem& menuItem)
{
#if defined(WIN32) && !defined(WXPORT)
for (UINT menuItemId = IDC_LUAMENU_RESERVE_START; menuItemId <= IDC_LUAMENU_RESERVE_END; menuItemId++)
{
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_ID;
if (!GetMenuItemInfo(menu, menuItemId, FALSE, &mii) &&
GetLastError() == ERROR_MENU_ITEM_NOT_FOUND)
{
menuItem = menuItemId;
return true;
}
}
return false;
#else
return false;
#endif
}
static PlatformMenu AddSubMenu(PlatformMenu topMenu, PlatformMenu menu, const char* menuName)
{
#if defined(WIN32) && !defined(WXPORT)
LuaContextInfo& info = GetCurrentInfo();
MENUITEMINFO mii;
// search existing submenu
for (int index = 0; index < GetMenuItemCount(menu); index++)
{
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_ID | MIIM_SUBMENU | MIIM_STRING;
const UINT bufferSize = 128;
TCHAR menuItemText[bufferSize];
mii.dwTypeData = menuItemText;
mii.cch = bufferSize;
GetMenuItemInfo(menu, index, TRUE, &mii);
// if exists, return it
if (mii.hSubMenu != NULL && lstrcmp(menuName, mii.dwTypeData) == 0)
{
if (IsLuaMenuItem(mii.wID))
{
info.menuData.subMenuData.push_back(LuaSubMenuData(menu, mii.hSubMenu, mii.wID));
}
return mii.hSubMenu;
}
}
// add new submenu
UINT subMenuId;
if (!SearchFreeMenuItem(topMenu, subMenuId))
{
return NULL;
}
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_SUBMENU;
mii.fType = MFT_STRING;
mii.fState = MFS_ENABLED;
mii.wID = subMenuId;
mii.hSubMenu = CreatePopupMenu();
mii.dwTypeData = (char*) menuName;
if (!InsertMenuItem(menu, (UINT)-1, TRUE, &mii))
{
if (mii.hSubMenu != NULL)
{
DestroyMenu(mii.hSubMenu);
}
return NULL;
}
info.menuData.subMenuData.push_back(LuaSubMenuData(menu, mii.hSubMenu, subMenuId));
return mii.hSubMenu;
#else
return 0;
#endif
}
bool AddMenuEntries(PlatformMenu topMenu, PlatformMenu menu)
{
#if defined(WIN32) && !defined(WXPORT)
LuaContextInfo& info = GetCurrentInfo();
lua_State* L = info.L;
luaL_checktype(L, -1, LUA_TTABLE);
luaL_checkstack(L, 6, "");
// for index = 1, #menuEntries
unsigned int count = lua_objlen(L, -1);
for (unsigned int index = 1; index <= count; index++)
{
// switch(type(menuEntries[index]))
lua_rawgeti(L, -1, index);
if (lua_isnil(L, -1))
{
UINT menuItem;
if (!SearchFreeMenuItem(topMenu, menuItem))
{
luaL_error(L, "too many menu items");
return false;
}
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_ID | MIIM_FTYPE;
mii.wID = menuItem;
mii.fType = MFT_SEPARATOR;
if (!InsertMenuItem(menu, menuItem, FALSE, &mii))
{
luaL_error(L, "menu item addition failed");
return false;
}
info.menuData.menuItemMap.insert(map<PlatformMenuItem, PlatformMenu>::value_type(menuItem, menu));
lua_pop(L, 1);
}
else if (lua_istable(L, -1))
{
lua_rawgeti(L, -1, 1);
const char* menuName = luaL_checkstring(L, -1);
lua_insert(L, -2);
lua_rawgeti(L, -1, 2);
if (lua_isfunction(L, -1))
{
UINT menuItem;
if (!SearchFreeMenuItem(topMenu, menuItem))
{
luaL_error(L, "too many menu items");
return false;
}
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_ID | MIIM_STRING;
mii.wID = menuItem;
mii.dwTypeData = (char*) menuName;
if (!InsertMenuItem(menu, menuItem, FALSE, &mii))
{
luaL_error(L, "menu item addition failed");
return false;
}
info.menuData.menuItemMap.insert(map<PlatformMenuItem, PlatformMenu>::value_type(menuItem, menu));
lua_getfield(L, LUA_REGISTRYINDEX, menuCallbackIDString);
lua_insert(L, -2);
lua_rawseti(L, -2, menuItem);
lua_pop(L, 3);
}
else if (lua_istable(L, -1))
{
HMENU subMenu = AddSubMenu(topMenu, menu, menuName);
if (subMenu == NULL)
{
luaL_error(L, "menu item addition failed");
return false;
}
if (!AddMenuEntries(topMenu, subMenu))
{
return false;
}
lua_pop(L, 3);
}
else
{
luaL_typerror(L, -1, "function or table");
return false;
}
}
else
{
luaL_typerror(L, -1, "nil or table");
return false;
}
}
return true;
#else
return false;
#endif
}
DEFINE_LUA_FUNCTION(emu_addmenu, "menuName, menuEntries")
{
#if defined(WIN32) && !defined(WXPORT)
int nargs = lua_gettop(L);
if (nargs > 1 && !lua_isnil(L, 1))
{
const char* menuName = luaL_checkstring(L, 1);
luaL_checktype(L, 2, LUA_TTABLE);
lua_settop(L, 2); // drop redundant args
HMENU menu = mainMenu;
HMENU subMenu = AddSubMenu(menu, menu, menuName);
if (subMenu != NULL)
{
AddMenuEntries(menu, subMenu);
DrawMenuBar(MainWindow->getHWnd());
}
else
{
luaL_error(L, "menu item addition failed");
}
}
else
{
//HMENU menu = NULL; // TODO: set popup (right-click) menu
//int index = (nargs > 1) ? 2 : 1;
//luaL_checktype(L, index, LUA_TTABLE);
//lua_settop(L, index); // drop redundant args
//AddMenuEntries(menu, menu);
}
#endif
return 0;
}
DEFINE_LUA_FUNCTION(emu_setmenuiteminfo, "menuItem, infoTable")
{
luaL_checktype(L, 1, LUA_TFUNCTION);
luaL_checktype(L, 2, LUA_TTABLE);
#if defined(WIN32) && !defined(WXPORT)
LuaContextInfo& info = GetCurrentInfo();
map<PlatformMenuItem, PlatformMenu>::iterator it = info.menuData.menuItemMap.begin();
while(it != info.menuData.menuItemMap.end())
{
HMENU menu = (*it).second;
UINT menuItem = (*it).first;
lua_getfield(L, LUA_REGISTRYINDEX, menuCallbackIDString);
lua_rawgeti(L, -1, menuItem);
if (lua_rawequal(L, 1, -1) != 0)
{
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_STATE | MIIM_STRING;
GetMenuItemInfo(menu, menuItem, FALSE, &mii);
mii.fMask = 0;
lua_getfield(L, 2, "enabled");
if (lua_isboolean(L, -1))
{
mii.fMask |= MIIM_STATE;
if (lua_toboolean(L, -1) != 0)
{
mii.fState &= ~MFS_DISABLED;
}
else
{
mii.fState |= MFS_DISABLED;
}
}
else if (!lua_isnil(L, -1))
{
luaL_where(L, 0);
luaL_error(L, "%s bad argument \"enabled\" (boolean expected, got %s)",
luaL_optstring(L, -1, ""), luaL_typename(L, -2));
}
lua_pop(L, 1);
lua_getfield(L, 2, "checked");
if (lua_isboolean(L, -1))
{
mii.fMask |= MIIM_STATE;
if (lua_toboolean(L, -1) != 0)
{
mii.fState |= MFS_CHECKED;
}
else
{
mii.fState &= ~MFS_CHECKED;
}
}
else if (!lua_isnil(L, -1))
{
luaL_where(L, 0);
luaL_error(L, "%s bad argument \"checked\" (boolean expected, got %s)",
luaL_optstring(L, -1, ""), luaL_typename(L, -2));
}
lua_pop(L, 1);
lua_getfield(L, 2, "name");
if (lua_isstring(L, -1))
{
mii.fMask |= MIIM_STRING;
mii.dwTypeData = (LPSTR) lua_tostring(L, -1);
}
else if (!lua_isnil(L, -1))
{
luaL_where(L, 0);
luaL_error(L, "%s bad argument \"name\" (string expected, got %s)",
luaL_optstring(L, -1, ""), luaL_typename(L, -2));
}
SetMenuItemInfo(menu, menuItem, FALSE, &mii);
lua_pop(L, 1);
}
lua_pop(L, 1);
it++;
}
#endif
return 0;
}
DEFINE_LUA_FUNCTION(emu_registermenustart, "func")
{
if (!lua_isnil(L,1))
luaL_checktype(L, 1, LUA_TFUNCTION);
lua_settop(L,1);
lua_getfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_ONINITMENU]);
lua_insert(L,1);
lua_setfield(L, LUA_REGISTRYINDEX, luaCallIDStrings[LUACALL_ONINITMENU]);
StopScriptIfFinished(luaStateToUIDMap[L->l_G->mainthread]);
return 1;
}
// TODO // TODO
/* /*
DEFINE_LUA_FUNCTION(emu_loadrom, "filename") DEFINE_LUA_FUNCTION(emu_loadrom, "filename")
@ -4152,6 +4502,9 @@ static const struct luaL_reg emulib [] =
{"openscript", emu_openscript}, {"openscript", emu_openscript},
// {"loadrom", emu_loadrom}, // {"loadrom", emu_loadrom},
{"reset", emu_reset}, {"reset", emu_reset},
{"addmenu", emu_addmenu},
{"setmenuiteminfo", emu_setmenuiteminfo},
{"registermenustart", emu_registermenustart},
// alternative names // alternative names
// {"openrom", emu_loadrom}, // {"openrom", emu_loadrom},
{NULL, NULL} {NULL, NULL}
@ -4539,6 +4892,10 @@ void registerLibs(lua_State* L)
lua_setfield(L, LUA_REGISTRYINDEX, luaMemHookTypeStrings[i]); lua_setfield(L, LUA_REGISTRYINDEX, luaMemHookTypeStrings[i]);
} }
// push an array for menu handlers
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, menuCallbackIDString);
// register type // register type
luaL_newmetatable(L, "EMUFILE_MEMORY*"); luaL_newmetatable(L, "EMUFILE_MEMORY*");
lua_pushcfunction(L, gcEMUFILE_MEMORY); lua_pushcfunction(L, gcEMUFILE_MEMORY);
@ -4699,7 +5056,7 @@ void StopScriptIfFinished(int uid, bool justReturned)
// because it may have registered a function that it expects to keep getting called // because it may have registered a function that it expects to keep getting called
// so check if it has any registered functions and stop the script only if it doesn't // so check if it has any registered functions and stop the script only if it doesn't
bool keepAlive = (info.numMemHooks != 0); bool keepAlive = (info.numMemHooks != 0 || !info.menuData.menuItemMap.empty());
for(int calltype = 0; calltype < LUACALL_COUNT && !keepAlive; calltype++) for(int calltype = 0; calltype < LUACALL_COUNT && !keepAlive; calltype++)
{ {
lua_State* L = info.L; lua_State* L = info.L;
@ -4943,6 +5300,43 @@ void StopLuaScript(int uid)
info.numMemHooks = 0; info.numMemHooks = 0;
for(int i = 0; i < LUAMEMHOOK_COUNT; i++) for(int i = 0; i < LUAMEMHOOK_COUNT; i++)
CalculateMemHookRegions((LuaMemHookType)i); CalculateMemHookRegions((LuaMemHookType)i);
#if defined(WIN32) && !defined(WXPORT)
// remove items
map<PlatformMenuItem, PlatformMenu>::iterator it = info.menuData.menuItemMap.begin();
while(it != info.menuData.menuItemMap.end())
{
HMENU menu = (*it).second;
UINT menuItem = (*it).first;
DeleteMenu(menu, menuItem, MF_BYCOMMAND);
it++;
}
info.menuData.menuItemMap.clear();
// remove submenus
vector<LuaSubMenuData>::reverse_iterator rit = info.menuData.subMenuData.rbegin();
while(rit != info.menuData.subMenuData.rend())
{
HMENU menu = (*rit).menu;
UINT menuItem = (*rit).menuItem;
MENUITEMINFO mii;
ZeroMemory(&mii, sizeof(MENUITEMINFO));
mii.cbSize = sizeof(MENUITEMINFO);
mii.fMask = MIIM_SUBMENU;
GetMenuItemInfo(menu, menuItem, FALSE, &mii);
HMENU subMenu = mii.hSubMenu;
// delete if it's empty
if (GetMenuItemCount(subMenu) == 0)
{
DeleteMenu(menu, menuItem, MF_BYCOMMAND);
}
rit++;
}
info.menuData.subMenuData.clear();
DrawMenuBar(MainWindow->getHWnd());
#endif
} }
RefreshScriptStartedStatus(); RefreshScriptStartedStatus();
} }
@ -5053,6 +5447,50 @@ void CallRegisteredLuaMemHook_LuaMatch(unsigned int address, int size, unsigned
} }
void CallRegisteredLuaMenuHandlers(PlatformMenuItem menuItem)
{
std::map<int, LuaContextInfo*>::iterator iter = luaContextInfo.begin();
std::map<int, LuaContextInfo*>::iterator end = luaContextInfo.end();
while(iter != end)
{
LuaContextInfo& info = *iter->second;
lua_State* L = info.L;
if(L && !info.panic)
{
#ifdef USE_INFO_STACK
infoStack.insert(infoStack.begin(), &info);
struct Scope { ~Scope(){ infoStack.erase(infoStack.begin()); } } scope;
#endif
int top = lua_gettop(L);
lua_getfield(L, LUA_REGISTRYINDEX, menuCallbackIDString);
lua_rawgeti(L, -1, menuItem);
if (lua_isfunction(L, -1))
{
bool wasRunning = info.running;
info.running = true;
RefreshScriptSpeedStatus();
int errorcode = lua_pcall(L, 0, 0, 0);
info.running = wasRunning;
RefreshScriptSpeedStatus();
if (errorcode)
{
int uid = iter->first;
HandleCallbackError(L,info,uid,true);
}
break;
}
else
{
lua_pop(L,1);
}
if(!info.crashed)
lua_settop(L, top);
}
++iter;
}
}
bool AnyLuaActive() bool AnyLuaActive()
{ {
std::map<int, LuaContextInfo*>::iterator iter = luaContextInfo.begin(); std::map<int, LuaContextInfo*>::iterator iter = luaContextInfo.begin();

View File

@ -3,6 +3,12 @@
#include "types.h" #include "types.h"
#if defined(WIN32) && !defined(WXPORT)
#include <winsock2.h>
#include <windows.h>
#include "resource.h"
#endif
void OpenLuaContext(int uid, void(*print)(int uid, const char* str) = 0, void(*onstart)(int uid) = 0, void(*onstop)(int uid, bool statusOK) = 0); void OpenLuaContext(int uid, void(*print)(int uid, const char* str) = 0, void(*onstart)(int uid) = 0, void(*onstop)(int uid, bool statusOK) = 0);
void RunLuaScriptFile(int uid, const char* filename); void RunLuaScriptFile(int uid, const char* filename);
void StopLuaScript(int uid); void StopLuaScript(int uid);
@ -19,6 +25,7 @@ enum LuaCallID
LUACALL_BEFORESAVE, LUACALL_BEFORESAVE,
LUACALL_AFTERLOAD, LUACALL_AFTERLOAD,
LUACALL_ONSTART, LUACALL_ONSTART,
LUACALL_ONINITMENU,
LUACALL_SCRIPT_HOTKEY_1, LUACALL_SCRIPT_HOTKEY_1,
LUACALL_SCRIPT_HOTKEY_2, LUACALL_SCRIPT_HOTKEY_2,
@ -86,6 +93,18 @@ private:
void CallRegisteredLuaSaveFunctions(int savestateNumber, LuaSaveData& saveData); void CallRegisteredLuaSaveFunctions(int savestateNumber, LuaSaveData& saveData);
void CallRegisteredLuaLoadFunctions(int savestateNumber, const LuaSaveData& saveData); void CallRegisteredLuaLoadFunctions(int savestateNumber, const LuaSaveData& saveData);
#if defined(WIN32) && !defined(WXPORT)
typedef HMENU PlatformMenu; // hMenu
typedef UINT PlatformMenuItem; // menuId
#define MAX_MENU_COUNT (IDC_LUAMENU_RESERVE_END - IDC_LUAMENU_RESERVE_START + 1)
#else
// TODO: define appropriate types for menu
typedef void* PlatformMenu;
typedef u32 PlatformMenuItem;
#define MAX_MENU_COUNT 0
#endif
void CallRegisteredLuaMenuHandlers(PlatformMenuItem menuItem);
void StopAllLuaScripts(); void StopAllLuaScripts();
void RestartAllLuaScripts(); void RestartAllLuaScripts();
void EnableStopAllLuaScripts(bool enable); void EnableStopAllLuaScripts(bool enable);

View File

@ -4050,6 +4050,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM
UpdateHotkeyAssignments(); //Add current hotkey mappings to menu item names UpdateHotkeyAssignments(); //Add current hotkey mappings to menu item names
CallRegisteredLuaFunctions(LUACALL_ONINITMENU);
return 0; return 0;
} }
@ -4615,6 +4617,12 @@ DOKEYDOWN:
return 0; return 0;
} }
if(wParam >= IDC_LUAMENU_RESERVE_START &&
wParam <= IDC_LUAMENU_RESERVE_END)
{
CallRegisteredLuaMenuHandlers(wParam);
return 0;
}
} }
switch(LOWORD(wParam)) switch(LOWORD(wParam))
{ {

View File

@ -897,7 +897,9 @@
#define IDC_LUASCRIPT_RESERVE_END 58099 #define IDC_LUASCRIPT_RESERVE_END 58099
#define IDD_LUARECENT_RESERVE_START 58100 #define IDD_LUARECENT_RESERVE_START 58100
#define IDD_LUARECENT_RESERVE_END 58199 #define IDD_LUARECENT_RESERVE_END 58199
#define IDC_FRAMEADVANCE 58200 #define IDC_LUAMENU_RESERVE_START 58200
#define IDC_LUAMENU_RESERVE_END 58399
#define IDC_FRAMEADVANCE 58400
#define IDC_LABEL_HK1 60001 #define IDC_LABEL_HK1 60001
#define IDC_LABEL_HK2 60002 #define IDC_LABEL_HK2 60002
#define IDC_LABEL_HK3 60003 #define IDC_LABEL_HK3 60003