The Battle for Wesnoth  1.19.5+dev
lua_widget_attributes.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
3  by Chris Beck <render787@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
19 #include "gui/widgets/listbox.hpp"
23 #include "gui/widgets/slider.hpp"
25 #include "gui/widgets/text_box.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_unit.hpp"
36 #include "scripting/push_check.hpp"
37 #include "scripting/lua_widget.hpp"
41 
42 #include <functional>
43 
44 #include <boost/preprocessor/cat.hpp>
45 
46 #include <map>
47 #include <utility>
48 #include <vector>
49 
50 
51 static lg::log_domain log_scripting_lua("scripting/lua");
52 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
53 
55 {
56  assert(i > 0);
57  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(&w)) {
58  int n = list->get_item_count();
59  if(i > n) {
60  for(; n < i; ++n) {
61  list->add_row(gui2::widget_item{});
62  }
63  }
64  return list->get_row_grid(i - 1);
65  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(&w)) {
66  int n = multi_page->get_page_count();
67  if(i > n) {
68  for(; n < i; ++n) {
69  multi_page->add_page(gui2::widget_item{});
70  }
71  }
72  return &multi_page->page_grid(i - 1);
73  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(&w)) {
74  gui2::tree_view_node& tvn = tree_view->get_root_node();
75  int n = tvn.count_children();
76  if(i > n) {
77  throw std::invalid_argument("out of range");
78  }
79  return &tvn.get_child_at(i - 1);
80  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(&w)) {
81  int n = tree_view_node->count_children();
82  if(i > n) {
83  throw std::invalid_argument("out of range");
84  }
85  return &tree_view_node->get_child_at(i - 1);
86  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(&w)) {
87  int n = stacked_widget->get_layer_count();
88  if(i > n) {
89  throw std::invalid_argument("out of range");
90  }
91  return stacked_widget->get_layer_grid(i - 1);
92  }
93  return nullptr;
94 }
95 
96 static gui2::widget* find_child_by_name(gui2::widget& w, const std::string& m)
97 {
98  return w.find(m, false);
99 }
100 
101 using tgetters = std::map<std::string, std::vector<std::function<bool(lua_State*, gui2::widget&, bool)>>>;
103 
104 using tsetters = std::map<std::string, std::vector<std::function<bool(lua_State*, int, gui2::widget&, bool)>>>;
106 
107 template<typename widget_type, typename value_type, typename action_type, bool setter>
108 void register_widget_attribute(const char* name)
109 {
110  utils::split_foreach(name, ',', 0, [](std::string_view name_part) {
111  using map_type = std::conditional_t<setter, tsetters, tgetters>;
112  using list_type = typename map_type::mapped_type;
113  using callback_type = typename list_type::value_type;
114  map_type* map;
115  callback_type fcn;
116  if constexpr(setter) {
117  map = &setters;
118  fcn = [action = action_type()](lua_State* L, int idx, gui2::widget& w, bool nop) {
119  if(widget_type* pw = dynamic_cast<widget_type*>(&w)) {
120  if(!nop) action.set(L, *pw, lua_check<value_type>(L, idx));
121  return true;
122  }
123  return false;
124  };
125  } else {
126  map = &getters;
127  fcn = [action = action_type()](lua_State* L, gui2::widget& w, bool nop) {
128  if(widget_type* pw = dynamic_cast<widget_type*>(&w)) {
129  if(!nop) lua_push(L, action.get(L, *pw));
130  return true;
131  }
132  return false;
133  };
134  }
135  list_type& list = (*map)[std::string(name_part)];
136  list.push_back(fcn);
137  });
138 }
139 
140 #define WIDGET_GETTER4(name, value_type, widgt_type, id) \
141 struct BOOST_PP_CAT(getter_, id) : public lua_getter<widgt_type, value_type> { \
142  value_type get(lua_State* L, const widgt_type& w) const override; \
143 }; \
144 struct BOOST_PP_CAT(getter_adder_, id) { \
145  BOOST_PP_CAT(getter_adder_, id) () \
146  { \
147  register_widget_attribute<widgt_type, value_type, BOOST_PP_CAT(getter_, id), false>(name); \
148  } \
149 }; \
150 static BOOST_PP_CAT(getter_adder_, id) BOOST_PP_CAT(getter_adder_instance_, id) ; \
151 value_type BOOST_PP_CAT(getter_, id)::get([[maybe_unused]] lua_State* L, const widgt_type& w) const
152 
153 
154 #define WIDGET_SETTER4(name, value_type, widgt_type, id) \
155 struct BOOST_PP_CAT(setter_, id) : public lua_setter<widgt_type, value_type> { \
156  void set(lua_State* L, widgt_type& w, const value_type& value) const override; \
157 }; \
158 struct BOOST_PP_CAT(setter_adder_, id) { \
159  BOOST_PP_CAT(setter_adder_, id) ()\
160  { \
161  register_widget_attribute<widgt_type, value_type, BOOST_PP_CAT(setter_, id), true>(name); \
162  } \
163 }; \
164 static BOOST_PP_CAT(setter_adder_, id) BOOST_PP_CAT(setter_adder_instance_, id); \
165 void BOOST_PP_CAT(setter_, id)::set([[maybe_unused]] lua_State* L, widgt_type& w, const value_type& value) const
166 
167 
168 /**
169  * @param name: string comma seperated list
170  * @param value_type: the type of the attribute, for example int or std::string
171  * @param widgt_type: the type of the widget, for example gui2::listbox
172  */
173 #define WIDGET_GETTER(name, value_type, widgt_type) WIDGET_GETTER4(name, value_type, widgt_type, __LINE__)
174 
175 #define WIDGET_SETTER(name, value_type, widgt_type) WIDGET_SETTER4(name, value_type, widgt_type, __LINE__)
176 
177 
178 /// CLASSIC
179 
180 WIDGET_GETTER("value_compat,selected_index", int, gui2::listbox)
181 {
182  return w.get_selected_row() + 1;
183 }
184 
185 WIDGET_SETTER("value_compat,selected_index", int, gui2::listbox)
186 {
187  w.select_row(value - 1);
188 }
189 
190 WIDGET_GETTER("value_compat,selected_index", int, gui2::multi_page)
191 {
192  return w.get_selected_page() + 1;
193 }
194 
195 WIDGET_SETTER("value_compat,selected_index", int, gui2::multi_page)
196 {
197  w.select_page(value -1);
198 }
199 
200 WIDGET_GETTER("value_compat,selected_index", int, gui2::stacked_widget)
201 {
202  return w.current_layer() + 1;
203 }
204 
205 WIDGET_SETTER("value_compat,selected_index", int, gui2::stacked_widget)
206 {
207  w.select_layer(value - 1);
208 }
209 
210 WIDGET_GETTER("selected_index", int, gui2::selectable_item)
211 {
212  return w.get_value() + 1;
213 }
214 
215 WIDGET_SETTER("selected_index", int, gui2::selectable_item)
216 {
217  if(value > int(w.num_states())) {
218  throw std::invalid_argument("invalid index");
219  }
220  w.set_value(value - 1);
221 }
222 
223 WIDGET_GETTER("value_compat,selected", bool, gui2::selectable_item)
224 {
225  if(w.num_states() == 2) {
226  return w.get_value_bool();
227  }
228  throw std::invalid_argument("invalid widget");
229 }
230 
231 WIDGET_SETTER("value_compat,selected", bool, gui2::selectable_item)
232 {
233  w.set_value_bool(value);
234 }
235 
236 WIDGET_GETTER("value_compat,text", std::string, gui2::text_box)
237 {
238  return w.get_value();
239 }
240 
241 WIDGET_SETTER("value_compat,text", std::string, gui2::text_box)
242 {
243  w.set_value(value);
244 }
245 
246 WIDGET_GETTER("value_compat,value", int, gui2::slider)
247 {
248  return w.get_value();
249 }
250 
251 WIDGET_SETTER("value_compat,value", int, gui2::slider)
252 {
253  w.set_value(value);
254 }
255 
256 WIDGET_GETTER("max_value", int, gui2::slider)
257 {
258  return w.get_maximum_value();
259 }
260 
261 WIDGET_SETTER("max_value", int, gui2::slider)
262 {
263  w.set_value_range(w.get_minimum_value(), value);
264 }
265 
266 WIDGET_GETTER("min_value", int, gui2::slider)
267 {
268  return w.get_minimum_value();
269 }
270 
271 WIDGET_SETTER("min_value", int, gui2::slider)
272 {
273  w.set_value_range(value, w.get_maximum_value());
274 }
275 
276 WIDGET_GETTER("value_compat,percentage", int, gui2::progress_bar)
277 {
278  return w.get_percentage();
279 }
280 
281 WIDGET_SETTER("value_compat,percentage", int, gui2::progress_bar)
282 {
283  w.set_percentage(value);
284 }
285 
286 WIDGET_GETTER("value_compat,selected_item_path", std::vector<int>, gui2::tree_view)
287 {
288  auto res = w.selected_item()->describe_path();
289  for(int& a : res) { ++a;}
290  return res;
291 }
292 
293 WIDGET_GETTER("path", std::vector<int>, gui2::tree_view_node)
294 {
295  auto res = w.describe_path();
296  for(int& a : res) { ++a;}
297  return res;
298 }
299 
300 WIDGET_SETTER("value_compat,unfolded", bool, gui2::tree_view_node)
301 {
302  if(value) {
303  w.unfold();
304  } else {
305  w.fold();
306  }
307 }
308 
310 {
311  if(const unit_type* ut = luaW_tounittype(L, value.index)) {
312  w.set_displayed_type(*ut);
313  } else if(unit* u = luaW_tounit(L, value.index)) {
314  w.set_displayed_unit(*u);
315  } else {
316  luaW_type_error(L, value.index, "unit or unit type");
317  }
318 }
319 
320 WIDGET_GETTER("item_count", int, gui2::multi_page)
321 {
322  return w.get_page_count();
323 }
324 
325 WIDGET_GETTER("item_count", int, gui2::listbox)
326 {
327  return w.get_item_count();
328 }
329 
330 WIDGET_SETTER("use_markup", bool, gui2::styled_widget)
331 {
332  w.set_use_markup(value);
333 }
334 
335 //TODO: while i think this shortcut is useful, i'm not that happy about
336 // the name since it changes 'label' and not 'text', the first one
337 // is the label that is part of most widgets (like checkboxes), the
338 // later is specific to input textboxes.
340 {
341  w.set_use_markup(true);
342  w.set_label(value);
343 }
344 
346 {
347  w.set_active(value);
348 }
349 
351 {
352  w.set_tooltip(value);
353 }
354 
355 
357 {
358  if(!luaW_getglobal(L, "gui", "widget", "set_callback")) {
359  ERR_LUA << "gui.widget.set_callback didn't exist";
360  }
361  luaW_pushwidget(L, w);
362  lua_pushvalue(L, value.index);
363  lua_call(L, 2, 0);
364 }
365 
367 {
368 
369  typedef gui2::styled_widget::visibility visibility;
370 
371  visibility flag = visibility::visible;
372 
373  switch(lua_type(L, value.index)) {
374  case LUA_TBOOLEAN:
375  flag = luaW_toboolean(L, value.index)
376  ? visibility::visible
377  : visibility::invisible;
378  break;
379  case LUA_TSTRING:
380  {
381  const std::string& str = lua_tostring(L, value.index);
382  if(str == "visible") {
383  flag = visibility::visible;
384  } else if(str == "hidden") {
385  flag = visibility::hidden;
386  } else if(str == "invisible") {
387  flag = visibility::invisible;
388  } else {
389  luaL_argerror(L, value.index, "string must be one of: visible, hidden, invisible");
390  }
391  }
392  break;
393  default:
394  luaW_type_error(L, value.index, "boolean or string");
395  }
396 
397  w.set_visible(flag);
398 
399  if(flag == visibility::hidden) {
400  // HACK: this is needed to force the widget to be repainted immediately
401  // to get rid of its ghost image.
402  gui2::window* window = w.get_window();
403  if(window) {
404  window->invalidate_layout();
405  }
406  }
407 }
408 
409 //must be last
411 {
412  gui2::window* window = w.get_window();
413  if(window) {
414  window->invalidate_layout();
415  }
416  w.set_label(value);
417 }
418 
419 WIDGET_GETTER("type", std::string, gui2::widget)
420 {
421  if(const gui2::styled_widget* sw = dynamic_cast<const gui2::styled_widget*>(&w)) {
422  return sw->get_control_type();
423  }
424  else if(dynamic_cast<const gui2::tree_view_node*>(&w)) {
425  return "tree_view_node";
426  }
427  else if(dynamic_cast<const gui2::grid*>(&w)) {
428  return "grid";
429  }
430  else {
431  return "";
432  }
433 }
434 
435 ///////////////////////////////////////////////////////
436 ////////////////////// CALLBACKS //////////////////////
437 ///////////////////////////////////////////////////////
438 namespace {
439 
440 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
441 {
442  gui2::widget* w = wp.get_ptr();
443  if(!w) {
444  ERR_LUA << "widget was deleted";
445  return;
446  }
447  gui2::window* wd = w->get_window();
448  if(!wd) {
449  ERR_LUA << "cannot find window in widget callback";
450  return;
451  }
452  luaW_callwidgetcallback(L, w, wd, id);
453 }
454 
456 {
457  gui2::window* wd = w.get_window();
458  if(!wd) {
459  throw std::invalid_argument("the widget has no window assigned");
460  }
461  lua_pushvalue(L, value.index);
462  if (!luaW_setwidgetcallback(L, &w, wd, "on_modified")) {
463  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_modified"));
464  }
465 }
466 
467 WIDGET_SETTER("on_left_click", lua_index_raw, gui2::widget)
468 {
469  gui2::window* wd = w.get_window();
470  if(!wd) {
471  throw std::invalid_argument("the widget has no window assigned");
472  }
473  lua_pushvalue(L, value.index);
474  if (!luaW_setwidgetcallback(L, &w, wd, "on_left_click")) {
475  connect_signal_mouse_left_click(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_left_click"));
476  }
477 }
478 
479 WIDGET_SETTER("on_button_click", lua_index_raw, gui2::widget)
480 {
481  gui2::window* wd = w.get_window();
482  gui2::clickable_item* cl = dynamic_cast<gui2::clickable_item*>(&w);
483 
484  if(!wd) {
485  throw std::invalid_argument("the widget has no window assigned");
486  }
487  if(!cl) {
488  throw std::invalid_argument("unsupported widget");
489  }
490  lua_pushvalue(L, value.index);
491  if (!luaW_setwidgetcallback(L, &w, wd, "on_button_click")) {
492  cl->connect_click_handler(std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_button_click"));
493  }
494 }
495 
496 }
497 
498 namespace lua_widget {
499 
500 int impl_widget_get(lua_State* L)
501 {
503  if(lua_isinteger(L, 2)) {
504 
505  if(auto pwidget = find_child_by_index(w, luaL_checkinteger(L, 2))) {
506  luaW_pushwidget(L, *pwidget);
507  return 1;
508  }
509 
510  }
511  std::string_view str = lua_check<std::string_view>(L, 2);
512 
513  tgetters::iterator it = getters.find(std::string(str));
514  if(it != getters.end()) {
515  for(const auto& func : it->second) {
516  if(func(L, w, false)) {
517  return 1;
518  }
519  }
520  }
521  if(luaW_getglobal(L, "gui", "widget", std::string(str).c_str())) {
522  return 1;
523  }
524  if(auto pwidget = find_child_by_name(w, std::string(str))) {
525  luaW_pushwidget(L, *pwidget);
526  return 1;
527  }
528  ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str;
529  std::string err = "invalid property of widget: ";
530  err += str;
531  return luaL_argerror(L, 2, err.c_str());
532 }
533 
534 int impl_widget_set(lua_State* L)
535 {
537  std::string_view str = lua_check<std::string_view>(L, 2);
538 
539 
540  tsetters::iterator it = setters.find(std::string(str));
541  if(it != setters.end()) {
542  for(const auto& func : it->second) {
543  if(func(L, 3, w, false)) {
544  return 0;
545  }
546  }
547  ERR_LUA << "none of "<< it->second.size() << " setters matched";
548  }
549  else {
550  ERR_LUA << "unknown property id : " << str << " #known properties=" << setters.size();
551 
552  }
553  ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str;
554  std::string err = "invalid modifiable property of widget: ";
555  err += str;
556  return luaL_argerror(L, 2, err.c_str());
557 }
558 
559 int impl_widget_dir(lua_State* L)
560 {
562  std::vector<std::string> keys;
563  // Add any readable keys
564  for(const auto& [key, funcs] : getters) {
565  if(key == "value_compat") continue;
566  for(const auto& func : funcs) {
567  if(func(L, w, true)){
568  keys.push_back(key);
569  break;
570  }
571  }
572  }
573  // Add any writable keys
574  for(const auto& [key, funcs] : setters) {
575  if(key == "value_compat") continue;
576  if(key == "callback") continue;
577  for(const auto& func : funcs) {
578  if(func(L, 0, w, true)){
579  keys.push_back(key);
580  break;
581  }
582  }
583  }
584  // Add any nested widget IDs
586  for(auto child = iter_t(w); !child.at_end(); child.next()) {
587  const auto& key = child->id();
588  if(!key.empty() && key != w.id()) {
589  keys.push_back(key);
590  }
591  }
592  // Add the gui.widget methods
593  luaW_getglobal(L, "gui", "widget");
594  auto methods = luaW_get_attributes(L, -1);
595  keys.insert(keys.end(), methods.begin(), methods.end());
596  lua_push(L, keys);
597  return 1;
598 }
599 }
Small concept class.
virtual void connect_click_handler(const event::signal &signal)=0
Connects a signal handler for a 'click' event.
Base container class.
Definition: grid.hpp:32
The iterator class.
Definition: iterator.hpp:37
The listbox class.
Definition: listbox.hpp:43
Small abstract helper class.
A widget that allows the user to input text in single line.
Definition: text_box.hpp:125
tree_view_node & get_child_at(int index)
std::size_t count_children() const
The number of children in this widget.
Base class for all widgets.
Definition: widget.hpp:55
const std::string & id() const
Definition: widget.cpp:110
visibility
Visibility settings done by the user.
Definition: widget.hpp:65
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:761
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:34
T * get_ptr()
Definition: lua_ptr.hpp:37
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:133
std::size_t i
Definition: function.cpp:1028
int w
This file contains the window object, this object is a top level container which has the event manage...
Contains the base iterator class for the gui2 widgets.
Standard logging facilities (interface).
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:998
int luaW_type_error(lua_State *L, int narg, const char *tname)
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil.
Definition: lua_common.cpp:979
std::vector< std::string > luaW_get_attributes(lua_State *L, int idx)
This function does the actual work of grabbing all the attribute names.
unit * luaW_tounit(lua_State *L, int index, bool only_on_map)
Converts a Lua value to a unit pointer.
Definition: lua_unit.cpp:142
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
void luaW_pushwidget(lua_State *L, gui2::widget &w)
Definition: lua_widget.cpp:35
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
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
std::map< std::string, std::vector< std::function< bool(lua_State *, int, gui2::widget &, bool)> >> tsetters
#define WIDGET_GETTER(name, value_type, widgt_type)
#define ERR_LUA
static lg::log_domain log_scripting_lua("scripting/lua")
static gui2::widget * find_child_by_index(gui2::widget &w, int i)
#define WIDGET_SETTER(name, value_type, widgt_type)
std::map< std::string, std::vector< std::function< bool(lua_State *, gui2::widget &, bool)> >> tgetters
static tsetters setters
static gui2::widget * find_child_by_name(gui2::widget &w, const std::string &m)
void register_widget_attribute(const char *name)
static tgetters getters
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 connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
logger & err()
Definition: log.cpp:305
int impl_widget_set(lua_State *L)
int impl_widget_get(lua_State *L)
int impl_widget_dir(lua_State *L)
void split_foreach(std::string_view s, char sep, const int flags, const F &f)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:425
static map_location::direction n
static map_location::direction sw