The Battle for Wesnoth  1.19.5+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  auto wp = std::make_unique<gui2::window>(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  }
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  }
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  int number_of_items(gui2::listbox& mp)
202  {
203  return mp.get_item_count();
204  }
205  int number_of_items(gui2::multi_page& mp)
206  {
207  return mp.get_page_count();
208  }
209 
210  int number_of_items(gui2::tree_view_node& mp)
211  {
212  return mp.count_children();
213  }
214 
215  int number_of_items(gui2::tree_view& mp)
216  {
217  return number_of_items(mp.get_root_node());
218  }
219 
220  // converts a 1-based index given as lua paraemter to a 0-based index to be used in the c++ api.
221  // and checks that it is in range
222  template<typename TWidget>
223  int check_index(lua_State* L, int arg, TWidget& w, bool for_insertion, utils::optional<int>& index)
224  {
225  int nitems = number_of_items(w);
226 
227  // index == nitems + 1 -> insert at the end.
228  int max = for_insertion ? nitems + 1 : nitems;
229  if(!index) {
230  index = max;
231  }
232 
233  if(*index <= 0 || *index > max) {
234  luaL_argerror(L, arg, "widget child index out of range");
235  }
236  return *index - 1;
237  }
238 
239  void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
240  {
241  //Not tested yet.
242  gui2::tree_view& tv = node.get_tree_view();
243  if(pos >= node.count_children()) {
244  return;
245  }
246  if(number <= 0 || number + pos > node.count_children()) {
247  number = node.count_children() - pos;
248  }
249  for(int i = 0; i < number; ++i) {
250  tv.remove_node(&node.get_child_at(pos));
251  }
252  }
253 }
254 
255 /**
256  * Removes an entry from a list.
257  * - Arg 1: widget
258  * - Arg 2: number (optional), index of the element to delete.
259  * - Arg 3: number (optional), number of the elements to delete. (0 to delete all elements after index)
260  */
261 static int intf_remove_dialog_item(lua_State* L)
262 {
263  gui2::widget* w = &luaW_checkwidget(L, 1);
264  utils::optional<int> pos = lua_check<utils::optional<int>>(L, 2);
265  int number = lua_check<utils::optional<int>>(L, 3).value_or(1);
266 
267  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w)) {
268  int realpos = check_index(L, 2, *list, false, pos);
269  list->remove_row(realpos, number);
270  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
271  int realpos = check_index(L, 2, *multi_page,false, pos);
272  multi_page->remove_page(realpos, number);
273  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
274  int realpos = check_index(L, 2, *tree_view, false, pos);
275  remove_treeview_node(tree_view->get_root_node(), realpos, number);
276  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
277  int realpos = check_index(L, 2, *tree_view_node, false, pos);
278  remove_treeview_node(*tree_view_node, realpos, number);
279  } else {
280  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
281  }
282 
283  return 0;
284 }
285 
286 /**
287  * Removes all entries from a list.
288  * - Arg 1: widget
289 */
290 static int intf_clear_items(lua_State* L)
291 {
292  gui2::widget* w = &luaW_checkwidget(L, 1);
293 
294  if(auto* lb = dynamic_cast<gui2::listbox*>(w)) {
295  lb->clear();
296  } else if(auto* mp = dynamic_cast<gui2::multi_page*>(w)) {
297  mp->clear();
298  } else if(auto* tv = dynamic_cast<gui2::tree_view*>(w)) {
299  tv->clear();
300  } else if(auto* tvn = dynamic_cast<gui2::tree_view_node*>(w)) {
301  tvn->clear();
302  } else {
303  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
304  }
305 
306  return 0;
307 }
308 
309 namespace { // helpers of intf_set_dialog_callback()
310  void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
311  {
312  gui2::widget* w = wp.get_ptr();
313  if(!w) {
314  ERR_LUA << "widget was deleted";
315  return;
316  }
317  gui2::window* wd = w->get_window();
318  if(!wd) {
319  ERR_LUA << "cannot find window in widget callback";
320  return;
321  }
322  luaW_callwidgetcallback(L, w, wd, id);
323  }
324 }//unnamed namespace for helpers of intf_set_dialog_callback()
325 
326 /**
327  * Sets a callback on a widget of the current dialog.
328  * - Arg 1: widget.
329  * - Arg 2: function.
330  */
331 static int intf_set_dialog_callback(lua_State* L)
332 {
334  gui2::widget* w = wp.get_ptr();
335  assert(w);
336  gui2::window* wd = w->get_window();
337  if(!wd) {
338  throw std::invalid_argument("the widget has no window assigned");
339  }
340  if(!lua_isfunction(L, 2)) {
341  return luaL_argerror(L, 2, "callback must be a function");
342  }
343 
344  lua_pushvalue(L, 2);
345  bool already_exists = luaW_setwidgetcallback(L, w, wd, "callback");
346  if(already_exists) {
347  return 0;
348  }
349 
350  // TODO: i am not sure whether it is 100% safe to bind the lua_state here,
351  // (meaning whether it can happen that the lus state is destroyed)
352  // when a widgets callback is called.
353  if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
354  c->connect_click_handler(std::bind(&dialog_callback, L, wp, "callback"));
355  } else if( dynamic_cast<gui2::selectable_item*>(w)) {
356  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
357  } else if(dynamic_cast<gui2::integer_selector*>(w)) {
358  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
359  } else if(dynamic_cast<gui2::listbox*>(w)) {
360  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
361  } else if(dynamic_cast<gui2::tree_view*>(w)) {
362  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
363  } else {
364  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
365  };
366 
367  return 0;
368 }
369 
370 
371 /**
372  * Sets a canvas on a widget of the current dialog.
373  * - Arg 1: widget.
374  * - Arg 2: integer.
375  * - Arg 3: WML table.
376  */
377 static int intf_set_dialog_canvas(lua_State* L)
378 {
379  gui2::widget* w = &luaW_checkwidget(L, 1);
380  int i = luaL_checkinteger(L, 2);
381  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
382  if(!c) {
383  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
384  }
385 
386  std::vector<gui2::canvas> &cv = c->get_canvases();
387  if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
388  return luaL_argerror(L, 2, "out of bounds");
389  }
390 
391  config cfg = luaW_checkconfig(L, 3);
392  cv[i - 1].set_cfg(cfg);
393  c->queue_redraw();
394  return 0;
395 }
396 
397 /**
398  * Sets a widget to have the focus
399  * - Arg 1: widget.
400  */
401 static int intf_set_dialog_focus(lua_State* L)
402 {
403  gui2::widget* w = &luaW_checkwidget(L, 1);
404  if(gui2::window* wd = w->get_window()) {
405  wd->keyboard_capture(w);
406  }
407  return 0;
408 }
409 
410 
411 /**
412  * Adds an item to a container widget that supports different types of items, for example a treeview.
413  * - Arg 1: widget.
414  * - Arg 2: string, the type (id of [node_definition]) of the new item.
415  * - Arg 3: integer (optional), where to insert the new item.
416  */
417 static int intf_add_item_of_type(lua_State* L)
418 {
419  gui2::widget* w = &luaW_checkwidget(L, 1);
420  gui2::widget* res = nullptr;
421  const std::string node_type = luaL_checkstring(L, 2);
422  utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 3);
423  static const gui2::widget_data data;
424 
425  if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
426  int realpos = check_index(L, 2, *twn, true, insert_pos);
427  res = &twn->add_child(node_type, data, realpos);
428  } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
429  int realpos = check_index(L, 2, *tw, true, insert_pos);
430  res = &tw->get_root_node().add_child(node_type, data, realpos);
431  } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
432  int realpos = check_index(L, 2, *mp, true, insert_pos);
433  res = &mp->add_page(node_type, realpos, data);
434  } else {
435  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
436  }
437  if(res) {
438  luaW_pushwidget(L, *res);
439  lua_push(L, insert_pos.value());
440  return 2;
441  }
442  return 0;
443 }
444 /**
445  * Adds an item to a container widget, for example a listbox
446  * - Arg 1: widget.
447  * - Arg 2: integer (optional), where to insert the new item.
448  */
449 static int intf_add_dialog_item(lua_State* L)
450 {
451  gui2::widget* w = &luaW_checkwidget(L, 1);
452  utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 2);
453 
454  gui2::widget* res = nullptr;
455  static const gui2::widget_data data;
456 
457  if(gui2::listbox* lb = dynamic_cast<gui2::listbox*>(w)) {
458  int realpos = check_index(L, 2, *lb, true, insert_pos);
459  res = &lb->add_row(data, realpos);
460  } else {
461  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
462  }
463  if(res) {
464  luaW_pushwidget(L, *res);
465  lua_push(L, insert_pos.value());
466  return 2;
467  }
468  return 0;
469 }
470 
471 /** Closes a window */
472 static int intf_dialog_close(lua_State* L)
473 {
474  gui2::widget* w = &luaW_checkwidget(L, 1);
475  if(gui2::window* wd = dynamic_cast<gui2::window*>(w)) {
476  wd->close();
477  return 0;
478  } else {
479  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
480  }
481 }
482 namespace lua_widget {
483 int luaW_open(lua_State* L)
484 {
485  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
486  lk.add_log("Adding widgets module...\n");
487  static luaL_Reg const gui_callbacks[] = {
488  //TODO: the naming is a bit arbitrary: widgets with different
489  // types of elements use add_node, widgets with only
490  // one type of element use add_element
491  { "add_item_of_type", &intf_add_item_of_type },
492  { "add_item", &intf_add_dialog_item },
493  { "focus", &intf_set_dialog_focus },
494  { "set_canvas", &intf_set_dialog_canvas },
495  { "set_callback", &intf_set_dialog_callback },
496  { "remove_items_at", &intf_remove_dialog_item },
497  { "clear_items", &intf_clear_items },
498  { "find", &intf_find_widget },
499  { "close", &intf_dialog_close },
500  { nullptr, nullptr },
501  };
502  lua_newtable(L);
503  luaL_setfuncs(L, gui_callbacks, 0);
504  return 1;
505 }
506 }
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:172
Small concept class.
Small abstract helper class.
The listbox class.
Definition: listbox.hpp:43
Small abstract helper class.
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.
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:55
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void close()
Requests to close the window.
Definition: window.hpp:219
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:34
T * get_ptr()
Definition: lua_ptr.hpp:37
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1028
int w
static auto & dummy
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:927
void luaW_pushwidget(lua_State *L, gui2::widget &w)
Definition: lua_widget.cpp:35
void luaW_clearwindowtable(lua_State *L, gui2::window *owner)
[-0, +0, -]
Definition: lua_widget.cpp:103
gui2::widget & luaW_checkwidget(lua_State *L, int n)
Definition: lua_widget.cpp:41
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
lua_ptr< gui2::widget > & luaW_checkwidget_ptr(lua_State *L, int n)
Definition: lua_widget.cpp:51
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 ERR_LUA
static lg::log_domain log_scripting_lua("scripting/lua")
static int intf_add_dialog_item(lua_State *L)
Adds an item to a container widget, for example a listbox.
static int intf_add_item_of_type(lua_State *L)
Adds an item to a container widget that supports different types of items, for example a treeview.
static int intf_dialog_close(lua_State *L)
Closes a window.
static int intf_clear_items(lua_State *L)
Removes all entries from a list.
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:1074
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
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:1072
int luaW_open(lua_State *L)
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:50
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
std::string_view data
Definition: picture.cpp:178
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:425
#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