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