The Battle for Wesnoth  1.17.4+dev
lua_widget_attributes.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2022
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, lua_setfield, etc
58 
59 static lg::log_domain log_scripting_lua("scripting/lua");
60 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
61 
63 {
64  assert(i > 0);
65  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(&w)) {
66  int n = list->get_item_count();
67  if(i > n) {
68  for(; n < i; ++n) {
69  list->add_row(utils::string_map());
70  }
71  }
72  return list->get_row_grid(i - 1);
73  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(&w)) {
74  int n = multi_page->get_page_count();
75  if(i > n) {
76  for(; n < i; ++n) {
77  multi_page->add_page(utils::string_map());
78  }
79  }
80  return &multi_page->page_grid(i - 1);
81  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(&w)) {
82  gui2::tree_view_node& tvn = tree_view->get_root_node();
83  int n = tvn.count_children();
84  if(i > n) {
85  throw std::invalid_argument("out of range");
86  }
87  return &tvn.get_child_at(i - 1);
88  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(&w)) {
89  int n = tree_view_node->count_children();
90  if(i > n) {
91  throw std::invalid_argument("out of range");
92  }
93  return &tree_view_node->get_child_at(i - 1);
94  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(&w)) {
95  int n = stacked_widget->get_layer_count();
96  if(i > n) {
97  throw std::invalid_argument("out of range");
98  }
99  return stacked_widget->get_layer_grid(i - 1);
100  }
101  return nullptr;
102 }
103 
104 static gui2::widget* find_child_by_name(gui2::widget& w, const std::string& m)
105 {
106  return w.find(m, false);
107 }
108 
109 using tgetters = std::map<std::string, std::vector<std::function<bool(lua_State*, gui2::widget&)>>>;
111 
112 using tsetters = std::map<std::string, std::vector<std::function<bool(lua_State*, int, gui2::widget&)>>>;
114 
115 #define WIDGET_GETTER4(name, value_type, widgt_type, id) \
116 /* use a class member for L to surpress unused parameter wanring */ \
117 struct BOOST_PP_CAT(getter_, id) { value_type do_it(widgt_type& w); lua_State* L; }; \
118 struct BOOST_PP_CAT(getter_adder_, id) { \
119  BOOST_PP_CAT(getter_adder_, id) () \
120  { \
121  utils::split_foreach(name, ',', 0, [](std::string_view name_part){\
122  getters[std::string(name_part)].push_back([](lua_State* L, gui2::widget& w) { \
123  if(widgt_type* pw = dynamic_cast<widgt_type*>(&w)) { \
124  lua_push(L, BOOST_PP_CAT(getter_, id){L}.do_it(*pw)); \
125  return true; \
126  } \
127  return false; \
128  }); \
129  }); \
130  } \
131 }; \
132 static BOOST_PP_CAT(getter_adder_, id) BOOST_PP_CAT(getter_adder_instance_, id) ; \
133 value_type BOOST_PP_CAT(getter_, id)::do_it(widgt_type& w)
134 
135 
136 #define WIDGET_SETTER4(name, value_type, widgt_type, id) \
137 struct BOOST_PP_CAT(setter_, id) { void do_it(widgt_type& w, const value_type& value); lua_State* L; }; \
138 struct BOOST_PP_CAT(setter_adder_, id) { \
139  BOOST_PP_CAT(setter_adder_, id) ()\
140  { \
141  utils::split_foreach(name, ',', 0, [](std::string_view name_part){\
142  setters[std::string(name_part)].push_back([](lua_State* L, int idx, gui2::widget& w) { \
143  if(widgt_type* pw = dynamic_cast<widgt_type*>(&w)) { \
144  BOOST_PP_CAT(setter_, id){L}.do_it(*pw, lua_check<value_type>(L, idx)); \
145  return true; \
146  } \
147  return false; \
148  }); \
149  }); \
150  } \
151 }; \
152 static BOOST_PP_CAT(setter_adder_, id) BOOST_PP_CAT(setter_adder_instance_, id); \
153 void BOOST_PP_CAT(setter_, id)::do_it(widgt_type& w, const value_type& value)
154 
155 
156 /**
157  * @param name: string comma seperated list
158  * @param value_type: the type of the attribute, for example int or std::string
159  * @param widgt_type: the type of the widget, for example gui2::listbox
160  */
161 #define WIDGET_GETTER(name, value_type, widgt_type) WIDGET_GETTER4(name, value_type, widgt_type, __LINE__)
162 
163 #define WIDGET_SETTER(name, value_type, widgt_type) WIDGET_SETTER4(name, value_type, widgt_type, __LINE__)
164 
165 
166 /// CLASSIC
167 
168 WIDGET_GETTER("value_compat,selected_index", int, gui2::listbox)
169 {
170  return w.get_selected_row() + 1;
171 }
172 
173 WIDGET_SETTER("value_compat,selected_index", int, gui2::listbox)
174 {
175  w.select_row(value - 1);
176 }
177 
178 WIDGET_GETTER("value_compat,selected_index", int, gui2::multi_page)
179 {
180  return w.get_selected_page() + 1;
181 }
182 
183 WIDGET_SETTER("value_compat,selected_index", int, gui2::multi_page)
184 {
185  w.select_page(value -1);
186 }
187 
188 WIDGET_GETTER("value_compat,selected_index", int, gui2::stacked_widget)
189 {
190  return w.current_layer() + 1;
191 }
192 
193 WIDGET_SETTER("value_compat,selected_index", int, gui2::stacked_widget)
194 {
195  w.select_layer(value - 1);
196 }
197 
198 WIDGET_GETTER("selected_index", int, gui2::selectable_item)
199 {
200  return w.get_value() + 1;
201 }
202 
203 WIDGET_SETTER("selected_index", int, gui2::selectable_item)
204 {
205  if(value > int(w.num_states())) {
206  throw std::invalid_argument("invalid index");
207  }
208  w.set_value(value + 1);
209 }
210 
211 WIDGET_GETTER("value_compat,selected", bool, gui2::selectable_item)
212 {
213  if(w.num_states() == 2) {
214  return w.get_value_bool();
215  }
216  throw std::invalid_argument("invalid widget");
217 }
218 
219 WIDGET_SETTER("value_compat,selected", bool, gui2::selectable_item)
220 {
221  w.set_value_bool(value);
222 }
223 
224 WIDGET_GETTER("value_compat,text", std::string, gui2::text_box)
225 {
226  return w.get_value();
227 }
228 
229 WIDGET_SETTER("value_compat,text", std::string, gui2::text_box)
230 {
231  w.set_value(value);
232 }
233 
234 WIDGET_GETTER("value_compat,value", int, gui2::slider)
235 {
236  return w.get_value();
237 }
238 
239 WIDGET_SETTER("value_compat,value", int, gui2::slider)
240 {
241  w.set_value(value);
242 }
243 
244 WIDGET_GETTER("max_value", int, gui2::slider)
245 {
246  return w.get_maximum_value();
247 }
248 
249 WIDGET_SETTER("max_value", int, gui2::slider)
250 {
251  w.set_value_range(w.get_minimum_value(), value);
252 }
253 
254 WIDGET_GETTER("min_value", int, gui2::slider)
255 {
256  return w.get_minimum_value();
257 }
258 
259 WIDGET_SETTER("min_value", int, gui2::slider)
260 {
261  w.set_value_range(value, w.get_maximum_value());
262 }
263 
264 WIDGET_GETTER("value_compat,percentage", int, gui2::progress_bar)
265 {
266  return w.get_percentage();
267 }
268 
269 WIDGET_SETTER("value_compat,percentage", int, gui2::progress_bar)
270 {
271  w.set_percentage(value);
272 }
273 
274 WIDGET_GETTER("value_compat,selected_item_path", std::vector<int>, gui2::tree_view)
275 {
276  auto res = w.selected_item()->describe_path();
277  for(int& a : res) { ++a;}
278  return res;
279 }
280 
281 WIDGET_GETTER("path", std::vector<int>, gui2::tree_view_node)
282 {
283  auto res = w.describe_path();
284  for(int& a : res) { ++a;}
285  return res;
286 }
287 
288 WIDGET_SETTER("value_compat,unfolded", bool, gui2::tree_view_node)
289 {
290  if(value) {
291  w.unfold();
292  } else {
293  w.fold();
294  }
295 }
296 
298 {
299  if(const unit_type* ut = luaW_tounittype(L, value.index)) {
300  w.set_displayed_type(*ut);
301  } else if(unit* u = luaW_tounit(L, value.index)) {
302  w.set_displayed_unit(*u);
303  } else {
304  luaW_type_error(L, value.index, "unit or unit type");
305  }
306 }
307 
308 WIDGET_GETTER("item_count", int, gui2::multi_page)
309 {
310  return w.get_page_count();
311 }
312 
313 WIDGET_GETTER("item_count", int, gui2::listbox)
314 {
315  return w.get_item_count();
316 }
317 
318 WIDGET_SETTER("use_markup", bool, gui2::styled_widget)
319 {
320  w.set_use_markup(value);
321 }
322 
323 //TODO: while i think this shortcut is useful, i'm not that happy about
324 // the name since it changes 'label' and not 'text', the first one
325 // is the label that is part of most widgets (like checkboxes), the
326 // later is specific to input textboxes.
328 {
329  w.set_use_markup(true);
330  w.set_label(value);
331 }
332 
334 {
335  w.set_active(value);
336 }
337 
339 {
340  w.set_tooltip(value);
341 }
342 
343 
345 {
346  if(!luaW_getglobal(L, "gui", "widget", "set_callback")) {
347  ERR_LUA << "gui.widget.set_callback didn't exist\n";
348  }
349  luaW_pushwidget(L, w);
350  lua_pushvalue(L, value.index);
351  lua_call(L, 2, 0);
352 }
353 
355 {
356 
357  typedef gui2::styled_widget::visibility visibility;
358 
359  visibility flag = visibility::visible;
360 
361  switch(lua_type(L, value.index)) {
362  case LUA_TBOOLEAN:
363  flag = luaW_toboolean(L, value.index)
364  ? visibility::visible
365  : visibility::invisible;
366  break;
367  case LUA_TSTRING:
368  {
369  const std::string& str = lua_tostring(L, value.index);
370  if(str == "visible") {
371  flag = visibility::visible;
372  } else if(str == "hidden") {
373  flag = visibility::hidden;
374  } else if(str == "invisible") {
375  flag = visibility::invisible;
376  } else {
377  luaL_argerror(L, value.index, "string must be one of: visible, hidden, invisible");
378  }
379  }
380  break;
381  default:
382  luaW_type_error(L, value.index, "boolean or string");
383  }
384 
385  w.set_visible(flag);
386 
387  if(flag == visibility::hidden) {
388  // HACK: this is needed to force the widget to be repainted immediately
389  // to get rid of its ghost image.
390  gui2::window* window = w.get_window();
391  if(window) {
392  window->invalidate_layout();
393  }
394  }
395 }
396 
397 //must be last
399 {
400  gui2::window* window = w.get_window();
401  if(window) {
402  window->invalidate_layout();
403  }
404  w.set_label(value);
405 }
406 
407 WIDGET_GETTER("type", std::string, gui2::widget)
408 {
409  if(gui2::styled_widget* sw = dynamic_cast<gui2::styled_widget*>(&w)) {
410  return sw->get_control_type();
411  }
412  else if(dynamic_cast<gui2::tree_view_node*>(&w)) {
413  return "tree_view_node";
414  }
415  else if(dynamic_cast<gui2::grid*>(&w)) {
416  return "grid";
417  }
418  else {
419  return "";
420  }
421 }
422 
423 ///////////////////////////////////////////////////////
424 ////////////////////// CALLBACKS //////////////////////
425 ///////////////////////////////////////////////////////
426 namespace {
427 
428 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
429 {
430  gui2::widget* w = wp.get_ptr();
431  if(!w) {
432  ERR_LUA << "widget was deleted\n";
433  return;
434  }
435  gui2::window* wd = w->get_window();
436  if(!wd) {
437  ERR_LUA << "cannot find window in widget callback\n";
438  return;
439  }
440  luaW_callwidgetcallback(L, w, wd, id);
441 }
442 
444 {
445  gui2::window* wd = w.get_window();
446  if(!wd) {
447  throw std::invalid_argument("the widget has no window assigned");
448  }
449  lua_pushvalue(L, value.index);
450  if (!luaW_setwidgetcallback(L, &w, wd, "on_modified")) {
451  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_modified"));
452  }
453 }
454 
455 WIDGET_SETTER("on_left_click", lua_index_raw, gui2::widget)
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_left_click")) {
463  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_left_click"));
464  }
465 }
466 
467 WIDGET_SETTER("on_button_click", lua_index_raw, gui2::widget)
468 {
469  gui2::window* wd = w.get_window();
470  gui2::clickable_item* cl = dynamic_cast<gui2::clickable_item*>(&w);
471 
472  if(!wd) {
473  throw std::invalid_argument("the widget has no window assigned");
474  }
475  if(!cl) {
476  throw std::invalid_argument("unsupported widget");
477  }
478  lua_pushvalue(L, value.index);
479  if (!luaW_setwidgetcallback(L, &w, wd, "on_button_click")) {
480  cl->connect_click_handler(std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_button_click"));
481  }
482 }
483 
484 }
485 
486 namespace lua_widget {
487 
488 int impl_widget_get(lua_State* L)
489 {
491  if(lua_isinteger(L, 2)) {
492 
493  if(auto pwidget = find_child_by_index(w, luaL_checkinteger(L, 2))) {
494  luaW_pushwidget(L, *pwidget);
495  return 1;
496  }
497 
498  }
499  std::string_view str = lua_check<std::string_view>(L, 2);
500 
501  tgetters::iterator it = getters.find(std::string(str));
502  if(it != getters.end()) {
503  for(const auto& func : it->second) {
504  if(func(L, w)) {
505  return 1;
506  }
507  }
508  }
509  if(luaW_getglobal(L, "gui", "widget", std::string(str).c_str())) {
510  return 1;
511  }
512  if(auto pwidget = find_child_by_name(w, std::string(str))) {
513  luaW_pushwidget(L, *pwidget);
514  return 1;
515  }
516  ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str << "\n";
517  return luaL_argerror(L, 2, "invalid property of widget");
518 }
519 
520 int impl_widget_set(lua_State* L)
521 {
523  std::string_view str = lua_check<std::string_view>(L, 2);
524 
525 
526  tsetters::iterator it = setters.find(std::string(str));
527  if(it != setters.end()) {
528  for(const auto& func : it->second) {
529  if(func(L, 3, w)) {
530  return 0;
531  }
532  }
533  ERR_LUA << "none of "<< it->second.size() << " setters matched\n";
534  }
535  else {
536  ERR_LUA << "unknown property id : " << str << " #known properties=" << setters.size() << "\n";
537 
538  }
539  ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str << "\n";
540  return luaL_argerror(L, 2, "invalid modifiable property of widget");
541 }
542 }
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)
This class represents a single unit of a specific type.
Definition: unit.hpp:120
virtual widget * find(const std::string &id, const bool must_be_active)
Returns a widget with the wanted id.
Definition: widget.cpp:582
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:35
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:45
std::map< std::string, std::vector< std::function< bool(lua_State *, int, gui2::widget &)> >> tsetters
#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:952
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:971
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
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:174
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
gui2::widget & luaW_checkwidget(lua_State *L, int n)
Definition: lua_widget.cpp:41
tree_view_node & get_child_at(int index)
window * get_window()
Get the parent window.
Definition: widget.cpp:118
visibility
Visibility settings done by the user.
Definition: widget.hpp:59
#define ERR_LUA
int w
Base class for all visible items.
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
virtual void connect_click_handler(const event::signal &signal)=0
Connects a signal handler for a &#39;click&#39; event.
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:797
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:140
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:66
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:198