
headers from lua/.. contain macros that can break other headers in wesnoth, boost or other libraries. In this case it was a macro #define cast(t, exp) ((t)(exp)) defined in lua/llimits.h that broke a boost header.
459 lines
12 KiB
C++
459 lines
12 KiB
C++
/*
|
|
Copyright (C) 2014 - 2015 by Chris Beck <render787@gmail.com>
|
|
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
|
|
|
|
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.
|
|
|
|
See the COPYING file for more details.
|
|
*/
|
|
|
|
#include "lua_gui2.hpp"
|
|
|
|
#include "gui/auxiliary/canvas.hpp" // for tcanvas
|
|
#include "gui/auxiliary/window_builder.hpp" // for twindow_builder, etc
|
|
#include "gui/dialogs/gamestate_inspector.hpp"
|
|
#include "gui/dialogs/lua_interpreter.hpp"
|
|
#include "gui/widgets/clickable.hpp" // for tclickable_
|
|
#include "gui/widgets/control.hpp" // for tcontrol
|
|
#include "gui/widgets/multi_page.hpp" // for tmulti_page
|
|
#include "gui/widgets/progress_bar.hpp" // for tprogress_bar
|
|
#include "gui/widgets/selectable.hpp" // for tselectable_
|
|
#include "gui/widgets/slider.hpp" // for tslider
|
|
#include "gui/widgets/text_box.hpp" // for ttext_box
|
|
#include "gui/widgets/widget.hpp" // for twidget
|
|
#include "gui/widgets/window.hpp" // for twindow
|
|
|
|
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
|
#include "gui/widgets/list.hpp"
|
|
#else
|
|
#include "gui/widgets/listbox.hpp"
|
|
#endif
|
|
|
|
#include "config.hpp"
|
|
#include "log.hpp"
|
|
#include "scripting/lua_api.hpp" // for luaW_toboolean, etc
|
|
#include "scripting/lua_common.hpp"
|
|
#include "scripting/lua_types.hpp" // for getunitKey, dlgclbkKey, etc
|
|
#include "serialization/string_utils.hpp"
|
|
#include "tstring.hpp"
|
|
#include "video.hpp"
|
|
|
|
#include <boost/bind.hpp>
|
|
|
|
#include <map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "lua/lauxlib.h" // for luaL_checkinteger, etc
|
|
#include "lua/lua.h" // for lua_setfield, etc
|
|
|
|
static lg::log_domain log_scripting_lua("scripting/lua");
|
|
#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
|
|
|
|
static const char * dlgclbkKey = "dialog callback";
|
|
|
|
namespace {
|
|
struct scoped_dialog
|
|
{
|
|
lua_State *L;
|
|
scoped_dialog *prev;
|
|
static scoped_dialog *current;
|
|
gui2::twindow *window;
|
|
typedef std::map<gui2::twidget *, int> callback_map;
|
|
callback_map callbacks;
|
|
|
|
scoped_dialog(lua_State *l, gui2::twindow *w);
|
|
~scoped_dialog();
|
|
private:
|
|
scoped_dialog(const scoped_dialog &); // not implemented; not allowed.
|
|
};
|
|
|
|
scoped_dialog *scoped_dialog::current = NULL;
|
|
|
|
scoped_dialog::scoped_dialog(lua_State *l, gui2::twindow *w)
|
|
: L(l), prev(current), window(w), callbacks()
|
|
{
|
|
lua_pushstring(L
|
|
, dlgclbkKey);
|
|
lua_createtable(L, 1, 0);
|
|
lua_pushvalue(L, -2);
|
|
lua_rawget(L, LUA_REGISTRYINDEX);
|
|
lua_rawseti(L, -2, 1);
|
|
lua_rawset(L, LUA_REGISTRYINDEX);
|
|
current = this;
|
|
}
|
|
|
|
scoped_dialog::~scoped_dialog()
|
|
{
|
|
delete window;
|
|
current = prev;
|
|
lua_pushstring(L
|
|
, dlgclbkKey);
|
|
lua_pushvalue(L, -1);
|
|
lua_rawget(L, LUA_REGISTRYINDEX);
|
|
lua_rawgeti(L, -1, 1);
|
|
lua_remove(L, -2);
|
|
lua_rawset(L, LUA_REGISTRYINDEX);
|
|
}
|
|
}//unnamed namespace for scoped_dialog
|
|
|
|
static gui2::twidget *find_widget(lua_State *L, int i, bool readonly)
|
|
{
|
|
if (!scoped_dialog::current) {
|
|
luaL_error(L, "no visible dialog");
|
|
error_call_destructors_1:
|
|
luaL_argerror(L, i, "out of bounds");
|
|
error_call_destructors_2:
|
|
luaL_typerror(L, i, "string");
|
|
error_call_destructors_3:
|
|
luaL_argerror(L, i, "widget not found");
|
|
return NULL;
|
|
}
|
|
|
|
gui2::twidget *w = scoped_dialog::current->window;
|
|
for (; !lua_isnoneornil(L, i); ++i)
|
|
{
|
|
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
|
if (gui2::tlist *l = dynamic_cast<gui2::tlist *>(w))
|
|
#else
|
|
if (gui2::tlistbox *l = dynamic_cast<gui2::tlistbox *>(w))
|
|
#endif
|
|
{
|
|
int v = lua_tointeger(L, i);
|
|
if (v < 1)
|
|
goto error_call_destructors_1;
|
|
int n = l->get_item_count();
|
|
if (v > n) {
|
|
if (readonly)
|
|
goto error_call_destructors_1;
|
|
utils::string_map dummy;
|
|
for (; n < v; ++n)
|
|
l->add_row(dummy);
|
|
}
|
|
w = l->get_row_grid(v - 1);
|
|
}
|
|
else if (gui2::tmulti_page *l = dynamic_cast<gui2::tmulti_page *>(w))
|
|
{
|
|
int v = lua_tointeger(L, i);
|
|
if (v < 1)
|
|
goto error_call_destructors_1;
|
|
int n = l->get_page_count();
|
|
if (v > n) {
|
|
if (readonly)
|
|
goto error_call_destructors_1;
|
|
utils::string_map dummy;
|
|
for (; n < v; ++n)
|
|
l->add_page(dummy);
|
|
}
|
|
w = &l->page_grid(v - 1);
|
|
}
|
|
else
|
|
{
|
|
char const *m = lua_tostring(L, i);
|
|
if (!m) goto error_call_destructors_2;
|
|
w = w->find(m, false);
|
|
}
|
|
if (!w) goto error_call_destructors_3;
|
|
}
|
|
|
|
return w;
|
|
}
|
|
|
|
namespace lua_gui2 {
|
|
|
|
/**
|
|
* Displays a window.
|
|
* - Arg 1: WML table describing the window.
|
|
* - Arg 2: function called at pre-show.
|
|
* - Arg 3: function called at post-show.
|
|
* - Ret 1: integer.
|
|
*/
|
|
int show_dialog(lua_State *L, CVideo & video)
|
|
{
|
|
config def_cfg = luaW_checkconfig(L, 1);
|
|
|
|
gui2::twindow_builder::tresolution def(def_cfg);
|
|
scoped_dialog w(L, gui2::build(video, &def));
|
|
|
|
if (!lua_isnoneornil(L, 2)) {
|
|
lua_pushvalue(L, 2);
|
|
lua_call(L, 0, 0);
|
|
}
|
|
|
|
int v = scoped_dialog::current->window->show(true, 0);
|
|
|
|
if (!lua_isnoneornil(L, 3)) {
|
|
lua_pushvalue(L, 3);
|
|
lua_call(L, 0, 0);
|
|
}
|
|
|
|
lua_pushinteger(L, v);
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Sets the value of a widget on the current dialog.
|
|
* - Arg 1: scalar.
|
|
* - Args 2..n: path of strings and integers.
|
|
*/
|
|
int intf_set_dialog_value(lua_State *L)
|
|
{
|
|
gui2::twidget *w = find_widget(L, 2, false);
|
|
|
|
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
|
if (gui2::tlist *l = dynamic_cast<gui2::tlist *>(w))
|
|
#else
|
|
if (gui2::tlistbox *l = dynamic_cast<gui2::tlistbox *>(w))
|
|
#endif
|
|
{
|
|
int v = luaL_checkinteger(L, 1);
|
|
int n = l->get_item_count();
|
|
if (1 <= v && v <= n)
|
|
l->select_row(v - 1);
|
|
else
|
|
return luaL_argerror(L, 1, "out of bounds");
|
|
}
|
|
else if (gui2::tmulti_page *l = dynamic_cast<gui2::tmulti_page *>(w))
|
|
{
|
|
int v = luaL_checkinteger(L, 1);
|
|
int n = l->get_page_count();
|
|
if (1 <= v && v <= n)
|
|
l->select_page(v - 1);
|
|
else
|
|
return luaL_argerror(L, 1, "out of bounds");
|
|
}
|
|
else if (gui2::tselectable_ *s = dynamic_cast<gui2::tselectable_ *>(w))
|
|
{
|
|
s->set_value(luaW_toboolean(L, 1));
|
|
}
|
|
else if (gui2::ttext_box *t = dynamic_cast<gui2::ttext_box *>(w))
|
|
{
|
|
const t_string& text = luaW_checktstring(L, 1);
|
|
t->set_value(text.str());
|
|
}
|
|
else if (gui2::tslider *s = dynamic_cast<gui2::tslider *>(w))
|
|
{
|
|
const int v = luaL_checkinteger(L, 1);
|
|
const int m = s->get_minimum_value();
|
|
const int n = s->get_maximum_value();
|
|
if (m <= v && v <= n)
|
|
s->set_value(v);
|
|
else
|
|
return luaL_argerror(L, 1, "out of bounds");
|
|
}
|
|
else if (gui2::tprogress_bar *p = dynamic_cast<gui2::tprogress_bar *>(w))
|
|
{
|
|
const int v = luaL_checkinteger(L, 1);
|
|
if (0 <= v && v <= 100)
|
|
p->set_percentage(v);
|
|
else
|
|
return luaL_argerror(L, 1, "out of bounds");
|
|
}
|
|
else
|
|
{
|
|
t_string v = luaW_checktstring(L, 1);
|
|
gui2::tcontrol *c = dynamic_cast<gui2::tcontrol *>(w);
|
|
if (!c) return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
c->set_label(v);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Gets the value of a widget on the current dialog.
|
|
* - Args 1..n: path of strings and integers.
|
|
* - Ret 1: scalar.
|
|
*/
|
|
int intf_get_dialog_value(lua_State *L)
|
|
{
|
|
gui2::twidget *w = find_widget(L, 1, true);
|
|
|
|
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
|
if (gui2::tlist *l = dynamic_cast<gui2::tlist *>(w))
|
|
#else
|
|
if (gui2::tlistbox *l = dynamic_cast<gui2::tlistbox *>(w))
|
|
#endif
|
|
{
|
|
lua_pushinteger(L, l->get_selected_row() + 1);
|
|
} else if (gui2::tmulti_page *l = dynamic_cast<gui2::tmulti_page *>(w)) {
|
|
lua_pushinteger(L, l->get_selected_page() + 1);
|
|
} else if (gui2::tselectable_ *s = dynamic_cast<gui2::tselectable_ *>(w)) {
|
|
lua_pushboolean(L, s->get_value());
|
|
} else if (gui2::ttext_box *t = dynamic_cast<gui2::ttext_box *>(w)) {
|
|
lua_pushstring(L, t->get_value().c_str());
|
|
} else if (gui2::tslider *s = dynamic_cast<gui2::tslider *>(w)) {
|
|
lua_pushinteger(L, s->get_value());
|
|
} else if (gui2::tprogress_bar *p = dynamic_cast<gui2::tprogress_bar *>(w)) {
|
|
lua_pushinteger(L, p->get_percentage());
|
|
} else
|
|
return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
|
|
return 1;
|
|
}
|
|
|
|
namespace { // helpers of intf_set_dialog_callback()
|
|
void dialog_callback(gui2::twidget& w)
|
|
{
|
|
int cb;
|
|
{
|
|
scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
|
|
scoped_dialog::callback_map::const_iterator i = m.find(&w);
|
|
if (i == m.end()) return;
|
|
cb = i->second;
|
|
}
|
|
lua_State *L = scoped_dialog::current->L;
|
|
lua_pushstring(L
|
|
, dlgclbkKey);
|
|
lua_rawget(L, LUA_REGISTRYINDEX);
|
|
lua_rawgeti(L, -1, cb);
|
|
lua_remove(L, -2);
|
|
lua_call(L, 0, 0);
|
|
}
|
|
|
|
/** Helper struct for intf_set_dialog_callback. */
|
|
struct tdialog_callback_wrapper
|
|
{
|
|
void forward(gui2::twidget* widget)
|
|
{
|
|
assert(widget);
|
|
dialog_callback(*widget);
|
|
}
|
|
};
|
|
}//unnamed namespace for helpers of intf_set_dialog_callback()
|
|
|
|
/**
|
|
* Sets a callback on a widget of the current dialog.
|
|
* - Arg 1: function.
|
|
* - Args 2..n: path of strings and integers.
|
|
*/
|
|
int intf_set_dialog_callback(lua_State *L)
|
|
{
|
|
gui2::twidget *w = find_widget(L, 2, true);
|
|
|
|
scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
|
|
scoped_dialog::callback_map::iterator i = m.find(w);
|
|
if (i != m.end())
|
|
{
|
|
lua_pushstring(L
|
|
, dlgclbkKey);
|
|
lua_rawget(L, LUA_REGISTRYINDEX);
|
|
lua_pushnil(L);
|
|
lua_rawseti(L, -2, i->second);
|
|
lua_pop(L, 1);
|
|
m.erase(i);
|
|
}
|
|
|
|
if (lua_isnil(L, 1)) return 0;
|
|
|
|
if (gui2::tclickable_ *c = dynamic_cast<gui2::tclickable_ *>(w)) {
|
|
static tdialog_callback_wrapper wrapper;
|
|
c->connect_click_handler(boost::bind(
|
|
&tdialog_callback_wrapper::forward
|
|
, wrapper
|
|
, w));
|
|
} else if (gui2::tselectable_ *s = dynamic_cast<gui2::tselectable_ *>(w)) {
|
|
s->set_callback_state_change(&dialog_callback);
|
|
}
|
|
#ifdef GUI2_EXPERIMENTAL_LISTBOX
|
|
else if (gui2::tlist *l = dynamic_cast<gui2::tlist *>(w)) {
|
|
static tdialog_callback_wrapper wrapper;
|
|
connect_signal_notify_modified(*l
|
|
, boost::bind(
|
|
&tdialog_callback_wrapper::forward
|
|
, wrapper
|
|
, w));
|
|
}
|
|
#else
|
|
else if (gui2::tlistbox *l = dynamic_cast<gui2::tlistbox *>(w)) {
|
|
l->set_callback_value_change(&dialog_callback);
|
|
}
|
|
#endif
|
|
else
|
|
return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
|
|
lua_pushstring(L
|
|
, dlgclbkKey);
|
|
lua_rawget(L, LUA_REGISTRYINDEX);
|
|
int n = lua_rawlen(L, -1) + 1;
|
|
m[w] = n;
|
|
lua_pushvalue(L, 1);
|
|
lua_rawseti(L, -2, n);
|
|
lua_pop(L, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Enables/disables Pango markup on the label of a widget of the current dialog.
|
|
* - Arg 1: boolean.
|
|
* - Args 2..n: path of strings and integers.
|
|
*/
|
|
int intf_set_dialog_markup(lua_State *L)
|
|
{
|
|
bool b = luaW_toboolean(L, 1);
|
|
gui2::twidget *w = find_widget(L, 2, true);
|
|
gui2::tcontrol *c = dynamic_cast<gui2::tcontrol *>(w);
|
|
if (!c) return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
|
|
c->set_use_markup(b);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sets a canvas on a widget of the current dialog.
|
|
* - Arg 1: integer.
|
|
* - Arg 2: WML table.
|
|
* - Args 3..n: path of strings and integers.
|
|
*/
|
|
int intf_set_dialog_canvas(lua_State *L)
|
|
{
|
|
int i = luaL_checkinteger(L, 1);
|
|
gui2::twidget *w = find_widget(L, 3, true);
|
|
gui2::tcontrol *c = dynamic_cast<gui2::tcontrol *>(w);
|
|
if (!c) return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
|
|
std::vector<gui2::tcanvas> &cv = c->canvas();
|
|
if (i < 1 || unsigned(i) > cv.size())
|
|
return luaL_argerror(L, 1, "out of bounds");
|
|
|
|
config cfg = luaW_checkconfig(L, 2);
|
|
cv[i - 1].set_cfg(cfg);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Sets a widget's state to active or inactive
|
|
* - Arg 1: boolean.
|
|
* - Args 2..n: path of strings and integers.
|
|
*/
|
|
int intf_set_dialog_active(lua_State *L)
|
|
{
|
|
const bool b = luaW_toboolean(L, 1);
|
|
gui2::twidget *w = find_widget(L, 2, true);
|
|
gui2::tcontrol *c = dynamic_cast<gui2::tcontrol *>(w);
|
|
if (!c) return luaL_argerror(L, lua_gettop(L), "unsupported widget");
|
|
|
|
c->set_active(b);
|
|
return 0;
|
|
}
|
|
|
|
int show_lua_console(lua_State * /*L*/, CVideo & video, lua_kernel_base * lk)
|
|
{
|
|
gui2::tlua_interpreter::display(video, lk);
|
|
return 0;
|
|
}
|
|
|
|
int show_gamestate_inspector(CVideo & video, const vconfig & cfg)
|
|
{
|
|
gui2::tgamestate_inspector inspect_dialog(cfg);
|
|
inspect_dialog.show(video);
|
|
return 0;
|
|
}
|
|
|
|
} // end namespace lua_gui2
|