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