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