The Battle for Wesnoth  1.17.0-dev
lua_widget_attributes.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2021
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"
20 #include "gui/widgets/listbox.hpp"
25 #include "gui/widgets/slider.hpp"
27 #include "gui/widgets/text_box.hpp"
31 #include "gui/widgets/widget.hpp"
32 #include "gui/widgets/window.hpp"
33 #include "config.hpp"
34 #include "log.hpp"
35 #include "scripting/lua_common.hpp"
38 #include "scripting/lua_unit.hpp"
40 #include "scripting/push_check.hpp"
41 #include "scripting/lua_widget.hpp"
44 #include "tstring.hpp"
45 #include "game_data.hpp"
46 #include "game_state.hpp"
47 
48 #include <functional>
50 
51 #include <boost/preprocessor/cat.hpp>
52 
53 #include <map>
54 #include <utility>
55 #include <vector>
56 
57 #include "lua/lauxlib.h" // for luaL_checkinteger, etc
58 #include "lua/lua.h" // for 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(utils::string_map());
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(utils::string_map());
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&)>>>;
112 
113 using tsetters = std::map<std::string, std::vector<std::function<bool(lua_State*, int, gui2::widget&)>>>;
115 
116 #define WIDGET_GETTER4(name, value_type, widgt_type, id) \
117 /* use a class member for L to surpress unused parameter wanring */ \
118 struct BOOST_PP_CAT(getter_, id) { value_type do_it(widgt_type& w); lua_State* L; }; \
119 struct BOOST_PP_CAT(getter_adder_, id) { \
120  BOOST_PP_CAT(getter_adder_, id) () \
121  { \
122  utils::split_foreach(name, ',', 0, [](std::string_view name_part){\
123  getters[std::string(name_part)].push_back([](lua_State* L, gui2::widget& w) { \
124  if(widgt_type* pw = dynamic_cast<widgt_type*>(&w)) { \
125  lua_push(L, BOOST_PP_CAT(getter_, id){L}.do_it(*pw)); \
126  return true; \
127  } \
128  return false; \
129  }); \
130  }); \
131  } \
132 }; \
133 static BOOST_PP_CAT(getter_adder_, id) BOOST_PP_CAT(getter_adder_instance_, id) ; \
134 value_type BOOST_PP_CAT(getter_, id)::do_it(widgt_type& w)
135 
136 
137 #define WIDGET_SETTER4(name, value_type, widgt_type, id) \
138 struct BOOST_PP_CAT(setter_, id) { void do_it(widgt_type& w, const value_type& value); lua_State* L; }; \
139 struct BOOST_PP_CAT(setter_adder_, id) { \
140  BOOST_PP_CAT(setter_adder_, id) ()\
141  { \
142  utils::split_foreach(name, ',', 0, [](std::string_view name_part){\
143  setters[std::string(name_part)].push_back([](lua_State* L, int idx, gui2::widget& w) { \
144  if(widgt_type* pw = dynamic_cast<widgt_type*>(&w)) { \
145  BOOST_PP_CAT(setter_, id){L}.do_it(*pw, lua_check<value_type>(L, idx)); \
146  return true; \
147  } \
148  return false; \
149  }); \
150  }); \
151  } \
152 }; \
153 static BOOST_PP_CAT(setter_adder_, id) BOOST_PP_CAT(setter_adder_instance_, id); \
154 void BOOST_PP_CAT(setter_, id)::do_it(widgt_type& w, const value_type& value)
155 
156 
157 /**
158  * @param name: string comma seperated list
159  * @param value_type: the type of the attribute, for example int or std::string
160  * @param widgt_type: the type of the widget, for example gui2::listbox
161  */
162 #define WIDGET_GETTER(name, value_type, widgt_type) WIDGET_GETTER4(name, value_type, widgt_type, __LINE__)
163 
164 #define WIDGET_SETTER(name, value_type, widgt_type) WIDGET_SETTER4(name, value_type, widgt_type, __LINE__)
165 
166 
167 /// CLASSIC
168 
169 WIDGET_GETTER("value_compat,selected_index", int, gui2::listbox)
170 {
171  return w.get_selected_row() + 1;
172 }
173 
174 WIDGET_SETTER("value_compat,selected_index", int, gui2::listbox)
175 {
176  w.select_row(value - 1);
177 }
178 
179 WIDGET_GETTER("value_compat,selected_index", int, gui2::multi_page)
180 {
181  return w.get_selected_page() + 1;
182 }
183 
184 WIDGET_SETTER("value_compat,selected_index", int, gui2::multi_page)
185 {
186  w.select_page(value -1);
187 }
188 
189 WIDGET_GETTER("value_compat,selected_index", int, gui2::stacked_widget)
190 {
191  return w.current_layer() + 1;
192 }
193 
194 WIDGET_SETTER("value_compat,selected_index", int, gui2::stacked_widget)
195 {
196  w.select_layer(value - 1);
197 }
198 
199 WIDGET_GETTER("selected_index", int, gui2::selectable_item)
200 {
201  return w.get_value() + 1;
202 }
203 
204 WIDGET_SETTER("selected_index", int, gui2::selectable_item)
205 {
206  if(value > int(w.num_states())) {
207  throw std::invalid_argument("invalid index");
208  }
209  w.set_value(value + 1);
210 }
211 
212 WIDGET_GETTER("value_compat,selected", bool, gui2::selectable_item)
213 {
214  if(w.num_states() == 2) {
215  return w.get_value_bool();
216  }
217  throw std::invalid_argument("invalid widget");
218 }
219 
220 WIDGET_SETTER("value_compat,selected", bool, gui2::selectable_item)
221 {
222  w.set_value_bool(value);
223 }
224 
225 WIDGET_GETTER("value_compat,text", std::string, gui2::text_box)
226 {
227  return w.get_value();
228 }
229 
230 WIDGET_SETTER("value_compat,text", std::string, gui2::text_box)
231 {
232  w.set_value(value);
233 }
234 
235 WIDGET_GETTER("value_compat,value", int, gui2::slider)
236 {
237  return w.get_value();
238 }
239 
240 WIDGET_SETTER("value_compat,value", int, gui2::slider)
241 {
242  w.set_value(value);
243 }
244 
245 WIDGET_GETTER("max_value", int, gui2::slider)
246 {
247  return w.get_maximum_value();
248 }
249 
250 WIDGET_SETTER("max_value", int, gui2::slider)
251 {
252  w.set_value_range(w.get_minimum_value(), value);
253 }
254 
255 WIDGET_GETTER("min_value", int, gui2::slider)
256 {
257  return w.get_minimum_value();
258 }
259 
260 WIDGET_SETTER("min_value", int, gui2::slider)
261 {
262  w.set_value_range(value, w.get_maximum_value());
263 }
264 
265 WIDGET_GETTER("value_compat,percentage", int, gui2::progress_bar)
266 {
267  return w.get_percentage();
268 }
269 
270 WIDGET_SETTER("value_compat,percentage", int, gui2::progress_bar)
271 {
272  w.set_percentage(value);
273 }
274 
275 WIDGET_GETTER("value_compat,selected_item_path", std::vector<int>, gui2::tree_view)
276 {
277  auto res = w.selected_item()->describe_path();
278  for(int& a : res) { ++a;}
279  return res;
280 }
281 
282 WIDGET_GETTER("path", std::vector<int>, gui2::tree_view_node)
283 {
284  auto res = w.describe_path();
285  for(int& a : res) { ++a;}
286  return res;
287 }
288 
289 WIDGET_SETTER("value_compat,unfolded", bool, gui2::tree_view_node)
290 {
291  if(value) {
292  w.unfold();
293  } else {
294  w.fold();
295  }
296 }
297 
299 {
300  if(const unit_type* ut = luaW_tounittype(L, value.index)) {
301  w.set_displayed_type(*ut);
302  } else if(unit* u = luaW_tounit(L, value.index)) {
303  w.set_displayed_unit(*u);
304  } else {
305  luaW_type_error(L, value.index, "unit or unit type");
306  }
307 }
308 
309 WIDGET_GETTER("item_count", int, gui2::multi_page)
310 {
311  return w.get_page_count();
312 }
313 
314 WIDGET_GETTER("item_count", int, gui2::listbox)
315 {
316  return w.get_item_count();
317 }
318 
319 WIDGET_SETTER("use_markup", bool, gui2::styled_widget)
320 {
321  w.set_use_markup(value);
322 }
323 
324 //TODO: while i think this shortcut is useful, i'm not that happy about
325 // the name since it changes 'label' and not 'text', the first one
326 // is the label that is part of most widgets (like checkboxes), the
327 // later is specific to input textboxes.
329 {
330  w.set_use_markup(true);
331  w.set_label(value);
332 }
333 
335 {
336  w.set_active(value);
337 }
338 
340 {
341  w.set_tooltip(value);
342 }
343 
344 
346 {
347  if(!luaW_getglobal(L, "gui", "widget", "set_callback")) {
348  ERR_LUA << "gui.widget.set_callback didn't exist\n";
349  }
350  luaW_pushwidget(L, w);
351  lua_pushvalue(L, value.index);
352  lua_call(L, 2, 0);
353 }
354 
356 {
357 
358  typedef gui2::styled_widget::visibility visibility;
359 
360  visibility flag = visibility::visible;
361 
362  switch(lua_type(L, value.index)) {
363  case LUA_TBOOLEAN:
364  flag = luaW_toboolean(L, value.index)
365  ? visibility::visible
366  : visibility::invisible;
367  break;
368  case LUA_TSTRING:
369  {
370  const std::string& str = lua_tostring(L, value.index);
371  if(str == "visible") {
372  flag = visibility::visible;
373  } else if(str == "hidden") {
374  flag = visibility::hidden;
375  } else if(str == "invisible") {
376  flag = visibility::invisible;
377  } else {
378  luaL_argerror(L, value.index, "string must be one of: visible, hidden, invisible");
379  }
380  }
381  break;
382  default:
383  luaW_type_error(L, value.index, "boolean or string");
384  }
385 
386  w.set_visible(flag);
387 
388  if(flag == visibility::hidden) {
389  // HACK: this is needed to force the widget to be repainted immediately
390  // to get rid of its ghost image.
391  gui2::window* window = w.get_window();
392  if(window) {
393  window->invalidate_layout();
394  }
395  }
396 }
397 
398 //must be last
400 {
401  gui2::window* window = w.get_window();
402  if(window) {
403  window->invalidate_layout();
404  }
405  w.set_label(value);
406 }
407 
408 WIDGET_GETTER("type", std::string, gui2::widget)
409 {
410  if(gui2::styled_widget* sw = dynamic_cast<gui2::styled_widget*>(&w)) {
411  return sw->get_control_type();
412  }
413  else if(dynamic_cast<gui2::tree_view_node*>(&w)) {
414  return "tree_view_node";
415  }
416  else if(dynamic_cast<gui2::grid*>(&w)) {
417  return "grid";
418  }
419  else {
420  return "";
421  }
422 }
423 
424 ///////////////////////////////////////////////////////
425 ////////////////////// CALLBACKS //////////////////////
426 ///////////////////////////////////////////////////////
427 namespace {
428 
429 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
430 {
431  gui2::widget* w = wp.get_ptr();
432  if(!w) {
433  ERR_LUA << "widget was deleted\n";
434  return;
435  }
436  gui2::window* wd = w->get_window();
437  if(!wd) {
438  ERR_LUA << "cannot find window in widget callback\n";
439  return;
440  }
441  luaW_callwidgetcallback(L, w, wd, id);
442 }
443 
445 {
446  gui2::window* wd = w.get_window();
447  if(!wd) {
448  throw std::invalid_argument("the widget has no window assigned");
449  }
450  lua_pushvalue(L, value.index);
451  if (!luaW_setwidgetcallback(L, &w, wd, "on_modified")) {
452  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_modified"));
453  }
454 }
455 
456 WIDGET_SETTER("on_left_click", lua_index_raw, gui2::widget)
457 {
458  gui2::window* wd = w.get_window();
459  if(!wd) {
460  throw std::invalid_argument("the widget has no window assigned");
461  }
462  lua_pushvalue(L, value.index);
463  if (!luaW_setwidgetcallback(L, &w, wd, "on_left_click")) {
464  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_left_click"));
465  }
466 }
467 
468 WIDGET_SETTER("on_button_click", lua_index_raw, gui2::widget)
469 {
470  gui2::window* wd = w.get_window();
471  gui2::clickable_item* cl = dynamic_cast<gui2::clickable_item*>(&w);
472 
473  if(!wd) {
474  throw std::invalid_argument("the widget has no window assigned");
475  }
476  if(!cl) {
477  throw std::invalid_argument("unsupported widget");
478  }
479  lua_pushvalue(L, value.index);
480  if (!luaW_setwidgetcallback(L, &w, wd, "on_button_click")) {
481  cl->connect_click_handler(std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_button_click"));
482  }
483 }
484 
485 }
486 
487 namespace lua_widget {
488 
490 {
492  if(lua_isinteger(L, 2)) {
493 
494  if(auto pwidget = find_child_by_index(w, luaL_checkinteger(L, 2))) {
495  luaW_pushwidget(L, *pwidget);
496  return 1;
497  }
498 
499  }
500  std::string_view str = lua_check<std::string_view>(L, 2);
501 
502  tgetters::iterator it = getters.find(std::string(str));
503  if(it != getters.end()) {
504  for(const auto& func : it->second) {
505  if(func(L, w)) {
506  return 1;
507  }
508  }
509  }
510  if(luaW_getglobal(L, "gui", "widget", std::string(str).c_str())) {
511  return 1;
512  }
513  if(auto pwidget = find_child_by_name(w, std::string(str))) {
514  luaW_pushwidget(L, *pwidget);
515  return 1;
516  }
517  ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str << "\n";
518  return luaL_argerror(L, 2, "invalid property of widget");
519 }
520 
522 {
524  std::string_view str = lua_check<std::string_view>(L, 2);
525 
526 
527  tsetters::iterator it = setters.find(std::string(str));
528  if(it != setters.end()) {
529  for(const auto& func : it->second) {
530  if(func(L, 3, w)) {
531  return 0;
532  }
533  }
534  ERR_LUA << "none of "<< it->second.size() << " setters matched\n";
535  }
536  else {
537  ERR_LUA << "unknown property id : " << str << " #known properties=" << setters.size() << "\n";
538 
539  }
540  ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str << "\n";
541  return luaL_argerror(L, 2, "invalid modifiable property of widget");
542 }
543 }
int impl_widget_get(lua_State *L)
std::map< std::string, std::vector< std::function< bool(lua_State *, gui2::widget &)> >> tgetters
Small abstract helper class.
#define WIDGET_SETTER(name, value_type, widgt_type)
std::map< std::string, t_string > string_map
static gui2::widget * find_child_by_index(gui2::widget &w, int i)
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:260
This class represents a single unit of a specific type.
Definition: unit.hpp:121
virtual widget * find(const std::string &id, const bool must_be_active)
Returns a widget with the wanted id.
Definition: widget.cpp:584
int luaW_type_error(lua_State *L, int narg, const char *tname)
#define a
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:19
This file contains the window object, this object is a top level container which has the event manage...
Base class for all widgets.
Definition: widget.hpp:49
This object shows the progress of a certain action, or the value state of a certain item...
void luaW_pushwidget(lua_State *L, gui2::widget &w)
Definition: lua_widget.cpp:36
T * get_ptr()
Definition: lua_ptr.hpp:37
Definitions for the interface to Wesnoth Markup Language (WML).
A single unit type that the player may recruit.
Definition: types.hpp:45
Class for a single line text area.
Definition: text_box.hpp:141
std::size_t count_children() const
The number of children in this widget.
The listbox class.
Definition: listbox.hpp:43
#define LUA_TSTRING
Definition: lua.h:69
std::map< std::string, std::vector< std::function< bool(lua_State *, int, gui2::widget &)> >> tsetters
LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)
Definition: lauxlib.cpp:175
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:187
#define WIDGET_GETTER(name, value_type, widgt_type)
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:953
bool luaW_toboolean(lua_State *L, int n)
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:143
virtual void connect_click_handler(const event::signal_function &signal)=0
Connects a signal handler for a &#39;click&#39; event.
static lg::log_domain log_scripting_lua("scripting/lua")
This file contains the canvas object which is the part where the widgets draw (temporally) images on...
static tsetters setters
A tree view is a control that holds several items of the same or different types. ...
Definition: tree_view.hpp:60
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:175
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
std::size_t i
Definition: function.cpp:967
LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)
Definition: lauxlib.cpp:442
gui2::widget & luaW_checkwidget(lua_State *L, int n)
Definition: lua_widget.cpp:42
tree_view_node & get_child_at(int index)
window * get_window()
Get the parent window.
Definition: widget.cpp:117
#define lua_tostring(L, i)
Definition: lua.h:386
visibility
Visibility settings done by the user.
Definition: widget.hpp:59
#define ERR_LUA
int w
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:246
Base class for all visible items.
LUA_API int lua_isinteger(lua_State *L, int idx)
Definition: lapi.cpp:279
#define lua_call(L, n, r)
Definition: lua.h:283
static map_location::DIRECTION sw
static tgetters getters
A multi page is a control that contains several &#39;pages&#39; of which only one is visible.
Definition: multi_page.hpp:49
A slider is a control that can select a value by moving a grip on a groove.
Definition: slider.hpp:59
Standard logging facilities (interface).
static gui2::widget * find_child_by_name(gui2::widget &w, const std::string &m)
Small concept class.
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:799
A stacked widget holds several widgets on top of each other.
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:141
int impl_widget_set(lua_State *L)
static map_location::DIRECTION n
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:65
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
#define LUA_TBOOLEAN
Definition: lua.h:66