/*
* lua menue
*
* (C) 2014 by martii
* (C) 2014-2015 M. Liebmann (micha-bbg)
* (C) 2016 Sven Hoefer (svenhoefer)
* (C) 2015 Jacek Jendrzej (SatBaby)
*
* 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, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "luainstance.h"
#include "lua_menue.h"
CLuaInstMenu* CLuaInstMenu::getInstance()
{
static CLuaInstMenu* LuaInstMenu = NULL;
if(!LuaInstMenu)
LuaInstMenu = new CLuaInstMenu();
return LuaInstMenu;
}
bool CLuaMenuChangeObserver::changeNotify(lua_State *L, const std::string &luaAction, const std::string &luaId, void *Data)
{
const char *optionValue = (const char *) Data;
lua_pushglobaltable(L);
lua_getfield(L, -1, luaAction.c_str());
lua_remove(L, -2);
lua_pushstring(L, luaId.c_str());
lua_pushstring(L, optionValue);
int status = lua_pcall(L, 2 /* two args */, 1 /* one result */, 0);
if (status) {
bool isString = lua_isstring(L,-1);
const char *null = "NULL";
fprintf(stderr, "[CLuaMenuChangeObserver::%s:%d] error in script: %s\n", __func__, __LINE__, isString ? lua_tostring(L, -1): null);
DisplayErrorMessage(isString ? lua_tostring(L, -1):null, "Lua Script Error:");
}
double res = lua_isnumber(L, -1) ? lua_tonumber(L, -1) : 0;
return (((int)res == menu_return::RETURN_REPAINT) || ((int)res == menu_return::RETURN_EXIT_REPAINT));
}
void CLuaInstMenu::MenuRegister(lua_State *L)
{
luaL_Reg meth[] = {
{ "new", CLuaInstMenu::MenuNew },
{ "addKey", CLuaInstMenu::MenuAddKey },
{ "addItem", CLuaInstMenu::MenuAddItem },
{ "exec", CLuaInstMenu::MenuExec },
{ "hide", CLuaInstMenu::MenuHide },
{ "setActive", CLuaInstMenu::MenuSetActive },
{ "setName", CLuaInstMenu::MenuSetName },
{ "__gc", CLuaInstMenu::MenuDelete },
{ NULL, NULL }
};
luaL_newmetatable(L, "menu");
luaL_setfuncs(L, meth, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -1, "__index");
lua_setglobal(L, "menu");
// keep misspelled "menue" for backwards-compatibility
luaL_newmetatable(L, "menu");
luaL_setfuncs(L, meth, 0);
lua_pushvalue(L, -1);
lua_setfield(L, -1, "__index");
lua_setglobal(L, "menue");
}
CLuaMenu *CLuaInstMenu::MenuCheck(lua_State *L, int n)
{
return *(CLuaMenu **) luaL_checkudata(L, n, "menu");
}
CLuaMenu::CLuaMenu()
{
m = NULL;
observ = new CLuaMenuChangeObserver();
}
CLuaMenu::~CLuaMenu()
{
itemmap.clear();
delete m;
delete observ;
}
CLuaMenuForwarder::CLuaMenuForwarder(lua_State *_L, std::string _luaAction, std::string _luaId)
{
Init(_L, _luaAction, _luaId);
}
void CLuaMenuForwarder::Init(lua_State *_L, std::string _luaAction, std::string _luaId)
{
L = _L;
luaAction = _luaAction;
luaId = _luaId;
}
CLuaMenuForwarder::~CLuaMenuForwarder()
{
}
int CLuaMenuForwarder::exec(CMenuTarget* /*parent*/, const std::string & /*actionKey*/)
{
int res = menu_return::RETURN_REPAINT;
if (!luaAction.empty()) {
lua_pushglobaltable(L);
lua_getfield(L, -1, luaAction.c_str());
lua_remove(L, -2);
lua_pushstring(L, luaId.c_str());
int status = lua_pcall(L, 1 /* one arg */, 1 /* one result */, 0);
if (status) {
bool isString = lua_isstring(L,-1);
const char *null = "NULL";
fprintf(stderr, "[CLuaMenuForwarder::%s:%d] error in script: %s\n", __func__, __LINE__, isString ? lua_tostring(L, -1):null);
DisplayErrorMessage(isString ? lua_tostring(L, -1):null, "Lua Script Error:");
}
if (lua_isnumber(L, -1))
res = (int) lua_tonumber(L, -1);
lua_pop(L, 1);
}
return res;
}
CLuaMenuFilebrowser::CLuaMenuFilebrowser(lua_State *_L, std::string _luaAction, std::string _luaId, std::string *_value, bool _dirMode) : CLuaMenuForwarder(_L, _luaAction, _luaId)
{
Init(_value, _dirMode);
}
void CLuaMenuFilebrowser::Init(std::string *_value, bool _dirMode)
{
value = _value;
dirMode = _dirMode;
}
int CLuaMenuFilebrowser::exec(CMenuTarget* /*parent*/, const std::string& /*actionKey*/)
{
int res = menu_return::RETURN_REPAINT;
CFileBrowser fileBrowser;
fileBrowser.Dir_Mode = dirMode;
CFileFilter fileFilter;
for (std::vector::iterator it = filter.begin(); it != filter.end(); ++it)
fileFilter.addFilter(*it);
if (!filter.empty())
fileBrowser.Filter = &fileFilter;
std::string tmpValue = (dirMode) ? *value : getPathName(*value);
if (fileBrowser.exec(tmpValue.c_str()) == true)
*value = fileBrowser.getSelectedFile()->Name;
if (!luaAction.empty()) {
lua_pushglobaltable(L);
lua_getfield(L, -1, luaAction.c_str());
lua_remove(L, -2);
lua_pushstring(L, luaId.c_str());
lua_pushstring(L, value->c_str());
int status = lua_pcall(L, 2 /* two arg */, 1 /* one result */, 0);
if (status) {
bool isString = lua_isstring(L,-1);
const char *null = "NULL";
fprintf(stderr, "[CLuaMenuFilebrowser::%s:%d] error in script: %s\n", __func__, __LINE__, isString ? lua_tostring(L, -1):null);
DisplayErrorMessage(isString ? lua_tostring(L, -1):null, "Lua Script Error:");
}
if (lua_isnumber(L, -1))
res = (int) lua_tonumber(L, -1);
lua_pop(L, 1);
}
return res;
}
CLuaMenuStringinput::CLuaMenuStringinput(lua_State *_L, std::string _luaAction, std::string _luaId, const char *_name, std::string *_value, int _size, std::string _valid_chars, CChangeObserver *_observ, const char *_icon, bool _sms) : CLuaMenuForwarder(_L, _luaAction, _luaId)
{
Init(_name, _value, _size, _valid_chars, _observ, _icon, _sms);
}
void CLuaMenuStringinput::Init(const char *_name, std::string *_value, int _size, std::string _valid_chars, CChangeObserver *_observ, const char *_icon, bool _sms)
{
name = _name;
value = _value;
size = _size;
valid_chars = _valid_chars;
icon = _icon;
observ = _observ;
sms = _sms;
}
int CLuaMenuStringinput::exec(CMenuTarget* /*parent*/, const std::string & /*actionKey*/)
{
int res = menu_return::RETURN_REPAINT;
CStringInput *i;
if (sms)
i = new CStringInputSMS((char *)name, value, size,
NONEXISTANT_LOCALE, NONEXISTANT_LOCALE, valid_chars.c_str(), observ, icon);
else
i = new CStringInput((char *)name, value, size,
NONEXISTANT_LOCALE, NONEXISTANT_LOCALE, valid_chars.c_str(), observ, icon);
i->exec(NULL, "");
delete i;
if (!luaAction.empty()) {
lua_pushglobaltable(L);
lua_getfield(L, -1, luaAction.c_str());
lua_remove(L, -2);
lua_pushstring(L, luaId.c_str());
lua_pushstring(L, value->c_str());
int status = lua_pcall(L, 2 /* two arg */, 1 /* one result */, 0);
if (status) {
bool isString = lua_isstring(L,-1);
const char *null = "NULL";
fprintf(stderr, "[CLuaMenuStringinput::%s:%d] error in script: %s\n", __func__, __LINE__, isString ? lua_tostring(L, -1):null);
DisplayErrorMessage(isString ? lua_tostring(L, -1):null, "Lua Script Error:");
}
if (lua_isnumber(L, -1))
res = (int) lua_tonumber(L, -1);
lua_pop(L, 2);
}
return res;
}
CLuaMenuKeyboardinput::CLuaMenuKeyboardinput(lua_State *_L, std::string _luaAction, std::string _luaId, const char *_name, std::string *_value, int _size, CChangeObserver *_observ, const char *_icon, std::string _help, std::string _help2) : CLuaMenuForwarder(_L, _luaAction, _luaId)
{
Init(_name, _value, _size, _observ, _icon, _help, _help2);
}
void CLuaMenuKeyboardinput::Init(const char *_name, std::string *_value, int _size, CChangeObserver *_observ, const char *_icon, std::string _help, std::string _help2)
{
name = _name;
value = _value;
size = _size;
icon = _icon;
observ = _observ;
help = _help;
help2 = _help2;
}
int CLuaMenuKeyboardinput::exec(CMenuTarget* /*parent*/, const std::string & /*actionKey*/)
{
int res = menu_return::RETURN_REPAINT;
CKeyboardInput *i;
i = new CKeyboardInput((char *)name, value, size, observ, icon, help, help2);
i->exec(NULL, "");
delete i;
if (!luaAction.empty()) {
lua_pushglobaltable(L);
lua_getfield(L, -1, luaAction.c_str());
lua_remove(L, -2);
lua_pushstring(L, luaId.c_str());
lua_pushstring(L, value->c_str());
int status = lua_pcall(L, 2 /* two arg */, 1 /* one result */, 0);
if (status) {
bool isString = lua_isstring(L,-1);
const char *null = "NULL";
fprintf(stderr, "[CLuaMenuKeyboardinput::%s:%d] error in script: %s\n", __func__, __LINE__, isString ? lua_tostring(L, -1):null);
DisplayErrorMessage(isString ? lua_tostring(L, -1):null, "Lua Script Error:");
}
if (lua_isnumber(L, -1))
res = (int) lua_tonumber(L, -1);
lua_pop(L, 2);
}
return res;
}
int CLuaInstMenu::MenuNew(lua_State *L)
{
CMenuWidget *m;
if (lua_istable(L, 1)) {
std::string name; tableLookup(L, "name", name) || tableLookup(L, "title", name);
std::string icon; tableLookup(L, "icon", icon);
lua_Integer mwidth;
if (tableLookup(L, "mwidth", mwidth))
m = new CMenuWidget(name.c_str(), icon.c_str(), mwidth);
else
m = new CMenuWidget(name.c_str(), icon.c_str());
} else
m = new CMenuWidget();
CLuaMenu **udata = (CLuaMenu **) lua_newuserdata(L, sizeof(CLuaMenu *));
*udata = new CLuaMenu();
(*udata)->m = m;
luaL_getmetatable(L, "menu");
lua_setmetatable(L, -2);
return 1;
}
int CLuaInstMenu::MenuAddKey(lua_State *L)
{
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
lua_assert(lua_istable(L, 2));
std::string action; tableLookup(L, "action", action);
std::string id; tableLookup(L, "id", id);
lua_Unsigned directkey = CRCInput::RC_nokey; tableLookup(L, "directkey", directkey);
if ((!action.empty()) && (directkey != CRCInput::RC_nokey)) {
CLuaMenuForwarder *forwarder = new CLuaMenuForwarder(L, action, id);
D->m->addKey(directkey, forwarder, action);
D->targets.push_back(forwarder);
}
return 0;
}
int CLuaInstMenu::MenuAddItem(lua_State *L)
{
CLuaMenu *D = MenuCheck(L, 1);
if (!D) {
lua_pushnil(L);
return 1;
}
lua_assert(lua_istable(L, 2));
CMenuItem *mi = NULL;
CLuaMenuItem i;
D->items.push_back(i);
CLuaMenuItem *b = &D->items.back();
tableLookup(L, "name", b->name);
std::string type; tableLookup(L, "type", type);
if (type == "back") {
D->m->addItem(GenericMenuBack);
} else if (type == "next") {
D->m->addItem(GenericMenuNext);
} else if (type == "cancel") {
D->m->addItem(GenericMenuCancel);
} else if (type == "separator") {
D->m->addItem(GenericMenuSeparator);
} else if ((type == "separatorline") || (type == "subhead")) {
if (!b->name.empty()) {
int flag = (type == "separatorline") ? CMenuSeparator::LINE : CMenuSeparator::SUB_HEAD;
D->m->addItem(new CMenuSeparator(CMenuSeparator::STRING | flag, b->name.c_str(), NONEXISTANT_LOCALE));
} else
D->m->addItem(GenericMenuSeparatorLine);
} else {
std::string right_icon_str; tableLookup(L, "right_icon", right_icon_str);
std::string action; tableLookup(L, "action", action);
std::string value; tableLookup(L, "value", value);
std::string hint; tableLookup(L, "hint", hint);
std::string hint_icon_str; tableLookup(L, "hint_icon", hint_icon_str);
std::string icon_str; tableLookup(L, "icon", icon_str);
std::string id; tableLookup(L, "id", id);
std::string tmp;
char *right_icon = NULL;
if (!right_icon_str.empty()) {
right_icon = strdup(right_icon_str.c_str());
D->tofree.push_back(right_icon);
}
char *hint_icon = NULL;
if (!hint_icon_str.empty()) {
hint_icon = strdup(hint_icon_str.c_str());
D->tofree.push_back(hint_icon);
}
char *icon = NULL;
if (!icon_str.empty()) {
icon = strdup(icon_str.c_str());
D->tofree.push_back(icon);
}
lua_Unsigned directkey = CRCInput::RC_nokey; tableLookup(L, "directkey", directkey);
lua_Integer pulldown = false; tableLookup(L, "pulldown", pulldown);
bool enabled = true;
if (!(tableLookup(L, "enabled", enabled) || tableLookup(L, "active", enabled))) {
tmp = "true";
if (tableLookup(L, "enabled", tmp) || tableLookup(L, "active", tmp))
paramBoolDeprecated(L, tmp.c_str());
enabled = (tmp == "true" || tmp == "1" || tmp == "yes");
}
tableLookup(L, "range", tmp);
int range_from = 0, range_to = 99;
sscanf(tmp.c_str(), "%d,%d", &range_from, &range_to);
if (type == "forwarder") {
b->str_val = value;
CLuaMenuForwarder *forwarder = new CLuaMenuForwarder(L, action, id);
mi = new CMenuForwarder(b->name, enabled, b->str_val, forwarder, NULL/*ActionKey*/, directkey, icon, right_icon);
if (!hint.empty() || hint_icon)
mi->setHint(hint_icon, hint);
D->targets.push_back(forwarder);
} else if (type == "chooser") {
int options_count = 0;
lua_pushstring(L, "options");
lua_gettable(L, -2);
if (lua_istable(L, -1))
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 2)) {
lua_pushvalue(L, -2);
options_count++;
}
lua_pop(L, 1);
if (options_count == 0) {
D->m->addItem(new CMenuSeparator(CMenuSeparator::STRING | CMenuSeparator::LINE, "ERROR! (options_count)", NONEXISTANT_LOCALE));
lua_pushnil(L);
return 1;
}
CMenuOptionChooser::keyval_ext *kext = (CMenuOptionChooser::keyval_ext *)calloc(options_count, sizeof(CMenuOptionChooser::keyval_ext));
D->tofree.push_back(kext);
lua_pushstring(L, "options");
lua_gettable(L, -2);
b->int_val = 0;
if (lua_istable(L, -1)) {
int j = 0;
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 2)) {
lua_pushvalue(L, -2);
const char *key = lua_tostring(L, -1);
const char *val = lua_tostring(L, -2);
kext[j].key = atoi(key);
kext[j].value = NONEXISTANT_LOCALE;
kext[j].valname = strdup(val?val:"ERROR");
D->tofree.push_back((void *)kext[j].valname);
if (!strcmp(value.c_str(), kext[j].valname))
b->int_val = kext[j].key;
j++;
}
}
lua_pop(L, 1);
mi = new CMenuOptionChooser(b->name.c_str(), &b->int_val, kext, options_count, enabled, D->observ, directkey, icon, pulldown);
} else if (type == "numeric") {
b->int_val = range_from;
sscanf(value.c_str(), "%d", &b->int_val);
mi = new CMenuOptionNumberChooser(b->name, &b->int_val, enabled, range_from, range_to, D->observ, 0, 0, NONEXISTANT_LOCALE, pulldown);
} else if (type == "string") {
b->str_val = value;
mi = new CMenuOptionStringChooser(b->name.c_str(), &b->str_val, enabled, D->observ, directkey, icon, pulldown);
} else if (type == "stringinput") {
b->str_val = value;
std::string valid_chars = "abcdefghijklmnopqrstuvwxyz0123456789!\"ยง$%&/()=?-.@,_: ";
tableLookup(L, "valid_chars", valid_chars);
lua_Integer sms = 0; tableLookup(L, "sms", sms);
lua_Integer size = 30; tableLookup(L, "size", size);
CLuaMenuStringinput *stringinput = new CLuaMenuStringinput(L, action, id, b->name.c_str(), &b->str_val, size, valid_chars, D->observ, icon, sms);
mi = new CMenuForwarder(b->name, enabled, b->str_val, stringinput, NULL/*ActionKey*/, directkey, icon, right_icon);
D->targets.push_back(stringinput);
} else if (type == "keyboardinput") {
b->str_val = value;
lua_Integer size = 0; tableLookup(L, "size", size);
std::string help = ""; tableLookup(L, "help", help);
std::string help2 = ""; tableLookup(L, "help2", help2);
CLuaMenuKeyboardinput *keyboardinput = new CLuaMenuKeyboardinput(L, action, id, b->name.c_str(), &b->str_val, size, D->observ, icon, help, help2);
mi = new CMenuForwarder(b->name, enabled, b->str_val, keyboardinput, NULL/*ActionKey*/, directkey, icon, right_icon);
D->targets.push_back(keyboardinput);
} else if (type == "filebrowser") {
b->str_val = value;
lua_Integer dirMode = 0; tableLookup(L, "dir_mode", dirMode);
CLuaMenuFilebrowser *filebrowser = new CLuaMenuFilebrowser(L, action, id, &b->str_val, dirMode);
lua_pushstring(L, "filter");
lua_gettable(L, -2);
if (lua_istable(L, -1))
for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 2)) {
lua_pushvalue(L, -2);
const char *val = lua_tostring(L, -2);
filebrowser->addFilter(val);
}
lua_pop(L, 1);
mi = new CMenuForwarder(b->name, enabled, b->str_val, filebrowser, NULL/*ActionKey*/, directkey, icon, right_icon);
D->targets.push_back(filebrowser);
}
if (mi) {
mi->setLua(L, action, id);
if (!hint.empty() || hint_icon)
mi->setHint(hint_icon, hint);
D->m->addItem(mi);
}
}
if (mi) {
lua_Integer id = D->itemmap.size() + 1;
D->itemmap.insert(itemmap_pair_t(id, mi));
lua_pushinteger(L, id);
} else
lua_pushnil(L);
return 1;
}
int CLuaInstMenu::MenuExec(lua_State *L)
{
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
D->m->exec(NULL, "");
D->m->hide();
return 0;
}
int CLuaInstMenu::MenuHide(lua_State *L)
{
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
D->m->hide();
return 0;
}
int CLuaInstMenu::MenuSetActive(lua_State *L)
{
lua_assert(lua_istable(L, 2));
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
lua_Integer id; tableLookup(L, "item", id);
bool activ; tableLookup(L, "activ", activ);
CMenuItem* item = NULL;
for (itemmap_iterator_t it = D->itemmap.begin(); it != D->itemmap.end(); ++it) {
if (it->first == id) {
item = it->second;
break;
}
}
if (item)
item->setActive(activ);
return 0;
}
int CLuaInstMenu::MenuSetName(lua_State *L)
{
lua_assert(lua_istable(L, 2));
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
lua_Integer id; tableLookup(L, "item", id);
std::string name; tableLookup(L, "name", name);
CMenuItem* item = NULL;
for (itemmap_iterator_t it = D->itemmap.begin(); it != D->itemmap.end(); ++it) {
if (it->first == id) {
item = it->second;
break;
}
}
if (item)
item->setName(name);
return 0;
}
int CLuaInstMenu::MenuDelete(lua_State *L)
{
CLuaMenu *D = MenuCheck(L, 1);
if (!D) return 0;
while (!D->targets.empty()) {
delete D->targets.back();
D->targets.pop_back();
}
while (!D->tofree.empty()) {
free(D->tofree.back());
D->tofree.pop_back();
}
delete D;
return 0;
}