The Battle for Wesnoth  1.17.0-dev
lua_widget_methods.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2020 - 2021
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 
16 #include "config.hpp"
17 #include "gui/core/canvas.hpp"
19 #include "gui/core/event/handler.hpp" // for open_window_stack
23 #include "gui/widgets/listbox.hpp"
28 #include "gui/widgets/slider.hpp"
30 #include "gui/widgets/text_box.hpp"
34 #include "gui/widgets/widget.hpp"
35 #include "gui/widgets/window.hpp"
36 #include "log.hpp"
37 #include "scripting/lua_common.hpp"
40 #include "scripting/lua_ptr.hpp"
41 #include "scripting/lua_widget.hpp"
43 #include "scripting/push_check.hpp"
45 #include "tstring.hpp"
46 #include <functional>
47 
48 #include <type_traits>
49 #include <map>
50 #include <utility>
51 #include <vector>
52 
53 #include "lua/lauxlib.h"
54 #include "lua/lua.h"
55 
56 static lg::log_domain log_scripting_lua("scripting/lua");
57 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
58 
59 /**
60  * Displays a window.
61  * - Arg 1: WML table describing the window.
62  * - Arg 2: function called at pre-show.
63  * - Arg 3: function called at post-show.
64  * - Ret 1: integer.
65  */
67 {
68  config def_cfg = luaW_checkconfig(L, 1);
70 
71  std::unique_ptr<gui2::window> wp(gui2::build(def));
72 
73  if(!lua_isnoneornil(L, 2)) {
74  lua_pushvalue(L, 2);
75  luaW_pushwidget(L, *wp);
76  lua_call(L, 1, 0);
77  }
78 
79  gui2::open_window_stack.push_back(wp.get());
80  int v = wp->show(true, 0);
82 
83  if (!lua_isnoneornil(L, 3)) {
84  lua_pushvalue(L, 3);
85  luaW_pushwidget(L, *wp);
86  lua_call(L, 1, 0);
87  }
88  luaW_clearwindowtable(L, wp.get());
89  lua_pushinteger(L, v);
90  return 1;
91 }
92 
93 static gui2::widget* find_widget_impl(lua_State* L, gui2::widget* w, int i, bool readonly)
94 {
95  assert(w);
96 
97  for(; !lua_isnoneornil(L, i); ++i)
98  {
99  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
100  {
101  int v = lua_tointeger(L, i);
102  if(v < 1) {
103  throw std::invalid_argument("negative index");
104  }
105  int n = list->get_item_count();
106  if(v > n) {
107  if(readonly) {
108  throw std::invalid_argument("index out of range");
109  }
111  for(; n < v; ++n) {
112  list->add_row(dummy);
113  }
114  }
115  w = list->get_row_grid(v - 1);
116  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
117  int v = lua_tointeger(L, i);
118  if(v < 1) {
119  throw std::invalid_argument("negative index");
120  }
121  int n = multi_page->get_page_count();
122  if(v > n) {
123  if(readonly) {
124  throw std::invalid_argument("index out of range");
125  }
127  for(; n < v; ++n) {
128  multi_page->add_page(dummy);
129  }
130  }
131  w = &multi_page->page_grid(v - 1);
132  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
133  gui2::tree_view_node& tvn = tree_view->get_root_node();
134  if(lua_isnumber(L, i)) {
135  int v = lua_tointeger(L, i);
136  if(v < 1) {
137  throw std::invalid_argument("negative index");
138  }
139  int n = tvn.count_children();
140  if(v > n) {
141  throw std::invalid_argument("index out of range");
142  }
143  w = &tvn.get_child_at(v - 1);
144 
145  } else {
146  std::string m = luaL_checkstring(L, i);
147  w = tvn.find(m, false);
148  }
149  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
150  if(lua_isnumber(L, i)) {
151  int v = lua_tointeger(L, i);
152  if(v < 1) {
153  throw std::invalid_argument("negative index");
154  }
155  int n = tree_view_node->count_children();
156  if(v > n) {
157  throw std::invalid_argument("index out of range");
158  }
159  w = &tree_view_node->get_child_at(v - 1);
160 
161  } else {
162  std::string m = luaL_checkstring(L, i);
163  w = tree_view_node->find(m, false);
164  }
165  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
166  if(lua_isnumber(L, i)) {
167  int v = lua_tointeger(L, i);
168  if(v < 1) {
169  throw std::invalid_argument("negative index");
170  }
171  int n = stacked_widget->get_layer_count();
172  if(v > n) {
173  throw std::invalid_argument("index out of range");
174  }
175  w = stacked_widget->get_layer_grid(v - 1);
176  } else {
177  std::string m = luaL_checkstring(L, i);
178  w = stacked_widget->find(m, false);
179  }
180  } else {
181  char const *m = lua_tostring(L, i);
182  if(!m) {
183  throw std::invalid_argument("expected a string");
184  }
185  w = w->find(m, false);
186  }
187  if(!w) {
188  throw std::invalid_argument("widget not found");
189  }
190  }
191 
192  return w;
193 }
194 
196 {
197  gui2::widget* w = &luaW_checkwidget(L, 1);
198  auto pw = find_widget_impl(L, w, 2, false);
199  if(pw) {
200  luaW_pushwidget(L, *pw);
201  return 1;
202  }
203  return 0;
204 }
205 
206 
207 namespace
208 {
209  void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
210  {
211  //Not tested yet.
212  gui2::tree_view& tv = node.get_tree_view();
213  if(pos >= node.count_children()) {
214  return;
215  }
216  if(number <= 0 || number + pos > node.count_children()) {
217  number = node.count_children() - pos;
218  }
219  for(int i = 0; i < number; ++i) {
220  tv.remove_node(&node.get_child_at(pos));
221  }
222  }
223 }
224 
225 /**
226  * Removes an entry from a list.
227  * - Arg 1: widget
228  * - Arg 2: number, index of the element to delete.
229  * - Arg 3: number, number of the elements to delete. (0 to delete all elements after index)
230  */
232 {
233  gui2::widget* w = &luaW_checkwidget(L, 1);
234  int pos = luaL_checkinteger(L, 2) - 1;
235  int number = luaL_checkinteger(L, 3);
236 
237  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
238  {
239  list->remove_row(pos, number);
240  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
241  multi_page->remove_page(pos, number);
242  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
243  remove_treeview_node(tree_view->get_root_node(), pos, number);
244  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
245  remove_treeview_node(*tree_view_node, pos, number);
246  } else {
247  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
248  }
249 
250  return 1;
251 }
252 
253 namespace { // helpers of intf_set_dialog_callback()
254  void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
255  {
256  gui2::widget* w = wp.get_ptr();
257  if(!w) {
258  ERR_LUA << "widget was deleted\n";
259  return;
260  }
261  gui2::window* wd = w->get_window();
262  if(!wd) {
263  ERR_LUA << "cannot find window in widget callback\n";
264  return;
265  }
266  luaW_callwidgetcallback(L, w, wd, id);
267  }
268 }//unnamed namespace for helpers of intf_set_dialog_callback()
269 
270 /**
271  * Sets a callback on a widget of the current dialog.
272  * - Arg 1: widget.
273  * - Arg 2: function.
274  */
276 {
278  gui2::widget* w = wp.get_ptr();
279  assert(w);
280  gui2::window* wd = w->get_window();
281  if(!wd) {
282  throw std::invalid_argument("the widget has no window assigned");
283  }
284 
285  lua_pushvalue(L, 2);
286  bool already_exists = luaW_setwidgetcallback(L, w, wd, "callback");
287  if(already_exists) {
288  return 0;
289  }
290 
291  // TODO: i am not sure whether it is 100% safe to bind the lua_state here,
292  // (meaning whether it can happen that the lus state is destroyed)
293  // when a widgets callback is called.
294  if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
295  c->connect_click_handler(std::bind(&dialog_callback, L, wp, "callback"));
296  } else if( dynamic_cast<gui2::selectable_item*>(w)) {
297  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
298  } else if(dynamic_cast<gui2::integer_selector*>(w)) {
299  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
300  } else if(dynamic_cast<gui2::listbox*>(w)) {
301  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
302  } else if(dynamic_cast<gui2::tree_view*>(w)) {
303  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
304  } else {
305  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
306  };
307 
308  return 0;
309 }
310 
311 
312 /**
313  * Sets a canvas on a widget of the current dialog.
314  * - Arg 1: widget.
315  * - Arg 2: integer.
316  * - Arg 3: WML table.
317  */
319 {
320  gui2::widget* w = &luaW_checkwidget(L, 1);
321  int i = luaL_checkinteger(L, 2);
322  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
323  if(!c) {
324  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
325  }
326 
327  std::vector<gui2::canvas> &cv = c->get_canvases();
328  if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
329  return luaL_argerror(L, 2, "out of bounds");
330  }
331 
332  config cfg = luaW_checkconfig(L, 3);
333  cv[i - 1].set_cfg(cfg);
334  c->set_is_dirty(true);
335  return 0;
336 }
337 
338 /**
339  * Sets a widget to have the focus
340  * - Arg 1: widget.
341  */
343 {
344  gui2::widget* w = &luaW_checkwidget(L, 1);
345  if(gui2::window* wd = w->get_window()) {
346  wd->keyboard_capture(w);
347  }
348  return 0;
349 }
350 
351 
352 /**
353  * Sets a widget's state to active or inactive
354  * - Arg 1: widget.
355  * - Arg 2: string, the type (id of [node_definition]) of the new node.
356  * - Arg 3: integer, where to insert the new node.
357  */
359 {
360  gui2::widget* w = &luaW_checkwidget(L, 1);
361  gui2::widget* res = nullptr;
362  const std::string node_type = luaL_checkstring(L, 2);
363  int insert_pos = -1;
364  if(lua_isnumber(L, 3)) {
365  insert_pos = luaL_checkinteger(L, 3);
366  }
367  static const std::map<std::string, string_map> data;
368 
369  if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
370  res = &twn->add_child(node_type, data, insert_pos);
371  } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
372  res = &tw->get_root_node().add_child(node_type, data, insert_pos);
373  } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
374  res = &mp->add_page(node_type, insert_pos, data);
375  } else {
376  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
377  }
378  if(res) {
379  luaW_pushwidget(L, *res);
380  lua_push(L, insert_pos);
381  return 2;
382  }
383  return 0;
384 }
385 /**
386  * Sets a widget's state to active or inactive
387  * - Arg 1: widget.
388  */
390 {
391 
392  gui2::widget* w = &luaW_checkwidget(L, 1);
393  gui2::widget* res = nullptr;
394  static const std::map<std::string, string_map> data;
395 
396  if(gui2::listbox* lb = dynamic_cast<gui2::listbox*>(w)) {
397  res = &lb->add_row(data);
398  } else {
399  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
400  }
401  if(res) {
402  luaW_pushwidget(L, *res);
403  return 1;
404  }
405  return 0;
406 }
407 
408 /** Closes a window */
410 {
411  gui2::widget* w = &luaW_checkwidget(L, 1);
412  if(gui2::window* wd = dynamic_cast<gui2::window*>(w)) {
413  wd->close();
414  return 0;
415  } else {
416  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
417  }
418 }
419 namespace lua_widget {
421 {
422  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
423  lk.add_log("Adding widgets module...\n");
424  static luaL_Reg const gui_callbacks[] = {
425  //TODO: the naming is a bit arbitrary: widgets with different
426  // types of elements use add_node, eidgets with only
427  // one type of element use add_element
428  { "add_item_of_type", &intf_add_item_of_type },
429  { "add_item", &intf_add_dialog_item },
430  { "focus", &intf_set_dialog_focus },
431  { "set_canvas", &intf_set_dialog_canvas },
432  { "set_callback", &intf_set_dialog_callback },
433  { "remove_items_at", &intf_remove_dialog_item },
434  { "find", &intf_find_widget },
435  { "close", &intf_dialog_close },
436  { nullptr, nullptr },
437  };
438  lua_newtable(L);
439  luaL_setfuncs(L, gui_callbacks, 0);
440  return 1;
441 }
442 }
static int intf_set_dialog_callback(lua_State *L)
Sets a callback on a widget of the current dialog.
#define lua_isnoneornil(L, n)
Definition: lua.h:379
std::map< std::string, t_string > string_map
Key Type Default Description window_width unsigned 0 Width of the application window.
int dummy
Definition: lstrlib.cpp:1347
virtual widget * find(const std::string &id, const bool must_be_active)
Returns a widget with the wanted id.
Definition: widget.cpp:584
static int intf_dialog_close(lua_State *L)
Closes a window.
static lg::log_domain log_scripting_lua("scripting/lua")
LUA_API int lua_gettop(lua_State *L)
Definition: lapi.cpp:168
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:19
This file contains the window object, this object is a top level container which has the event manage...
Base class for all widgets.
Definition: widget.hpp:49
#define lua_tointeger(L, i)
Definition: lua.h:362
void luaW_pushwidget(lua_State *L, gui2::widget &w)
Definition: lua_widget.cpp:36
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:385
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
T * get_ptr()
Definition: lua_ptr.hpp:37
Definitions for the interface to Wesnoth Markup Language (WML).
static gui2::widget * find_widget_impl(lua_State *L, gui2::widget *w, int i, bool readonly)
std::size_t count_children() const
The number of children in this widget.
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:52
The listbox class.
Definition: listbox.hpp:43
#define ERR_LUA
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.
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:187
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:466
static int intf_set_dialog_canvas(lua_State *L)
Sets a canvas on a widget of the current dialog.
void remove_from_window_stack(window *window)
Removes a entry from the open_window_stack list.
Definition: handler.cpp:1106
This file contains the canvas object which is the part where the widgets draw (temporally) images on...
std::vector< canvas > & get_canvases()
A tree view is a control that holds several items of the same or different types. ...
Definition: tree_view.hpp:60
std::pair< std::shared_ptr< tree_view_node >, int > remove_node(tree_view_node *node)
Removes the given node as a child of its parent node.
Definition: tree_view.cpp:63
void luaW_callwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
callas a widgets callback [-0, +0, e]
Definition: lua_widget.cpp:175
#define lua_newtable(L)
Definition: lua.h:366
static int intf_add_item_of_type(lua_State *L)
Sets a widget&#39;s state to active or inactive.
std::size_t i
Definition: function.cpp:967
int show(const bool restore=true, const unsigned auto_close_timeout=0)
Shows the window.
Definition: window.cpp:492
LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)
Definition: lauxlib.cpp:442
gui2::widget & luaW_checkwidget(lua_State *L, int n)
Definition: lua_widget.cpp:42
tree_view_node & get_child_at(int index)
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:901
static int intf_set_dialog_focus(lua_State *L)
Sets a widget to have the focus.
window * get_window()
Get the parent window.
Definition: widget.cpp:117
int luaW_open(lua_State *L)
#define lua_tostring(L, i)
Definition: lua.h:386
int w
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:246
lua_ptr< gui2::widget > & luaW_checkwidget_ptr(lua_State *L, int n)
Definition: lua_widget.cpp:52
Base class for all visible items.
LUA_API int lua_isnumber(lua_State *L, int idx)
Definition: lapi.cpp:285
#define lua_call(L, n, r)
Definition: lua.h:283
tree_view & get_tree_view()
A multi page is a control that contains several &#39;pages&#39; of which only one is visible.
Definition: multi_page.hpp:49
Standard logging facilities (interface).
Small concept class.
static int intf_remove_dialog_item(lua_State *L)
Removes an entry from a list.
std::unique_ptr< window > build(const builder_window::window_resolution &definition)
Builds a window.
void luaW_clearwindowtable(lua_State *L, gui2::window *owner)
[-0, +0, -]
Definition: lua_widget.cpp:104
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
Definition: lauxlib.cpp:904
A stacked widget holds several widgets on top of each other.
bool luaW_setwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
returns true if a callback already existed.
Definition: lua_widget.cpp:141
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
mock_char c
static map_location::DIRECTION n
LUA_API void lua_pushinteger(lua_State *L, lua_Integer n)
Definition: lapi.cpp:489
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:65
static int intf_add_dialog_item(lua_State *L)
Sets a widget&#39;s state to active or inactive.
static int intf_find_widget(lua_State *L)
std::vector< window * > open_window_stack
Keeps track of any open windows of any type (modal, non-modal, or tooltip) in the order in which they...
Definition: handler.cpp:1104
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138