The Battle for Wesnoth  1.17.0-dev
lua_gui2.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include "scripting/lua_gui2.hpp"
17 
26 #include "gui/dialogs/message.hpp"
27 #include "gui/widgets/retval.hpp"
29 #include "scripting/lua_widget_methods.hpp" //intf_show_dialog
30 
31 #include "config.hpp"
32 #include "log.hpp"
33 #include "scripting/lua_common.hpp"
37 #include "scripting/push_check.hpp"
39 #include "help/help.hpp"
40 #include "game_config_manager.hpp"
41 #include "tstring.hpp"
42 #include "game_data.hpp"
43 #include "game_state.hpp"
44 
45 #include <functional>
46 #include <optional>
47 
48 #include <map>
49 #include <utility>
50 #include <vector>
51 
52 #include "lua/lauxlib.h" // for luaL_checkinteger, etc
53 #include "lua/lua.h" // for lua_setfield, etc
54 
55 static lg::log_domain log_scripting_lua("scripting/lua");
56 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
57 
58 namespace lua_gui2 {
59 
60 
61 /**
62  * Displays a message window
63  * - Arg 1: Table describing the window
64  * - Arg 2: List of options (nil or empty table - no options)
65  * - Arg 3: Text input specifications (nil or empty table - no text input)
66  * - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
67  * - Ret 2: string entered (empty if none, nil if no text input)
68  */
70 {
71  config txt_cfg;
72  const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
73 
75  input.caption = txt_cfg["label"].str();
76  input.text = txt_cfg["text"].str();
77  input.maximum_length = txt_cfg["max_length"].to_int(256);
78  input.text_input_was_specified = has_input;
79 
81  if(!lua_isnoneornil(L, 2)) {
83  std::size_t n = lua_rawlen(L, 2);
84  for(std::size_t i = 1; i <= n; i++) {
85  lua_rawgeti(L, 2, i);
86  t_string short_opt;
87  config opt;
88  if(luaW_totstring(L, -1, short_opt)) {
89  opt["description"] = short_opt;
90  } else if(!luaW_toconfig(L, -1, opt)) {
91  std::ostringstream error;
92  error << "expected array of config and/or translatable strings, but index ";
93  error << i << " was a " << lua_typename(L, lua_type(L, -1));
94  return luaL_argerror(L, 2, error.str().c_str());
95  }
96  gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
97  if(opt["default"].to_bool(false)) {
98  options.chosen_option = i - 1;
99  }
100  options.option_list.push_back(option);
101  lua_pop(L, 1);
102  }
103  lua_getfield(L, 2, "default");
104  if(lua_isnumber(L, -1)) {
105  int i = lua_tointeger(L, -1);
106  if(i < 1 || std::size_t(i) > n) {
107  std::ostringstream error;
108  error << "default= key in options list is not a valid option index (1-" << n << ")";
109  return luaL_argerror(L, 2, error.str().c_str());
110  }
111  options.chosen_option = i - 1;
112  }
113  lua_pop(L, 1);
114  }
115 
116  const config& def_cfg = luaW_checkconfig(L, 1);
117  const std::string& title = def_cfg["title"];
118  const std::string& message = def_cfg["message"];
119 
120  using portrait = gui2::dialogs::wml_message_portrait;
121  std::unique_ptr<portrait> left;
122  std::unique_ptr<portrait> right;
123  const bool is_double = def_cfg.has_attribute("second_portrait");
124  const bool left_side = def_cfg["left_side"].to_bool(true);
125  if(is_double || left_side) {
126  left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
127  } else {
128  // This means right side only.
129  right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
130  }
131  if(is_double) {
132  right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
133  }
134 
135  int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);
136 
137  if(!has_input && options.option_list.empty()) {
138  lua_pushinteger(L, dlg_result);
139  } else {
140  lua_pushinteger(L, options.chosen_option + 1);
141  }
142 
143  if(has_input) {
144  lua_pushlstring(L, input.text.c_str(), input.text.length());
145  } else {
146  lua_pushnil(L);
147  }
148 
149  return 2;
150 }
151 
152 /**
153  * Displays a popup message
154  * - Arg 1: Title (allows Pango markup)
155  * - Arg 2: Message (allows Pango markup)
156  * - Arg 3: Image (optional)
157  */
159  t_string title = luaW_checktstring(L, 1);
161  std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
162 
163  gui2::show_transient_message(title, msg, image, true, true);
164  return 0;
165 }
166 
167 /**
168  * Displays a story screen
169  * - Arg 1: The story config
170  * - Arg 2: The default title
171  */
173  config story = luaW_checkconfig(L, 1);
174  t_string title = luaW_checktstring(L, 2);
176  return 0;
177 }
178 
179 /**
180  * Displays a popup menu at the current mouse position
181  * Best used from a [set_menu_item], to show a submenu
182  * - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
183  * - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
184  */
186  std::vector<config> items = lua_check<std::vector<config>>(L, 1);
187  SDL_Rect pos {1,1,1,1};
188  SDL_GetMouseState(&pos.x, &pos.y);
189 
190  int initial = -1;
191  bool markup = false;
192  if(lua_isnumber(L, 2)) {
193  initial = lua_tointeger(L, 2) - 1;
194  markup = luaW_toboolean(L, 3);
195  } else if(lua_isnumber(L, 3)) {
196  initial = lua_tointeger(L, 3) - 1;
197  markup = luaW_toboolean(L, 2);
198  } else if(lua_isboolean(L, 2)) {
199  markup = luaW_toboolean(L, 2);
200  }
201 
202  gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
203  menu.show();
204  lua_pushinteger(L, menu.selected_item() + 1);
205  return 1;
206 }
207 
208 /**
209  * Displays a simple message box.
210  */
212  const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
213  std::string button = luaL_optstring(L, 3, "ok"), btn_style;
214  std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
215  bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
216  using button_style = gui2::dialogs::message::button_style;
217  std::optional<button_style> style;
218  if(btn_style.empty()) {
219  style = button_style::auto_close;
220  } else if(btn_style == "ok") {
221  style = button_style::ok_button;
222  } else if(btn_style == "close") {
223  style = button_style::close_button;
224  } else if(btn_style == "ok_cancel") {
225  style = button_style::ok_cancel_buttons;
226  } else if(btn_style == "cancel") {
227  style = button_style::cancel_button;
228  } else if(btn_style == "yes_no") {
229  style = button_style::yes_no_buttons;
230  }
231  if(style) {
232  int result = gui2::show_message(title, message, *style, markup, markup);
233  if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
234  lua_pushboolean(L, result == gui2::retval::OK);
235  return 1;
236  }
237  } else {
238  gui2::show_message(title, message, button, false, markup, markup);
239  }
240  return 0;
241 }
242 
244 {
246  return 0;
247 }
248 
249 int show_gamestate_inspector(const vconfig& cfg, const game_data& data, const game_state& state)
250 {
251  gui2::dialogs::gamestate_inspector::display(data.get_variables(), *state.events_manager_, state.board_, cfg["name"]);
252  return 0;
253 }
254 
255 static int show_help(lua_State *L)
256 {
258  return 0;
259 }
260 
261 /**
262  * - Arg 1: string, widget type
263  * - Arg 3: string, id
264  * - Arg 3: config,
265  */
266 
268 {
269  std::string type = luaL_checkstring(L, 1);
270  std::string id = luaL_checkstring(L, 2);
271  try {
273  lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
274  }
275  } catch(const std::invalid_argument& e) {
276  return luaL_argerror(L, 1, e.what());
277  }
278  return 0;
279 }
280 
282 {
283  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
284  lk.add_log("Adding gui module...\n");
285  static luaL_Reg const gui_callbacks[] = {
286  { "show_menu", &show_menu },
287  { "show_narration", &show_message_dialog },
288  { "show_popup", &show_popup_dialog },
289  { "show_story", &show_story },
290  { "show_prompt", &show_message_box },
291  { "show_help", &show_help },
292  { "add_widget_definition", &intf_add_widget_definition },
293  { "show_dialog", &intf_show_dialog },
294  { nullptr, nullptr },
295  };
296  std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
297  {"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, std::placeholders::_1)},
298  {nullptr, nullptr}
299  };
300  lua_newtable(L);
301  luaL_setfuncs(L, gui_callbacks, 0);
302  lua_cpp::set_functions(L, cpp_gui_callbacks);
303 
304  lua_pushstring(L, "widget");
306  lua_rawset(L, -3);
307 
308  return 1;
309 }
310 
311 } // end namespace lua_gui2
#define lua_isnoneornil(L, n)
Definition: lua.h:379
Parameter pack for message portrait.
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:153
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition: lua_gui2.cpp:243
bool text_input_was_specified
True when [text_input] appeared in [message].
static void display(const std::string &scenario_name, const config &story)
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:144
int show_story(lua_State *L)
Displays a story screen.
Definition: lua_gui2.cpp:172
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:260
std::string caption
The caption for the optional input text box.
LUA_API void lua_pushboolean(lua_State *L, int b)
Definition: lapi.cpp:581
LUA_API int lua_rawgeti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:710
std::string text
The initial text value.
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:40
bool has_attribute(config_key_type key) const
Definition: config.cpp:211
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup, const bool restore_background)
Shows a transient message to the user.
button_style
Selects the style of the buttons to be shown.
Definition: message.hpp:69
#define lua_tointeger(L, i)
Definition: lua.h:362
Parameter pack for message list input options.
unsigned maximum_length
The maximum length of the text.
static int show_help(lua_State *L)
Definition: lua_gui2.cpp:255
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
const std::vector< std::string > items
const config & get_variables() const
Definition: game_data.hpp:34
int show_message_dialog(lua_State *L)
Displays a message window.
Definition: lua_gui2.cpp:69
bool show(const unsigned auto_close_time=0)
Shows the window.
Definitions for the interface to Wesnoth Markup Language (WML).
int luaW_open(lua_State *L)
Definition: lua_gui2.cpp:281
int intf_add_widget_definition(lua_State *L)
Definition: lua_gui2.cpp:267
const std::unique_ptr< game_events::manager > events_manager_
Definition: game_state.hpp:53
#define lua_pop(L, n)
Definition: lua.h:364
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
const config & options()
Definition: game.cpp:569
int show_gamestate_inspector(const vconfig &cfg, const game_data &data, const game_state &state)
Definition: lua_gui2.cpp:249
LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)
Definition: lauxlib.cpp:175
int intf_show_dialog(lua_State *L)
Displays a window.
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:972
int intf_show_lua_console(lua_State *L)
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:627
Used by the menu_button widget.
LUA_API const char * lua_pushlstring(lua_State *L, const char *s, size_t len)
Definition: lapi.cpp:502
int show_menu(lua_State *L)
Displays a popup menu at the current mouse position Best used from a [set_menu_item], to show a submenu.
Definition: lua_gui2.cpp:185
#define lua_newtable(L)
Definition: lua.h:366
LUA_API void lua_pushnil(lua_State *L)
Definition: lapi.cpp:473
LUALIB_API void luaL_checktype(lua_State *L, int arg, int t)
Definition: lauxlib.cpp:390
lu_byte right
Definition: lparser.cpp:1227
#define lua_isboolean(L, n)
Definition: lua.h:376
static lg::log_domain log_scripting_lua("scripting/lua")
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:824
int show_popup_dialog(lua_State *L)
Displays a popup message.
Definition: lua_gui2.cpp:158
bool luaW_totstring(lua_State *L, int index, t_string &str)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:602
std::size_t i
Definition: function.cpp:967
int show_wml_message(const std::string &title, const std::string &message, const wml_message_portrait *left, const wml_message_portrait *right, const wml_message_options &options, const wml_message_input &input)
Helper function to show a portrait.
LUA_API void lua_rawset(lua_State *L, int idx)
Definition: lapi.cpp:877
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:901
Helper class for message options.
Definition: wml_message.hpp:26
int luaW_open(lua_State *L)
LUA_API int lua_isnumber(lua_State *L, int idx)
Definition: lapi.cpp:285
LUA_API lua_Unsigned lua_rawlen(lua_State *L, int idx)
Definition: lapi.cpp:402
lu_byte left
Definition: lparser.cpp:1226
game_board board_
Definition: game_state.hpp:47
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
Functions to load and save images from/to disk.
Standard logging facilities (interface).
bool add_single_widget_definition(const std::string &widget_type, const std::string &definition_id, const config &cfg)
Adds a widget definition to the default GUI.
#define e
Dialog was closed with the OK button.
Definition: retval.hpp:35
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
Definition: lauxlib.cpp:904
int show_message_box(lua_State *L)
Displays a simple message box.
Definition: lua_gui2.cpp:211
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
mock_char c
LUA_API int lua_getfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:655
#define LUA_TTABLE
Definition: lua.h:70
static map_location::DIRECTION n
LUA_API void lua_pushinteger(lua_State *L, lua_Integer n)
Definition: lapi.cpp:489
#define luaL_optstring(L, n, d)
Definition: lauxlib.h:139
LUA_API const char * lua_pushstring(lua_State *L, const char *s)
Definition: lapi.cpp:514
bool empty() const
Definition: config.cpp:941
LUA_API const char * lua_typename(lua_State *L, int t)
Definition: lapi.cpp:266
Parameter pack for message text input options.
void set_functions(lua_State *L, const std::vector< lua_cpp::Reg > &functions)
Analogous to lua_setfuncs, it registers a collection of function wrapper objects into a table...
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138