354 lines
12 KiB
C++
354 lines
12 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Based on code named
|
|
* opentx - https://github.com/opentx/opentx
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include "lua_widget_factory.h"
|
|
#include "lua_widget.h"
|
|
|
|
#include "lua_api.h"
|
|
#include "lua_states.h"
|
|
#include "strhelpers.h"
|
|
|
|
#define MAX_INSTRUCTIONS (20000/100)
|
|
|
|
LuaWidgetFactory::LuaWidgetFactory(const char* name, WidgetOption* widgetOptions, int optionDefinitionsReference,
|
|
int createFunction, int updateFunction, int refreshFunction,
|
|
int backgroundFunction, int translateFunction, bool lvglLayout,
|
|
const char* filename) :
|
|
WidgetFactory(name, widgetOptions),
|
|
optionDefinitionsReference(optionDefinitionsReference),
|
|
createFunction(createFunction),
|
|
updateFunction(updateFunction),
|
|
refreshFunction(refreshFunction),
|
|
backgroundFunction(backgroundFunction),
|
|
translateFunction(translateFunction),
|
|
lvglLayout(lvglLayout),
|
|
path(filename)
|
|
{
|
|
path = path.substr(0, path.rfind("/") + 1);
|
|
translateOptions(widgetOptions);
|
|
}
|
|
|
|
LuaWidgetFactory::~LuaWidgetFactory() {
|
|
unregisterWidget(this);
|
|
|
|
if (name) delete name;
|
|
if (displayName) delete displayName;
|
|
|
|
auto option = getDefaultOptions();
|
|
while (option && option->name != nullptr) {
|
|
if (option->displayName) {
|
|
delete option->displayName;
|
|
}
|
|
option++;
|
|
}
|
|
}
|
|
|
|
Widget* LuaWidgetFactory::createNew(Window* parent, const rect_t& rect,
|
|
int screenNum, int zoneNum) const
|
|
{
|
|
if (lsWidgets == 0) return 0;
|
|
|
|
auto widgetData = g_model.getWidgetData(screenNum, zoneNum);
|
|
|
|
luaSetInstructionsLimit(lsWidgets, MAX_INSTRUCTIONS);
|
|
|
|
// Make 'zone' table for 'create' call
|
|
lua_newtable(lsWidgets);
|
|
l_pushtableint(lsWidgets, "x", 0);
|
|
l_pushtableint(lsWidgets, "y", 0);
|
|
l_pushtableint(lsWidgets, "w", rect.w);
|
|
l_pushtableint(lsWidgets, "h", rect.h);
|
|
l_pushtableint(lsWidgets, "xabs", rect.x);
|
|
l_pushtableint(lsWidgets, "yabs", rect.y);
|
|
|
|
// Store the zone data in registry for later updates
|
|
int zoneRectDataRef = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
|
|
// Create options table
|
|
lua_newtable(lsWidgets);
|
|
int i = 0;
|
|
for (const WidgetOption* option = options; option->name; option++, i++) {
|
|
if (option->type == WidgetOption::String || option->type == WidgetOption::File) {
|
|
lua_pushstring(lsWidgets, option->name);
|
|
lua_pushstring(lsWidgets, widgetData->options[i].value.stringValue.c_str());
|
|
lua_settable(lsWidgets, -3);
|
|
} else if (option->type == WidgetOption::Integer || option->type == WidgetOption::Switch) {
|
|
l_pushtableint(lsWidgets, option->name, widgetData->options[i].value.signedValue);
|
|
} else {
|
|
l_pushtableint(lsWidgets, option->name, widgetData->options[i].value.unsignedValue);
|
|
}
|
|
}
|
|
|
|
// Store the options data in registry for later updates
|
|
int optionsDataRef = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
|
|
|
|
return new LuaWidget(this, parent, rect, screenNum, zoneNum, zoneRectDataRef, optionsDataRef, createFunction, path);
|
|
}
|
|
|
|
void LuaWidgetFactory::translateOptions(WidgetOption * options)
|
|
{
|
|
if (lsWidgets == 0) return;
|
|
|
|
// No translations provided
|
|
if (!translateFunction) return;
|
|
|
|
#if defined(ALL_LANGS)
|
|
char lang[3];
|
|
lang[0] = toupper(g_eeGeneral.ttsLanguage[0]);
|
|
lang[1] = toupper(g_eeGeneral.ttsLanguage[1]);
|
|
lang[2] = 0;
|
|
#else
|
|
const char* lang = TRANSLATIONS;
|
|
#endif
|
|
|
|
auto option = options;
|
|
while (option && option->name != nullptr) {
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, translateFunction);
|
|
lua_pushstring(lsWidgets, option->name);
|
|
lua_pushstring(lsWidgets, lang);
|
|
bool err = lua_pcall(lsWidgets, 2, 1, 0);
|
|
if (!err) {
|
|
auto dn = lua_tostring(lsWidgets, -1);
|
|
if (dn) option->displayName = strdup(dn);
|
|
}
|
|
lua_pop(lsWidgets, 1);
|
|
|
|
option++;
|
|
}
|
|
|
|
// Widget display name
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, translateFunction);
|
|
lua_pushstring(lsWidgets, name);
|
|
lua_pushstring(lsWidgets, lang);
|
|
bool err = lua_pcall(lsWidgets, 2, 1, 0);
|
|
if (!err) {
|
|
auto dn = lua_tostring(lsWidgets, -1);
|
|
if (dn) displayName = strdup(dn);
|
|
}
|
|
lua_pop(lsWidgets, 1);
|
|
}
|
|
|
|
static int switchValue()
|
|
{
|
|
int v = SWSRC_INVERT;
|
|
if (lua_istable(lsWidgets, -1)) {
|
|
// Find first available
|
|
int t = lua_gettop(lsWidgets);
|
|
for (lua_pushnil(lsWidgets); v == SWSRC_INVERT && lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
v = getSwitchIndex(luaL_checkstring(lsWidgets, -1), false);
|
|
}
|
|
lua_settop(lsWidgets, t);
|
|
} else if (lua_type(lsWidgets, -1) == LUA_TSTRING) {
|
|
v = getSwitchIndex(lua_tostring(lsWidgets, -1), true);
|
|
} else {
|
|
v = luaL_checkinteger(lsWidgets, -1);
|
|
}
|
|
if (v == SWSRC_INVERT) v = SWSRC_NONE;
|
|
return v;
|
|
}
|
|
|
|
static int sourceValue()
|
|
{
|
|
int v = -1;
|
|
if (lua_istable(lsWidgets, -1)) {
|
|
// Find first available
|
|
int t = lua_gettop(lsWidgets);
|
|
for (lua_pushnil(lsWidgets); v < 0 && lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
v = getSourceIndex(luaL_checkstring(lsWidgets, -1), false);
|
|
}
|
|
lua_settop(lsWidgets, t);
|
|
} else if (lua_type(lsWidgets, -1) == LUA_TSTRING) {
|
|
v = getSourceIndex(lua_tostring(lsWidgets, -1), true);
|
|
} else {
|
|
v = luaL_checkunsigned(lsWidgets, -1);
|
|
}
|
|
if (v == -1) v = MIXSRC_NONE;
|
|
return v;
|
|
}
|
|
|
|
// Parse the options table to get the default, min and max values
|
|
// Called when the widget settings dialog is opened to get values
|
|
// for current loaded model.
|
|
const void LuaWidgetFactory::parseOptionDefaults() const
|
|
{
|
|
if (optionDefinitionsReference == LUA_REFNIL) {
|
|
// TRACE("parseOptionDefaults() no options");
|
|
return;
|
|
}
|
|
|
|
PROTECT_LUA()
|
|
{
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, optionDefinitionsReference);
|
|
WidgetOption *option = (WidgetOption*)options;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), option->name;
|
|
lua_pop(lsWidgets, 1)) {
|
|
// TRACE("parseOptionDefaults parsing option %s", option->name);
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table
|
|
uint8_t field = 0;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field < 5;
|
|
lua_pop(lsWidgets, 1), field++) {
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
switch (field) {
|
|
case 2:
|
|
if (option->type == WidgetOption::Switch) {
|
|
option->deflt.signedValue = switchValue();
|
|
} else if (option->type == WidgetOption::Source) {
|
|
option->deflt.unsignedValue = sourceValue();
|
|
} else if (option->type == WidgetOption::Integer) {
|
|
option->deflt.signedValue = luaL_checkinteger(lsWidgets, -1);
|
|
} else if (option->type == WidgetOption::Bool) {
|
|
option->deflt.boolValue = (luaL_checkunsigned(lsWidgets, -1) != 0);
|
|
} else if (option->type == WidgetOption::String || option->type == WidgetOption::File) {
|
|
option->deflt.stringValue = luaL_checkstring(lsWidgets, -1);
|
|
} else {
|
|
option->deflt.unsignedValue = luaL_checkunsigned(lsWidgets, -1);
|
|
}
|
|
break;
|
|
case 3:
|
|
if (option->type == WidgetOption::Switch) {
|
|
option->min.signedValue = switchValue();
|
|
} else if (option->type == WidgetOption::Source) {
|
|
option->min.unsignedValue = sourceValue();
|
|
} else if (option->type == WidgetOption::Integer || option->type == WidgetOption::Slider) {
|
|
option->min.signedValue = luaL_checkinteger(lsWidgets, -1);
|
|
} else if (option->type == WidgetOption::Choice) {
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is a table
|
|
option->choiceValues.clear();
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
option->choiceValues.push_back(luaL_checkstring(lsWidgets, -1));
|
|
}
|
|
} else if (option->type == WidgetOption::File) {
|
|
option->fileSelectPath = luaL_checkstring(lsWidgets, -1);
|
|
}
|
|
break;
|
|
case 4:
|
|
if (option->type == WidgetOption::Switch) {
|
|
option->max.signedValue = switchValue();
|
|
} else if (option->type == WidgetOption::Source) {
|
|
option->max.unsignedValue = sourceValue();
|
|
} else if (option->type == WidgetOption::Integer || option->type == WidgetOption::Slider) {
|
|
option->max.signedValue = luaL_checkinteger(lsWidgets, -1);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
option++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TRACE("error in theme/widget options");
|
|
}
|
|
UNPROTECT_LUA();
|
|
return;
|
|
}
|
|
|
|
// Parse options table to get name and type values.
|
|
// Called on radio startup to build base data for all widgtes.
|
|
WidgetOption* LuaWidgetFactory::parseOptionDefinitions(int reference)
|
|
{
|
|
if (reference == LUA_REFNIL) {
|
|
// TRACE("parseOptionDefinitions() no options");
|
|
return NULL;
|
|
}
|
|
|
|
int count = 0;
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
|
|
count++;
|
|
}
|
|
lua_pop(lsWidgets, 1);
|
|
|
|
// TRACE("we have %d options", count);
|
|
if (count > MAX_WIDGET_OPTIONS) {
|
|
count = MAX_WIDGET_OPTIONS;
|
|
// TRACE("limited to %d options", count);
|
|
}
|
|
|
|
WidgetOption *options = new WidgetOption[count + 1];
|
|
if (!options) {
|
|
return NULL;
|
|
}
|
|
|
|
PROTECT_LUA()
|
|
{
|
|
lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
|
|
WidgetOption *option = options;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), count-- > 0;
|
|
lua_pop(lsWidgets, 1)) {
|
|
// TRACE("parseOptionDefinitions parsing option %d", count);
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table
|
|
uint8_t field = 0;
|
|
for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field < 5;
|
|
lua_pop(lsWidgets, 1), field++) {
|
|
luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
|
|
switch (field) {
|
|
case 0:
|
|
option->name = luaL_checkstring(lsWidgets, -1);
|
|
option->displayName = nullptr;
|
|
// TRACE("name = %s", option->name);
|
|
break;
|
|
case 1:
|
|
option->type = (WidgetOption::Type)luaL_checkinteger(lsWidgets, -1);
|
|
option->deflt.unsignedValue = 0;
|
|
// set some sensible defaults
|
|
if (option->type == WidgetOption::Integer) {
|
|
option->min.signedValue = -100;
|
|
option->max.signedValue = 100;
|
|
} else if (option->type == WidgetOption::Switch) {
|
|
option->min.signedValue = SWSRC_FIRST;
|
|
option->max.signedValue = SWSRC_LAST;
|
|
} else if (option->type == WidgetOption::Timer) {
|
|
option->min.unsignedValue = 0;
|
|
option->max.unsignedValue = MAX_TIMERS - 1;
|
|
} else if (option->type == WidgetOption::TextSize) {
|
|
option->min.unsignedValue = FONT_STD_INDEX;
|
|
option->max.unsignedValue = FONTS_COUNT - 1;
|
|
} else if (option->type == WidgetOption::String || option->type == WidgetOption::File) {
|
|
option->deflt.stringValue.clear();
|
|
} else if (option->type == WidgetOption::Slider) {
|
|
option->min.unsignedValue = 0;
|
|
option->max.unsignedValue = 9;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
option++;
|
|
}
|
|
lua_pop(lsWidgets, 1);
|
|
option->name = NULL; // sentinel
|
|
}
|
|
else
|
|
{
|
|
TRACE("error in theme/widget options");
|
|
delete[] options;
|
|
return NULL;
|
|
}
|
|
UNPROTECT_LUA();
|
|
return options;
|
|
}
|