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