From e7dc68e5ce93de42a4fc9cd94b238df3d7f77cd3 Mon Sep 17 00:00:00 2001 From: gocha Date: Sat, 6 Nov 2010 14:59:44 +0000 Subject: [PATCH] Lua: add menu functions, sample http://pastebin.com/VD9quJRS --- desmume/src/lua-engine.cpp | 442 ++++++++++++++++++++++++++++++++- desmume/src/lua-engine.h | 19 ++ desmume/src/windows/main.cpp | 8 + desmume/src/windows/resource.h | 4 +- 4 files changed, 470 insertions(+), 3 deletions(-) diff --git a/desmume/src/lua-engine.cpp b/desmume/src/lua-engine.cpp index f894d70c0..b085c1956 100644 --- a/desmume/src/lua-engine.cpp +++ b/desmume/src/lua-engine.cpp @@ -13,9 +13,10 @@ #include "saves.h" #include "emufile.h" #if defined(WIN32) && !defined(WXPORT) +#include #include "main.h" -#include "windows.h" #include "video.h" +#include "resource.h" #endif #ifdef WIN32 #include @@ -60,6 +61,27 @@ struct LuaGUIData 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 subMenuData; + std::map menuItemMap; +}; + +static const char* menuCallbackIDString = "menuhandlers"; + struct LuaContextInfo { 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 @@ -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 unsigned int numMemHooks; // number of registered memory functions (1 per hooked byte) 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 void(*print)(int uid, const char* str); void(*onstart)(int uid); @@ -143,6 +166,7 @@ static const char* luaCallIDStrings [] = "CALL_BEFORESAVE", "CALL_AFTERLOAD", "CALL_ONSTART", + "CALL_ONINITMENU", "CALL_HOTKEY_1", "CALL_HOTKEY_2", @@ -3406,6 +3430,332 @@ DEFINE_LUA_FUNCTION(emu_reset, "") 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::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::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::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 /* DEFINE_LUA_FUNCTION(emu_loadrom, "filename") @@ -4152,6 +4502,9 @@ static const struct luaL_reg emulib [] = {"openscript", emu_openscript}, // {"loadrom", emu_loadrom}, {"reset", emu_reset}, + {"addmenu", emu_addmenu}, + {"setmenuiteminfo", emu_setmenuiteminfo}, + {"registermenustart", emu_registermenustart}, // alternative names // {"openrom", emu_loadrom}, {NULL, NULL} @@ -4539,6 +4892,10 @@ void registerLibs(lua_State* L) lua_setfield(L, LUA_REGISTRYINDEX, luaMemHookTypeStrings[i]); } + // push an array for menu handlers + lua_newtable(L); + lua_setfield(L, LUA_REGISTRYINDEX, menuCallbackIDString); + // register type luaL_newmetatable(L, "EMUFILE_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 // 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++) { lua_State* L = info.L; @@ -4943,6 +5300,43 @@ void StopLuaScript(int uid) info.numMemHooks = 0; for(int i = 0; i < LUAMEMHOOK_COUNT; i++) CalculateMemHookRegions((LuaMemHookType)i); + +#if defined(WIN32) && !defined(WXPORT) + // remove items + map::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::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(); } @@ -5053,6 +5447,50 @@ void CallRegisteredLuaMemHook_LuaMatch(unsigned int address, int size, unsigned } +void CallRegisteredLuaMenuHandlers(PlatformMenuItem menuItem) +{ + std::map::iterator iter = luaContextInfo.begin(); + std::map::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() { std::map::iterator iter = luaContextInfo.begin(); diff --git a/desmume/src/lua-engine.h b/desmume/src/lua-engine.h index e2594279a..701bb17bd 100644 --- a/desmume/src/lua-engine.h +++ b/desmume/src/lua-engine.h @@ -3,6 +3,12 @@ #include "types.h" +#if defined(WIN32) && !defined(WXPORT) +#include +#include +#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 RunLuaScriptFile(int uid, const char* filename); void StopLuaScript(int uid); @@ -19,6 +25,7 @@ enum LuaCallID LUACALL_BEFORESAVE, LUACALL_AFTERLOAD, LUACALL_ONSTART, + LUACALL_ONINITMENU, LUACALL_SCRIPT_HOTKEY_1, LUACALL_SCRIPT_HOTKEY_2, @@ -86,6 +93,18 @@ private: void CallRegisteredLuaSaveFunctions(int savestateNumber, 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 RestartAllLuaScripts(); void EnableStopAllLuaScripts(bool enable); diff --git a/desmume/src/windows/main.cpp b/desmume/src/windows/main.cpp index ea649ae20..6ab76a8bb 100644 --- a/desmume/src/windows/main.cpp +++ b/desmume/src/windows/main.cpp @@ -4050,6 +4050,8 @@ LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM UpdateHotkeyAssignments(); //Add current hotkey mappings to menu item names + CallRegisteredLuaFunctions(LUACALL_ONINITMENU); + return 0; } @@ -4615,6 +4617,12 @@ DOKEYDOWN: return 0; } + if(wParam >= IDC_LUAMENU_RESERVE_START && + wParam <= IDC_LUAMENU_RESERVE_END) + { + CallRegisteredLuaMenuHandlers(wParam); + return 0; + } } switch(LOWORD(wParam)) { diff --git a/desmume/src/windows/resource.h b/desmume/src/windows/resource.h index a16a307f0..c26d576e0 100644 --- a/desmume/src/windows/resource.h +++ b/desmume/src/windows/resource.h @@ -897,7 +897,9 @@ #define IDC_LUASCRIPT_RESERVE_END 58099 #define IDD_LUARECENT_RESERVE_START 58100 #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_HK2 60002 #define IDC_LABEL_HK3 60003