The Battle for Wesnoth  1.15.12+dev
lua_widget_attributes.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2018 by Chris Beck <render787@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #include "gui/core/canvas.hpp"
19 #include "gui/widgets/listbox.hpp"
24 #include "gui/widgets/slider.hpp"
26 #include "gui/widgets/text_box.hpp"
30 #include "gui/widgets/widget.hpp"
31 #include "gui/widgets/window.hpp"
32 #include "config.hpp"
33 #include "log.hpp"
34 #include "scripting/lua_common.hpp"
37 #include "scripting/lua_unit.hpp"
39 #include "scripting/push_check.hpp"
40 #include "scripting/lua_widget.hpp"
43 #include "tstring.hpp"
44 #include "game_data.hpp"
45 #include "game_state.hpp"
46 
47 #include <functional>
49 
50 #include <boost/preprocessor/cat.hpp>
51 
52 #include <map>
53 #include <utility>
54 #include <vector>
55 
56 #include "lua/lauxlib.h" // for luaL_checkinteger, etc
57 #include "lua/lua.h" // for 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  // scoped_dialog::current->window->invalidate_layout();
391  //}
392 }
393 
394 //must be last
396 {
397  w.set_label(value);
398 }
399 
400 WIDGET_GETTER("type", std::string, gui2::widget)
401 {
402  if(gui2::styled_widget* sw = dynamic_cast<gui2::styled_widget*>(&w)) {
403  return sw->get_control_type();
404  }
405  else if(dynamic_cast<gui2::tree_view_node*>(&w)) {
406  return "tree_view_node";
407  }
408  else if(dynamic_cast<gui2::grid*>(&w)) {
409  return "grid";
410  }
411  else {
412  return "";
413  }
414 }
415 
416 ///////////////////////////////////////////////////////
417 ////////////////////// CALLBACKS //////////////////////
418 ///////////////////////////////////////////////////////
419 namespace {
420 
421 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
422 {
423  gui2::widget* w = wp.get_ptr();
424  if(!w) {
425  ERR_LUA << "widget was deleted\n";
426  return;
427  }
428  gui2::window* wd = w->get_window();
429  if(!wd) {
430  ERR_LUA << "cannot find window in widget callback\n";
431  return;
432  }
433  luaW_callwidgetcallback(L, w, wd, id);
434 }
435 
437 {
438  gui2::window* wd = w.get_window();
439  if(!wd) {
440  throw std::invalid_argument("the widget has no window assigned");
441  }
442  lua_pushvalue(L, value.index);
443  if (!luaW_setwidgetcallback(L, &w, wd, "on_modified")) {
444  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_modified"));
445  }
446 }
447 
448 WIDGET_SETTER("on_left_click", lua_index_raw, gui2::widget)
449 {
450  gui2::window* wd = w.get_window();
451  if(!wd) {
452  throw std::invalid_argument("the widget has no window assigned");
453  }
454  lua_pushvalue(L, value.index);
455  if (!luaW_setwidgetcallback(L, &w, wd, "on_left_click")) {
456  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_left_click"));
457  }
458 }
459 
460 WIDGET_SETTER("on_button_click", lua_index_raw, gui2::widget)
461 {
462  gui2::window* wd = w.get_window();
463  gui2::clickable_item* cl = dynamic_cast<gui2::clickable_item*>(&w);
464 
465  if(!wd) {
466  throw std::invalid_argument("the widget has no window assigned");
467  }
468  if(!cl) {
469  throw std::invalid_argument("unsupported widget");
470  }
471  lua_pushvalue(L, value.index);
472  if (!luaW_setwidgetcallback(L, &w, wd, "on_button_click")) {
473  cl->connect_click_handler(std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_button_click"));
474  }
475 }
476 
477 }
478 
479 namespace lua_widget {
480 
482 {
484  if(lua_isinteger(L, 2)) {
485 
486  if(auto pwidget = find_child_by_index(w, luaL_checkinteger(L, 2))) {
487  luaW_pushwidget(L, *pwidget);
488  return 1;
489  }
490 
491  }
492  std::string_view str = lua_check<std::string_view>(L, 2);
493 
494  tgetters::iterator it = getters.find(std::string(str));
495  if(it != getters.end()) {
496  for(const auto& func : it->second) {
497  if(func(L, w)) {
498  return 1;
499  }
500  }
501  }
502  if(luaW_getglobal(L, "gui", "widget", std::string(str).c_str())) {
503  return 1;
504  }
505  if(auto pwidget = find_child_by_name(w, std::string(str))) {
506  luaW_pushwidget(L, *pwidget);
507  return 1;
508  }
509  ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str << "\n";
510  return luaL_argerror(L, 2, "invalid property of widget");
511 }
512 
514 {
516  std::string_view str = lua_check<std::string_view>(L, 2);
517 
518 
519  tsetters::iterator it = setters.find(std::string(str));
520  if(it != setters.end()) {
521  for(const auto& func : it->second) {
522  if(func(L, 3, w)) {
523  return 0;
524  }
525  }
526  ERR_LUA << "none of "<< it->second.size() << " setters matched\n";
527  }
528  else {
529  ERR_LUA << "unknown property id : " << str << " #known properties=" << setters.size() << "\n";
530 
531  }
532  ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str << "\n";
533  return luaL_argerror(L, 2, "invalid modifiable property of widget");
534 }
535 }
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:120
virtual widget * find(const std::string &id, const bool must_be_active)
Returns a widget with the wanted id.
Definition: widget.cpp:583
int luaW_type_error(lua_State *L, int narg, const char *tname)
#define a
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:18
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:36
Definitions for the interface to Wesnoth Markup Language (WML).
A single unit type that the player may recruit.
Definition: types.hpp:44
Class for a single line text area.
Definition: text_box.hpp:140
std::size_t count_children() const
The number of children in this widget.
The listbox class.
Definition: listbox.hpp:42
#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:186
#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:874
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:893
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
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:59
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:940
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:41
tree_view_node & get_child_at(int index)
window * get_window()
Get the parent window.
Definition: widget.cpp:116
#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:48
A slider is a control that can select a value by moving a grip on a groove.
Definition: slider.hpp:58
Standard logging facilities (interface).
static gui2::widget * find_child_by_name(gui2::widget &w, const std::string &m)
Small concept class.
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:64
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
#define LUA_TBOOLEAN
Definition: lua.h:66