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