The Battle for Wesnoth  1.15.11+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"
18 #include "gui/core/event/handler.hpp" // for open_window_stack
22 #include "gui/widgets/listbox.hpp"
27 #include "gui/widgets/slider.hpp"
29 #include "gui/widgets/text_box.hpp"
33 #include "gui/widgets/widget.hpp"
34 #include "gui/widgets/window.hpp"
35 #include "log.hpp"
36 #include "scripting/lua_common.hpp"
39 #include "scripting/lua_ptr.hpp"
40 #include "scripting/lua_widget.hpp"
42 #include "scripting/push_check.hpp"
44 #include "tstring.hpp"
45 #include <functional>
46 
47 #include <type_traits>
48 #include <map>
49 #include <utility>
50 #include <vector>
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(gui2::build(def));
71 
72  if(!lua_isnoneornil(L, 2)) {
73  lua_pushvalue(L, 2);
74  luaW_pushwidget(L, *wp);
75  lua_call(L, 1, 0);
76  }
77 
78  gui2::open_window_stack.push_back(wp.get());
79  int v = wp->show(true, 0);
81 
82  if (!lua_isnoneornil(L, 3)) {
83  lua_pushvalue(L, 3);
84  luaW_pushwidget(L, *wp);
85  lua_call(L, 1, 0);
86  }
87  luaW_clearwindowtable(L, wp.get());
88  lua_pushinteger(L, v);
89  return 1;
90 }
91 
92 static gui2::widget* find_widget_impl(lua_State* L, gui2::widget* w, int i, bool readonly)
93 {
94  assert(w);
95 
96  for(; !lua_isnoneornil(L, i); ++i)
97  {
98  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
99  {
100  int v = lua_tointeger(L, i);
101  if(v < 1) {
102  throw std::invalid_argument("negative index");
103  }
104  int n = list->get_item_count();
105  if(v > n) {
106  if(readonly) {
107  throw std::invalid_argument("index out of range");
108  }
110  for(; n < v; ++n) {
111  list->add_row(dummy);
112  }
113  }
114  w = list->get_row_grid(v - 1);
115  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
116  int v = lua_tointeger(L, i);
117  if(v < 1) {
118  throw std::invalid_argument("negative index");
119  }
120  int n = multi_page->get_page_count();
121  if(v > n) {
122  if(readonly) {
123  throw std::invalid_argument("index out of range");
124  }
126  for(; n < v; ++n) {
127  multi_page->add_page(dummy);
128  }
129  }
130  w = &multi_page->page_grid(v - 1);
131  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
132  gui2::tree_view_node& tvn = tree_view->get_root_node();
133  if(lua_isnumber(L, i)) {
134  int v = lua_tointeger(L, i);
135  if(v < 1) {
136  throw std::invalid_argument("negative index");
137  }
138  int n = tvn.count_children();
139  if(v > n) {
140  throw std::invalid_argument("index out of range");
141  }
142  w = &tvn.get_child_at(v - 1);
143 
144  } else {
145  std::string m = luaL_checkstring(L, i);
146  w = tvn.find(m, false);
147  }
148  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
149  if(lua_isnumber(L, i)) {
150  int v = lua_tointeger(L, i);
151  if(v < 1) {
152  throw std::invalid_argument("negative index");
153  }
154  int n = tree_view_node->count_children();
155  if(v > n) {
156  throw std::invalid_argument("index out of range");
157  }
158  w = &tree_view_node->get_child_at(v - 1);
159 
160  } else {
161  std::string m = luaL_checkstring(L, i);
162  w = tree_view_node->find(m, false);
163  }
164  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
165  if(lua_isnumber(L, i)) {
166  int v = lua_tointeger(L, i);
167  if(v < 1) {
168  throw std::invalid_argument("negative index");
169  }
170  int n = stacked_widget->get_layer_count();
171  if(v > n) {
172  throw std::invalid_argument("index out of range");
173  }
174  w = stacked_widget->get_layer_grid(v - 1);
175  } else {
176  std::string m = luaL_checkstring(L, i);
177  w = stacked_widget->find(m, false);
178  }
179  } else {
180  char const *m = lua_tostring(L, i);
181  if(!m) {
182  throw std::invalid_argument("expected a string");
183  }
184  w = w->find(m, false);
185  }
186  if(!w) {
187  throw std::invalid_argument("widget not found");
188  }
189  }
190 
191  return w;
192 }
193 
195 {
196  gui2::widget* w = &luaW_checkwidget(L, 1);
197  auto pw = find_widget_impl(L, w, 2, false);
198  if(pw) {
199  luaW_pushwidget(L, *pw);
200  return 1;
201  }
202  return 0;
203 }
204 
205 
206 namespace
207 {
208  void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
209  {
210  //Not tested yet.
211  gui2::tree_view& tv = node.get_tree_view();
212  if(pos >= node.count_children()) {
213  return;
214  }
215  if(number <= 0 || number + pos > node.count_children()) {
216  number = node.count_children() - pos;
217  }
218  for(int i = 0; i < number; ++i) {
219  tv.remove_node(&node.get_child_at(pos));
220  }
221  }
222 }
223 
224 /**
225  * Removes an entry from a list.
226  * - Arg 1: widget
227  * - Arg 2: number, index of the element to delete.
228  * - Arg 3: number, number of the elements to delete. (0 to delete all elements after index)
229  */
231 {
232  gui2::widget* w = &luaW_checkwidget(L, 1);
233  int pos = luaL_checkinteger(L, 2) - 1;
234  int number = luaL_checkinteger(L, 3);
235 
236  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
237  {
238  list->remove_row(pos, number);
239  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
240  multi_page->remove_page(pos, number);
241  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
242  remove_treeview_node(tree_view->get_root_node(), pos, number);
243  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
244  remove_treeview_node(*tree_view_node, pos, number);
245  } else {
246  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
247  }
248 
249  return 1;
250 }
251 
252 namespace { // helpers of intf_set_dialog_callback()
253  void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
254  {
255  gui2::widget* w = wp.get_ptr();
256  if(!w) {
257  ERR_LUA << "widget was deleted\n";
258  return;
259  }
260  gui2::window* wd = w->get_window();
261  if(!wd) {
262  ERR_LUA << "cannot find window in widget callback\n";
263  return;
264  }
265  luaW_callwidgetcallback(L, w, wd, id);
266  }
267 }//unnamed namespace for helpers of intf_set_dialog_callback()
268 
269 /**
270  * Sets a callback on a widget of the current dialog.
271  * - Arg 1: widget.
272  * - Arg 2: function.
273  */
275 {
277  gui2::widget* w = wp.get_ptr();
278  assert(w);
279  gui2::window* wd = w->get_window();
280  if(!wd) {
281  throw std::invalid_argument("the widget has no window assigned");
282  }
283 
284  lua_pushvalue(L, 2);
285  bool already_exists = luaW_setwidgetcallback(L, w, wd, "callback");
286  if(already_exists) {
287  return 0;
288  }
289 
290  // TODO: i am not sure whether it is 100% safe to bind the lua_state here,
291  // (meaning whether it can happen that the lus state is destroyed)
292  // when a widgets callback is called.
293  if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
294  c->connect_click_handler(std::bind(&dialog_callback, L, wp, "callback"));
295  } else if( dynamic_cast<gui2::selectable_item*>(w)) {
296  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
297  } else if(dynamic_cast<gui2::integer_selector*>(w)) {
298  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
299  } else if(dynamic_cast<gui2::listbox*>(w)) {
300  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
301  } else if(dynamic_cast<gui2::tree_view*>(w)) {
302  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
303  } else {
304  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
305  };
306 
307  return 0;
308 }
309 
310 
311 /**
312  * Sets a canvas on a widget of the current dialog.
313  * - Arg 1: widget.
314  * - Arg 2: integer.
315  * - Arg 3: WML table.
316  */
318 {
319  gui2::widget* w = &luaW_checkwidget(L, 1);
320  int i = luaL_checkinteger(L, 2);
321  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
322  if(!c) {
323  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
324  }
325 
326  std::vector<gui2::canvas> &cv = c->get_canvases();
327  if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
328  return luaL_argerror(L, 2, "out of bounds");
329  }
330 
331  config cfg = luaW_checkconfig(L, 3);
332  cv[i - 1].set_cfg(cfg);
333  c->set_is_dirty(true);
334  return 0;
335 }
336 
337 /**
338  * Sets a widget to have the focus
339  * - Arg 1: widget.
340  */
342 {
343  gui2::widget* w = &luaW_checkwidget(L, 1);
344  if(gui2::window* wd = w->get_window()) {
345  wd->keyboard_capture(w);
346  }
347  return 0;
348 }
349 
350 
351 /**
352  * Sets a widget's state to active or inactive
353  * - Arg 1: widget.
354  * - Arg 2: string, the type (id of [node_definition]) of the new node.
355  * - Arg 3: integer, where to insert the new node.
356  */
358 {
359  gui2::widget* w = &luaW_checkwidget(L, 1);
360  gui2::widget* res = nullptr;
361  const std::string node_type = luaL_checkstring(L, 2);
362  int insert_pos = -1;
363  if(lua_isnumber(L, 3)) {
364  insert_pos = luaL_checkinteger(L, 3);
365  }
366  static const std::map<std::string, string_map> data;
367 
368  if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
369  res = &twn->add_child(node_type, data, insert_pos);
370  } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
371  res = &tw->get_root_node().add_child(node_type, data, insert_pos);
372  } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
373  res = &mp->add_page(node_type, insert_pos, data);
374  } else {
375  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
376  }
377  if(res) {
378  luaW_pushwidget(L, *res);
379  lua_push(L, insert_pos);
380  return 2;
381  }
382  return 0;
383 }
384 /**
385  * Sets a widget's state to active or inactive
386  * - Arg 1: widget.
387  */
389 {
390 
391  gui2::widget* w = &luaW_checkwidget(L, 1);
392  gui2::widget* res = nullptr;
393  static const std::map<std::string, string_map> data;
394 
395  if(gui2::listbox* lb = dynamic_cast<gui2::listbox*>(w)) {
396  res = &lb->add_row(data);
397  } else {
398  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
399  }
400  if(res) {
401  luaW_pushwidget(L, *res);
402  return 1;
403  }
404  return 0;
405 }
406 
407 /** Closes a window */
409 {
410  gui2::widget* w = &luaW_checkwidget(L, 1);
411  if(gui2::window* wd = dynamic_cast<gui2::window*>(w)) {
412  wd->close();
413  return 0;
414  } else {
415  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
416  }
417 }
418 namespace lua_widget {
420 {
421  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
422  lk.add_log("Adding widgets module...\n");
423  static luaL_Reg const gui_callbacks[] = {
424  //TODO: the naming is a bit arbitrary: widgets with different
425  // types of elements use add_node, eidgets with only
426  // one type of element use add_element
427  { "add_item_of_type", &intf_add_item_of_type },
428  { "add_item", &intf_add_dialog_item },
429  { "focus", &intf_set_dialog_focus },
430  { "set_canvas", &intf_set_dialog_canvas },
431  { "set_callback", &intf_set_dialog_callback },
432  { "remove_items_at", &intf_remove_dialog_item },
433  { "find", &intf_find_widget },
434  { "close", &intf_dialog_close },
435  { nullptr, nullptr },
436  };
437  lua_newtable(L);
438  luaL_setfuncs(L, gui_callbacks, 0);
439  return 1;
440 }
441 }
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:583
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: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:362
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:384
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.
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:51
The listbox class.
Definition: listbox.hpp:42
#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:186
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.
void remove_from_window_stack(window *window)
Removes a entry from the open_window_stack list.
Definition: handler.cpp:1105
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:59
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:174
#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:940
int show(const bool restore=true, const unsigned auto_close_timeout=0)
Shows the window.
Definition: window.cpp:491
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: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:822
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
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:51
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:48
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:103
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:140
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
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:64
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:1103
#define luaL_checkstring(L, n)
Definition: lauxlib.h:138