The Battle for Wesnoth  1.19.8+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"
21 #include "gui/widgets/listbox.hpp"
24 #include "gui/widgets/slider.hpp"
28 #include "gui/widgets/widget.hpp"
29 #include "gui/widgets/window.hpp"
30 #include "log.hpp"
31 #include "scripting/lua_common.hpp"
33 #include "scripting/lua_ptr.hpp"
34 #include "scripting/lua_widget.hpp"
36 #include "scripting/push_check.hpp"
37 #include "utils/scope_exit.hpp"
38 #include <functional>
39 
40 #include <vector>
41 
42 
43 static lg::log_domain log_scripting_lua("scripting/lua");
44 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
45 
46 /**
47  * Displays a window.
48  * - Arg 1: WML table describing the window.
49  * - Arg 2: function called at pre-show.
50  * - Arg 3: function called at post-show.
51  * - Ret 1: integer.
52  */
53 int intf_show_dialog(lua_State* L)
54 {
55  config def_cfg = luaW_checkconfig(L, 1);
57 
58  auto wp = std::make_unique<gui2::window>(def);
59 
60  if(!lua_isnoneornil(L, 2)) {
61  lua_pushvalue(L, 2);
62  luaW_pushwidget(L, *wp);
63  lua_call(L, 1, 0);
64  }
65 
66  int v = wp->show();
67 
68  if (!lua_isnoneornil(L, 3)) {
69  lua_pushvalue(L, 3);
70  luaW_pushwidget(L, *wp);
71  lua_call(L, 1, 0);
72  }
73  luaW_clearwindowtable(L, wp.get());
74  lua_pushinteger(L, v);
75  return 1;
76 }
77 
78 static gui2::widget* find_widget_impl(lua_State* L, gui2::widget* w, int i, bool readonly)
79 {
80  assert(w);
81 
82  for(; !lua_isnoneornil(L, i); ++i)
83  {
84  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
85  {
86  int v = lua_tointeger(L, i);
87  if(v < 1) {
88  throw std::invalid_argument("negative index");
89  }
90  int n = list->get_item_count();
91  if(v > n) {
92  if(readonly) {
93  throw std::invalid_argument("index out of range");
94  }
96  for(; n < v; ++n) {
97  list->add_row(dummy);
98  }
99  }
100  w = list->get_row_grid(v - 1);
101  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
102  int v = lua_tointeger(L, i);
103  if(v < 1) {
104  throw std::invalid_argument("negative index");
105  }
106  int n = multi_page->get_page_count();
107  if(v > n) {
108  if(readonly) {
109  throw std::invalid_argument("index out of range");
110  }
112  for(; n < v; ++n) {
113  multi_page->add_page(dummy);
114  }
115  }
116  w = &multi_page->page_grid(v - 1);
117  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
118  gui2::tree_view_node& tvn = tree_view->get_root_node();
119  if(lua_isnumber(L, i)) {
120  int v = lua_tointeger(L, i);
121  if(v < 1) {
122  throw std::invalid_argument("negative index");
123  }
124  int n = tvn.count_children();
125  if(v > n) {
126  throw std::invalid_argument("index out of range");
127  }
128  w = &tvn.get_child_at(v - 1);
129 
130  } else {
131  std::string m = luaL_checkstring(L, i);
132  w = tvn.find(m, false);
133  }
134  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
135  if(lua_isnumber(L, i)) {
136  int v = lua_tointeger(L, i);
137  if(v < 1) {
138  throw std::invalid_argument("negative index");
139  }
140  int n = tree_view_node->count_children();
141  if(v > n) {
142  throw std::invalid_argument("index out of range");
143  }
144  w = &tree_view_node->get_child_at(v - 1);
145 
146  } else {
147  std::string m = luaL_checkstring(L, i);
148  w = tree_view_node->find(m, false);
149  }
150  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
151  if(lua_isnumber(L, i)) {
152  int v = lua_tointeger(L, i);
153  if(v < 1) {
154  throw std::invalid_argument("negative index");
155  }
156  int n = stacked_widget->get_layer_count();
157  if(v > n) {
158  throw std::invalid_argument("index out of range");
159  }
160  w = stacked_widget->get_layer_grid(v - 1);
161  } else {
162  std::string m = luaL_checkstring(L, i);
163  w = stacked_widget->find(m, false);
164  }
165  } else {
166  char const *m = lua_tostring(L, i);
167  if(!m) {
168  throw std::invalid_argument("expected a string");
169  }
170  w = w->find(m, false);
171  }
172  if(!w) {
173  throw std::invalid_argument("widget not found");
174  }
175  }
176 
177  return w;
178 }
179 
180 static int intf_find_widget(lua_State* L)
181 {
182  gui2::widget* w = &luaW_checkwidget(L, 1);
183  auto pw = find_widget_impl(L, w, 2, false);
184  if(pw) {
185  luaW_pushwidget(L, *pw);
186  return 1;
187  }
188  return 0;
189 }
190 
191 
192 namespace
193 {
194  int number_of_items(gui2::listbox& mp)
195  {
196  return mp.get_item_count();
197  }
198  int number_of_items(gui2::multi_page& mp)
199  {
200  return mp.get_page_count();
201  }
202 
203  int number_of_items(gui2::tree_view_node& mp)
204  {
205  return mp.count_children();
206  }
207 
208  int number_of_items(gui2::tree_view& mp)
209  {
210  return number_of_items(mp.get_root_node());
211  }
212 
213  // converts a 1-based index given as lua paraemter to a 0-based index to be used in the c++ api.
214  // and checks that it is in range
215  template<typename TWidget>
216  int check_index(lua_State* L, int arg, TWidget& w, bool for_insertion, utils::optional<int>& index)
217  {
218  int nitems = number_of_items(w);
219 
220  // index == nitems + 1 -> insert at the end.
221  int max = for_insertion ? nitems + 1 : nitems;
222  if(!index) {
223  index = max;
224  }
225 
226  if(*index <= 0 || *index > max) {
227  luaL_argerror(L, arg, "widget child index out of range");
228  }
229  return *index - 1;
230  }
231 
232  void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
233  {
234  //Not tested yet.
235  gui2::tree_view& tv = node.get_tree_view();
236  if(pos >= node.count_children()) {
237  return;
238  }
239  if(number <= 0 || number + pos > node.count_children()) {
240  number = node.count_children() - pos;
241  }
242  for(int i = 0; i < number; ++i) {
243  tv.remove_node(&node.get_child_at(pos));
244  }
245  }
246 }
247 
248 /**
249  * Removes an entry from a list.
250  * - Arg 1: widget
251  * - Arg 2: number (optional), index of the element to delete.
252  * - Arg 3: number (optional), number of the elements to delete. (0 to delete all elements after index)
253  */
254 static int intf_remove_dialog_item(lua_State* L)
255 {
256  gui2::widget* w = &luaW_checkwidget(L, 1);
257  utils::optional<int> pos = lua_check<utils::optional<int>>(L, 2);
258  int number = lua_check<utils::optional<int>>(L, 3).value_or(1);
259 
260  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w)) {
261  int realpos = check_index(L, 2, *list, false, pos);
262  list->remove_row(realpos, number);
263  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
264  int realpos = check_index(L, 2, *multi_page,false, pos);
265  multi_page->remove_page(realpos, number);
266  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
267  int realpos = check_index(L, 2, *tree_view, false, pos);
268  remove_treeview_node(tree_view->get_root_node(), realpos, number);
269  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
270  int realpos = check_index(L, 2, *tree_view_node, false, pos);
271  remove_treeview_node(*tree_view_node, realpos, number);
272  } else {
273  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
274  }
275 
276  return 0;
277 }
278 
279 /**
280  * Removes all entries from a list.
281  * - Arg 1: widget
282 */
283 static int intf_clear_items(lua_State* L)
284 {
285  gui2::widget* w = &luaW_checkwidget(L, 1);
286 
287  if(auto* lb = dynamic_cast<gui2::listbox*>(w)) {
288  lb->clear();
289  } else if(auto* mp = dynamic_cast<gui2::multi_page*>(w)) {
290  mp->clear();
291  } else if(auto* tv = dynamic_cast<gui2::tree_view*>(w)) {
292  tv->clear();
293  } else if(auto* tvn = dynamic_cast<gui2::tree_view_node*>(w)) {
294  tvn->clear();
295  } else {
296  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
297  }
298 
299  return 0;
300 }
301 
302 namespace { // helpers of intf_set_dialog_callback()
303  void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
304  {
305  gui2::widget* w = wp.get_ptr();
306  if(!w) {
307  ERR_LUA << "widget was deleted";
308  return;
309  }
310  gui2::window* wd = w->get_window();
311  if(!wd) {
312  ERR_LUA << "cannot find window in widget callback";
313  return;
314  }
315  luaW_callwidgetcallback(L, w, wd, id);
316  }
317 }//unnamed namespace for helpers of intf_set_dialog_callback()
318 
319 /**
320  * Sets a callback on a widget of the current dialog.
321  * - Arg 1: widget.
322  * - Arg 2: function.
323  */
324 static int intf_set_dialog_callback(lua_State* L)
325 {
327  gui2::widget* w = wp.get_ptr();
328  assert(w);
329  gui2::window* wd = w->get_window();
330  if(!wd) {
331  throw std::invalid_argument("the widget has no window assigned");
332  }
333  if(!lua_isfunction(L, 2)) {
334  return luaL_argerror(L, 2, "callback must be a function");
335  }
336 
337  lua_pushvalue(L, 2);
338  bool already_exists = luaW_setwidgetcallback(L, w, wd, "callback");
339  if(already_exists) {
340  return 0;
341  }
342 
343  // TODO: i am not sure whether it is 100% safe to bind the lua_state here,
344  // (meaning whether it can happen that the lus state is destroyed)
345  // when a widgets callback is called.
346  if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
347  c->connect_click_handler(std::bind(&dialog_callback, L, wp, "callback"));
348  } else if( dynamic_cast<gui2::selectable_item*>(w)) {
349  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
350  } else if(dynamic_cast<gui2::integer_selector*>(w)) {
351  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
352  } else if(dynamic_cast<gui2::listbox*>(w)) {
353  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
354  } else if(dynamic_cast<gui2::tree_view*>(w)) {
355  connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
356  } else {
357  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
358  };
359 
360  return 0;
361 }
362 
363 
364 /**
365  * Sets a canvas on a widget of the current dialog.
366  * - Arg 1: widget.
367  * - Arg 2: integer.
368  * - Arg 3: WML table.
369  */
370 static int intf_set_dialog_canvas(lua_State* L)
371 {
372  gui2::widget* w = &luaW_checkwidget(L, 1);
373  int i = luaL_checkinteger(L, 2);
374  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
375  if(!c) {
376  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
377  }
378 
379  std::vector<gui2::canvas> &cv = c->get_canvases();
380  if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
381  return luaL_argerror(L, 2, "out of bounds");
382  }
383 
384  config cfg = luaW_checkconfig(L, 3);
385  cv[i - 1].set_cfg(cfg);
386  c->queue_redraw();
387  return 0;
388 }
389 
390 /**
391  * Sets a widget to have the focus
392  * - Arg 1: widget.
393  */
394 static int intf_set_dialog_focus(lua_State* L)
395 {
396  gui2::widget* w = &luaW_checkwidget(L, 1);
397  if(gui2::window* wd = w->get_window()) {
398  wd->keyboard_capture(w);
399  }
400  return 0;
401 }
402 
403 
404 /**
405  * Adds an item to a container widget that supports different types of items, for example a treeview.
406  * - Arg 1: widget.
407  * - Arg 2: string, the type (id of [node_definition]) of the new item.
408  * - Arg 3: integer (optional), where to insert the new item.
409  */
410 static int intf_add_item_of_type(lua_State* L)
411 {
412  gui2::widget* w = &luaW_checkwidget(L, 1);
413  gui2::widget* res = nullptr;
414  const std::string node_type = luaL_checkstring(L, 2);
415  utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 3);
416  static const gui2::widget_data data;
417 
418  if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
419  int realpos = check_index(L, 2, *twn, true, insert_pos);
420  res = &twn->add_child(node_type, data, realpos);
421  } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
422  int realpos = check_index(L, 2, *tw, true, insert_pos);
423  res = &tw->get_root_node().add_child(node_type, data, realpos);
424  } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
425  int realpos = check_index(L, 2, *mp, true, insert_pos);
426  res = &mp->add_page(node_type, realpos, data);
427  } else {
428  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
429  }
430  if(res) {
431  luaW_pushwidget(L, *res);
432  lua_push(L, insert_pos.value());
433  return 2;
434  }
435  return 0;
436 }
437 /**
438  * Adds an item to a container widget, for example a listbox
439  * - Arg 1: widget.
440  * - Arg 2: integer (optional), where to insert the new item.
441  */
442 static int intf_add_dialog_item(lua_State* L)
443 {
444  gui2::widget* w = &luaW_checkwidget(L, 1);
445  utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 2);
446 
447  gui2::widget* res = nullptr;
448  static const gui2::widget_data data;
449 
450  if(gui2::listbox* lb = dynamic_cast<gui2::listbox*>(w)) {
451  int realpos = check_index(L, 2, *lb, true, insert_pos);
452  res = &lb->add_row(data, realpos);
453  } else {
454  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
455  }
456  if(res) {
457  luaW_pushwidget(L, *res);
458  lua_push(L, insert_pos.value());
459  return 2;
460  }
461  return 0;
462 }
463 
464 /** Closes a window */
465 static int intf_dialog_close(lua_State* L)
466 {
467  gui2::widget* w = &luaW_checkwidget(L, 1);
468  if(gui2::window* wd = dynamic_cast<gui2::window*>(w)) {
469  wd->close();
470  return 0;
471  } else {
472  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
473  }
474 }
475 namespace lua_widget {
476 int luaW_open(lua_State* L)
477 {
478  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
479  lk.add_log("Adding widgets module...\n");
480  static luaL_Reg const gui_callbacks[] = {
481  //TODO: the naming is a bit arbitrary: widgets with different
482  // types of elements use add_node, widgets with only
483  // one type of element use add_element
484  { "add_item_of_type", &intf_add_item_of_type },
485  { "add_item", &intf_add_dialog_item },
486  { "focus", &intf_set_dialog_focus },
487  { "set_canvas", &intf_set_dialog_canvas },
488  { "set_callback", &intf_set_dialog_callback },
489  { "remove_items_at", &intf_remove_dialog_item },
490  { "clear_items", &intf_clear_items },
491  { "find", &intf_find_widget },
492  { "close", &intf_dialog_close },
493  { nullptr, nullptr },
494  };
495  lua_newtable(L);
496  luaL_setfuncs(L, gui_callbacks, 0);
497  return 1;
498 }
499 }
static auto & dummy
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:158
Small concept class.
Small abstract helper class.
The listbox class.
Definition: listbox.hpp:41
Small abstract helper class.
tree_view & get_tree_view()
tree_view_node & get_child_at(int index)
widget * find(const std::string_view 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:1193
void close()
Requests to close the window.
Definition: window.hpp:218
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:45
T * get_ptr()
Definition: lua_ptr.hpp:48
Definitions for the interface to Wesnoth Markup Language (WML).
std::size_t i
Definition: function.cpp:1029
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.
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
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
int luaW_open(lua_State *L)
Main entry points of multiplayer mode.
Definition: lobby_data.cpp:50
std::size_t index(std::string_view 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
mock_char c
static map_location::direction n