The Battle for Wesnoth  1.15.2+dev
lua_gui2.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 "scripting/lua_gui2.hpp"
16 
17 #include "gui/core/canvas.hpp"
26 #include "gui/dialogs/message.hpp"
29 #include "gui/widgets/listbox.hpp"
34 #include "gui/widgets/slider.hpp"
36 #include "gui/widgets/text_box.hpp"
40 #include "gui/widgets/widget.hpp"
41 #include "gui/widgets/window.hpp"
42 
43 #include "config.hpp"
44 #include "log.hpp"
45 #include "scripting/lua_common.hpp"
48 #include "scripting/lua_unit.hpp"
50 #include "scripting/push_check.hpp"
52 #include "tstring.hpp"
53 #include "game_data.hpp"
54 #include "game_state.hpp"
55 
56 #include "utils/functional.hpp"
57 
58 #include <map>
59 #include <utility>
60 #include <vector>
61 #include <boost/optional.hpp>
62 
63 #include "lua/lauxlib.h" // for luaL_checkinteger, etc
64 #include "lua/lua.h" // for lua_setfield, etc
65 
66 static lg::log_domain log_scripting_lua("scripting/lua");
67 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
68 
69 static const char dlgclbkKey[] = "dialog callback";
70 
71 namespace {
72  struct scoped_dialog
73  {
74  lua_State* L;
75  scoped_dialog* prev;
76  static scoped_dialog* current;
77  std::unique_ptr<gui2::window> window;
78  typedef std::map<gui2::widget*, int> callback_map;
79  callback_map callbacks;
80 
81  scoped_dialog(lua_State* l, gui2::window* w);
82  ~scoped_dialog();
83  private:
84  scoped_dialog(const scoped_dialog&) = delete;
85  };
86 
87  scoped_dialog* scoped_dialog::current = nullptr;
88 
89  scoped_dialog::scoped_dialog(lua_State* l, gui2::window* w)
90  : L(l), prev(current), window(w), callbacks()
91  {
93  lua_createtable(L, 1, 0);
94  lua_pushvalue(L, -2);
96  lua_rawseti(L, -2, 1);
98  current = this;
99  }
100 
101  scoped_dialog::~scoped_dialog()
102  {
103  current = prev;
105  lua_pushvalue(L, -1);
107  lua_rawgeti(L, -1, 1);
108  lua_remove(L, -2);
110  }
111 }//unnamed namespace for scoped_dialog
112 
113 static gui2::widget* find_widget(lua_State* L, int i, bool readonly)
114 {
115  if(!scoped_dialog::current) {
116  luaL_error(L, "no visible dialog");
117  error_oob_call_dtors:
118  luaL_argerror(L, i, "out of bounds");
119  error_not_str_call_dtors:
120  luaW_type_error(L, i, "string");
121  error_no_wgt_call_dtors:
122  luaL_argerror(L, i, "widget not found");
123  return nullptr;
124  }
125 
126  gui2::widget* w = scoped_dialog::current->window.get();
127  for(; !lua_isnoneornil(L, i); ++i)
128  {
129  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
130  {
131  int v = lua_tointeger(L, i);
132  if(v < 1) {
133  goto error_oob_call_dtors;
134  }
135  int n = list->get_item_count();
136  if(v > n) {
137  if(readonly) {
138  goto error_oob_call_dtors;
139  }
141  for(; n < v; ++n) {
142  list->add_row(dummy);
143  }
144  }
145  w = list->get_row_grid(v - 1);
146  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
147  int v = lua_tointeger(L, i);
148  if(v < 1) {
149  goto error_oob_call_dtors;
150  }
151  int n = multi_page->get_page_count();
152  if(v > n) {
153  if(readonly) {
154  goto error_oob_call_dtors;
155  }
157  for(; n < v; ++n) {
158  multi_page->add_page(dummy);
159  }
160  }
161  w = &multi_page->page_grid(v - 1);
162  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
163  gui2::tree_view_node& tvn = tree_view->get_root_node();
164  if(lua_isnumber(L, i)) {
165  int v = lua_tointeger(L, i);
166  if(v < 1) {
167  goto error_oob_call_dtors;
168  }
169  int n = tvn.count_children();
170  if(v > n) {
171  goto error_oob_call_dtors;
172  }
173  w = &tvn.get_child_at(v - 1);
174 
175  } else {
176  std::string m = luaL_checkstring(L, i);
177  w = tvn.find(m, false);
178  }
179  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
180  if(lua_isnumber(L, i)) {
181  int v = lua_tointeger(L, i);
182  if(v < 1) {
183  goto error_oob_call_dtors;
184  }
185  int n = tree_view_node->count_children();
186  if(v > n) {
187  goto error_oob_call_dtors;
188  }
189  w = &tree_view_node->get_child_at(v - 1);
190 
191  } else {
192  std::string m = luaL_checkstring(L, i);
193  w = tree_view_node->find(m, false);
194  }
195  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
196  if(lua_isnumber(L, i)) {
197  int v = lua_tointeger(L, i);
198  if(v < 1) {
199  goto error_oob_call_dtors;
200  }
201  int n = stacked_widget->get_layer_count();
202  if(v > n) {
203  goto error_oob_call_dtors;
204  }
205  w = stacked_widget->get_layer_grid(v - 1);
206  } else {
207  std::string m = luaL_checkstring(L, i);
208  w = stacked_widget->find(m, false);
209  }
210  } else {
211  char const *m = lua_tostring(L, i);
212  if(!m) {
213  goto error_not_str_call_dtors;
214  }
215  w = w->find(m, false);
216  }
217  if(!w) {
218  goto error_no_wgt_call_dtors;
219  }
220  }
221 
222  return w;
223 }
224 
225 namespace lua_gui2 {
226 
227 /**
228  * Displays a window.
229  * - Arg 1: WML table describing the window.
230  * - Arg 2: function called at pre-show.
231  * - Arg 3: function called at post-show.
232  * - Ret 1: integer.
233  */
235 {
236  config def_cfg = luaW_checkconfig(L, 1);
237 
239  scoped_dialog w(L, gui2::build(&def));
240 
241  if(!lua_isnoneornil(L, 2)) {
242  lua_pushvalue(L, 2);
243  lua_call(L, 0, 0);
244  }
245 
246  int v = scoped_dialog::current->window->show(true, 0);
247 
248  if (!lua_isnoneornil(L, 3)) {
249  lua_pushvalue(L, 3);
250  lua_call(L, 0, 0);
251  }
252 
253  lua_pushinteger(L, v);
254  return 1;
255 }
256 
257 /**
258  * Displays a message window
259  * - Arg 1: Table describing the window
260  * - Arg 2: List of options (nil or empty table - no options)
261  * - Arg 3: Text input specifications (nil or empty table - no text input)
262  * - Ret 1: option chosen (if no options: 0 if there's text input, -2 if escape pressed, else -1)
263  * - Ret 2: string entered (empty if none, nil if no text input)
264  */
266 {
267  config txt_cfg;
268  const bool has_input = !lua_isnoneornil(L, 3) && luaW_toconfig(L, 3, txt_cfg) && !txt_cfg.empty();
269 
271  input.caption = txt_cfg["label"].str();
272  input.text = txt_cfg["text"].str();
273  input.maximum_length = txt_cfg["max_length"].to_int(256);
274  input.text_input_was_specified = has_input;
275 
277  if(!lua_isnoneornil(L, 2)) {
278  luaL_checktype(L, 2, LUA_TTABLE);
279  std::size_t n = lua_rawlen(L, 2);
280  for(std::size_t i = 1; i <= n; i++) {
281  lua_rawgeti(L, 2, i);
282  t_string short_opt;
283  config opt;
284  if(luaW_totstring(L, -1, short_opt)) {
285  opt["description"] = short_opt;
286  } else if(!luaW_toconfig(L, -1, opt)) {
287  std::ostringstream error;
288  error << "expected array of config and/or translatable strings, but index ";
289  error << i << " was a " << lua_typename(L, lua_type(L, -1));
290  return luaL_argerror(L, 2, error.str().c_str());
291  }
292  gui2::dialogs::wml_message_option option(opt["label"], opt["description"], opt["image"]);
293  if(opt["default"].to_bool(false)) {
294  options.chosen_option = i - 1;
295  }
296  options.option_list.push_back(option);
297  lua_pop(L, 1);
298  }
299  lua_getfield(L, 2, "default");
300  if(lua_isnumber(L, -1)) {
301  int i = lua_tointeger(L, -1);
302  if(i < 1 || std::size_t(i) > n) {
303  std::ostringstream error;
304  error << "default= key in options list is not a valid option index (1-" << n << ")";
305  return luaL_argerror(L, 2, error.str().c_str());
306  }
307  options.chosen_option = i - 1;
308  }
309  lua_pop(L, 1);
310  }
311 
312  const config& def_cfg = luaW_checkconfig(L, 1);
313  const std::string& title = def_cfg["title"];
314  const std::string& message = def_cfg["message"];
315 
316  using portrait = gui2::dialogs::wml_message_portrait;
317  std::unique_ptr<portrait> left;
318  std::unique_ptr<portrait> right;
319  const bool is_double = def_cfg.has_attribute("second_portrait");
320  const bool left_side = def_cfg["left_side"].to_bool(true);
321  if(is_double || left_side) {
322  left.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
323  } else {
324  // This means right side only.
325  right.reset(new portrait {def_cfg["portrait"], def_cfg["mirror"].to_bool(false)});
326  }
327  if(is_double) {
328  right.reset(new portrait {def_cfg["second_portrait"], def_cfg["second_mirror"].to_bool(false)});
329  }
330 
331  int dlg_result = gui2::dialogs::show_wml_message(title, message, left.get(), right.get(), options, input);
332 
333  if(!has_input && options.option_list.empty()) {
334  lua_pushinteger(L, dlg_result);
335  } else {
336  lua_pushinteger(L, options.chosen_option + 1);
337  }
338 
339  if(has_input) {
340  lua_pushlstring(L, input.text.c_str(), input.text.length());
341  } else {
342  lua_pushnil(L);
343  }
344 
345  return 2;
346 }
347 
348 /**
349  * Displays a popup message
350  * - Arg 1: Title (allows Pango markup)
351  * - Arg 2: Message (allows Pango markup)
352  * - Arg 3: Image (optional)
353  */
355  t_string title = luaW_checktstring(L, 1);
357  std::string image = lua_isnoneornil(L, 3) ? "" : luaL_checkstring(L, 3);
358 
359  gui2::show_transient_message(title, msg, image, true, true);
360  return 0;
361 }
362 
363 /**
364  * Displays a story screen
365  * - Arg 1: The story config
366  * - Arg 2: The default title
367  */
369  config story = luaW_checkconfig(L, 1);
370  t_string title = luaW_checktstring(L, 2);
372  return 0;
373 }
374 
375 /**
376  * Displays a popup menu at the current mouse position
377  * Best used from a [set_menu_item], to show a submenu
378  * - Arg 1: Configs defining each item, with keys icon, image/label, second_label, tooltip
379  * - Args 2, 3: Initial selection (integer); whether to parse markup (boolean)
380  */
382  std::vector<config> items = lua_check<std::vector<config>>(L, 1);
383  SDL_Rect pos {1,1,1,1};
384  SDL_GetMouseState(&pos.x, &pos.y);
385 
386  int initial = -1;
387  bool markup = false;
388  if(lua_isnumber(L, 2)) {
389  initial = lua_tointeger(L, 2) - 1;
390  markup = luaW_toboolean(L, 3);
391  } else if(lua_isnumber(L, 3)) {
392  initial = lua_tointeger(L, 3) - 1;
393  markup = luaW_toboolean(L, 2);
394  } else if(lua_isboolean(L, 2)) {
395  markup = luaW_toboolean(L, 2);
396  }
397 
398  gui2::dialogs::drop_down_menu menu(pos, items, initial, markup, false);
399  menu.show();
400  lua_pushinteger(L, menu.selected_item() + 1);
401  return 1;
402 }
403 
404 /**
405  * Displays a simple message box.
406  */
408  const t_string title = luaW_checktstring(L, 1), message = luaW_checktstring(L, 2);
409  std::string button = luaL_optstring(L, 3, "ok"), btn_style;
410  std::transform(button.begin(), button.end(), std::inserter(btn_style, btn_style.begin()), [](char c) { return std::tolower(c); });
411  bool markup = lua_isnoneornil(L, 3) ? luaW_toboolean(L, 3) : luaW_toboolean(L, 4);
412  using button_style = gui2::dialogs::message::button_style;
413  boost::optional<button_style> style;
414  if(btn_style.empty()) {
415  style = button_style::auto_close;
416  } else if(btn_style == "ok") {
417  style = button_style::ok_button;
418  } else if(btn_style == "close") {
419  style = button_style::close_button;
420  } else if(btn_style == "ok_cancel") {
421  style = button_style::ok_cancel_buttons;
422  } else if(btn_style == "cancel") {
423  style = button_style::cancel_button;
424  } else if(btn_style == "yes_no") {
425  style = button_style::yes_no_buttons;
426  }
427  if(style) {
428  int result = gui2::show_message(title, message, *style, markup, markup);
429  if(style == button_style::ok_cancel_buttons || style == button_style::yes_no_buttons) {
430  lua_pushboolean(L, result == gui2::retval::OK);
431  return 1;
432  }
433  } else {
434  gui2::show_message(title, message, button, false, markup, markup);
435  }
436  return 0;
437 }
438 
439 /**
440  * Sets the value of a widget on the current dialog.
441  * - Arg 1: scalar.
442  * - Args 2..n: path of strings and integers.
443  */
445 {
446  gui2::widget *w = find_widget(L, 2, false);
447 
448  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
449  {
450  if(lua_istable(L, 1)) {
451  // Do two passes in case has_minimum is true
452  // Probably not the best way to do it, but should work in the majority of cases.
453  std::vector<int> selected_vec = lua_check<std::vector<int>>(L, 1);
454  std::set<int> selected(selected_vec.begin(), selected_vec.end());
455  for(unsigned i = 0; i < list->get_item_count(); i++) {
456  list->select_row(i, selected.count(i + 1) > 0);
457  }
458  for(unsigned i = 0; i < list->get_item_count(); i++) {
459  list->select_row(i, selected.count(i + 1) > 0);
460  }
461  } else {
462  int v = luaL_checkinteger(L, 1);
463  int n = list->get_item_count();
464  if(1 <= v && v <= n) {
465  list->select_row(v - 1);
466  } else {
467  return luaL_argerror(L, 1, "out of bounds");
468  }
469  }
470  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
471  int v = luaL_checkinteger(L, 1);
472  int n = multi_page->get_page_count();
473  if(1 <= v && v <= n) {
474  multi_page->select_page(v - 1);
475  } else {
476  return luaL_argerror(L, 1, "out of bounds");
477  }
478  } else if (gui2::selectable_item* selectable = dynamic_cast<gui2::selectable_item*>(w)) {
479  if(selectable->num_states() == 2) {
480  selectable->set_value_bool(luaW_toboolean(L, 1));
481  } else {
482  selectable->set_value(luaL_checkinteger(L, 1) -1);
483  }
484  } else if (gui2::text_box* text_box = dynamic_cast<gui2::text_box*>(w)) {
486  const t_string& text = luaW_checktstring(L, 1);
487  text_box->set_value(text.str());
488  } else if (gui2::slider* slider = dynamic_cast<gui2::slider*>(w)) {
489  const int v = luaL_checkinteger(L, 1);
490  const int m = slider->get_minimum_value();
491  const int n = slider->get_maximum_value();
492  if(m <= v && v <= n) {
493  slider->set_value(v);
494  } else {
495  return luaL_argerror(L, 1, "out of bounds");
496  }
497  } else if(gui2::progress_bar* progress_bar = dynamic_cast<gui2::progress_bar*>(w)) {
498  const int v = luaL_checkinteger(L, 1);
499  if(0 <= v && v <= 100) {
500  progress_bar->set_percentage(v);
501  } else {
502  return luaL_argerror(L, 1, "out of bounds");
503  }
504  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
505  if(lua_istable(L, 1)) {
506  boost::dynamic_bitset<> states;
507  states.resize(stacked_widget->get_layer_count());
508  for(unsigned i : lua_check<std::vector<unsigned>>(L, 1)) {
509  if(i > 0 && i <= stacked_widget->get_layer_count()) {
510  states[i] = true;
511  }
512  }
513  stacked_widget->select_layers(states);
514  } else {
515  const int v = luaL_checkinteger(L, 1);
516  const int n = stacked_widget->get_layer_count();
517  if(v >= 0 && v <= n) {
518  stacked_widget->select_layer(v - 1);
519  }
520  }
521  } else if(gui2::unit_preview_pane* unit_preview_pane = dynamic_cast<gui2::unit_preview_pane*>(w)) {
523  if(const unit_type* ut = luaW_tounittype(L, 1)) {
524  unit_preview_pane->set_displayed_type(*ut);
525  } else if(unit* u = luaW_tounit(L, 1)) {
526  unit_preview_pane->set_displayed_unit(*u);
527  } else {
528  return luaW_type_error(L, 1, "unit or unit type");
529  }
530  } else if(gui2::tree_view_node* node = dynamic_cast<gui2::tree_view_node*>(w)) {
531  const bool unfolded = luaW_toboolean(L, 1);
532  if(unfolded) {
533  node->unfold();
534  } else {
535  node->fold();
536  }
537  } else if(gui2::multimenu_button* menu = dynamic_cast<gui2::multimenu_button*>(w)) {
538  if(lua_istable(L, 1)) {
539  boost::dynamic_bitset<> states;
540  states.resize(menu->num_options());
541  for(unsigned i : lua_check<std::vector<unsigned>>(L, 1)) {
542  if(i > 0 && i <= menu->num_options()) {
543  states[i] = true;
544  }
545  }
546  menu->select_options(states);
547  } else {
548  int v = luaL_checkinteger(L, 1);
549  int n = menu->num_options();
550  if(1 <= v && v <= n) {
551  menu->select_option(v - 1);
552  } else {
553  return luaL_argerror(L, 1, "out of bounds");
554  }
555  }
556  } else {
558  t_string v = luaW_checktstring(L, 1);
559  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
560  if(!c) {
561  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
562  }
563  c->set_label(v);
564  }
565 
566  return 0;
567 }
568 /**
569  * Sets the value of a widget on the current dialog.
570  * - Arg 1: text.
571  * - Args 2..n: path of strings and integers.
572  */
574 {
575  t_string v = luaW_checktstring(L, 1);
576  gui2::widget *w = find_widget(L, 2, false);
577  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
578  if(!c) {
579  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
580  }
581  c->set_tooltip(v);
582 
583  return 0;
584 }
585 
586 /**
587  * Gets the value of a widget on the current dialog.
588  * - Args 1..n: path of strings and integers.
589  * - Ret 1: scalar.
590  */
592 {
593  gui2::widget *w = find_widget(L, 1, true);
594  int num_rets = 1;
595 
596  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
597  {
598  lua_pushinteger(L, list->get_selected_row() + 1);
599  num_rets = 2;
600  lua_newtable(L);
601  int count = 0;
602  for(unsigned i = 0; i < list->get_item_count(); i++) {
603  if(list->row_selected(i)) {
604  count++;
605  lua_pushnumber(L, i + 1);
606  lua_rawseti(L, -2, count);
607  }
608  }
609  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
610  lua_pushinteger(L, multi_page->get_selected_page() + 1);
611  } else if(gui2::selectable_item* selectable = dynamic_cast<gui2::selectable_item*>(w)) {
612  if(selectable->num_states() == 2) {
613  lua_pushboolean(L, selectable->get_value_bool());
614  } else {
615  lua_pushinteger(L, selectable->get_value() + 1);
616  }
617  } else if(gui2::text_box* text_box = dynamic_cast<gui2::text_box*>(w)) {
618  lua_pushstring(L, text_box->get_value().c_str());
619  } else if(gui2::slider* slider = dynamic_cast<gui2::slider*>(w)) {
620  lua_pushinteger(L, slider->get_value());
621  } else if(gui2::progress_bar* progress_bar = dynamic_cast<gui2::progress_bar*>(w)) {
622  lua_pushinteger(L, progress_bar->get_percentage());
623  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
624  std::vector<int> path = tree_view->selected_item()->describe_path();
625  lua_createtable(L, path.size(), 0);
626  for(std::size_t i = 0; i < path.size(); ++i) {
627  lua_pushinteger(L, path[i] + 1);
628  lua_rawseti(L, -2, i + 1);
629  }
630  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
631  lua_pushinteger(L, stacked_widget->current_layer());
632  num_rets = 2;
633  lua_newtable(L);
634  int count = 0;
635  for(unsigned i = 0; i < stacked_widget->get_layer_count(); i++) {
636  if(stacked_widget->layer_selected(i)) {
637  count++;
638  lua_pushnumber(L, i + 1);
639  lua_rawseti(L, -2, count);
640  }
641  }
642  } else if(gui2::multimenu_button* menu = dynamic_cast<gui2::multimenu_button*>(w)) {
643  auto selected = menu->get_toggle_states();
644  int count = 0;
645  lua_newtable(L);
646  for(unsigned i = 0; i < selected.size(); i++) {
647  if(selected[i]) {
648  count++;
649  lua_pushnumber(L, i + 1);
650  lua_rawseti(L, -2, count);
651  }
652  }
653  } else {
654  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
655  }
656 
657  return num_rets;
658 }
659 namespace
660 {
661  void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
662  {
663  //Not tested yet.
664  gui2::tree_view& tv = node.get_tree_view();
665  if(pos >= node.count_children()) {
666  return;
667  }
668  if(number <= 0 || number + pos > node.count_children()) {
669  number = node.count_children() - pos;
670  }
671  for(int i = 0; i < number; ++i) {
672  tv.remove_node(&node.get_child_at(pos));
673  }
674  }
675 }
676 /**
677  * Removes an entry from a list.
678  * - Arg 1: number, index of the element to delete.
679  * - Arg 2: number, number of the elements to delete. (0 to delete all elements after index)
680  * - Args 2..n: path of strings and integers.
681  */
683 {
684  int pos = luaL_checkinteger(L, 1) - 1;
685  int number = luaL_checkinteger(L, 2);
686  gui2::widget* w = find_widget(L, 3, true);
687 
688  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
689  {
690  list->remove_row(pos, number);
691  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
692  multi_page->remove_page(pos, number);
693  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
694  remove_treeview_node(tree_view->get_root_node(), pos, number);
695  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
696  remove_treeview_node(*tree_view_node, pos, number);
697  } else {
698  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
699  }
700 
701  return 1;
702 }
703 
704 namespace { // helpers of intf_set_dialog_callback()
705  void dialog_callback(gui2::widget& w)
706  {
707  int cb;
708  {
709  scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
710  scoped_dialog::callback_map::const_iterator i = m.find(&w);
711  if(i == m.end()) {
712  return;
713  }
714  cb = i->second;
715  }
716  lua_State *L = scoped_dialog::current->L;
719  lua_rawgeti(L, -1, cb);
720  lua_remove(L, -2);
721  lua_call(L, 0, 0);
722  }
723 
724  /** Helper struct for intf_set_dialog_callback. */
725  struct dialog_callback_wrapper
726  {
727  void forward(gui2::widget* widget, bool& handled, bool& halt)
728  {
729  assert(widget);
730  dialog_callback(*widget);
731  handled = true;
732  halt = true;
733  }
734  };
735 }//unnamed namespace for helpers of intf_set_dialog_callback()
736 
737 /**
738  * Sets a callback on a widget of the current dialog.
739  * - Arg 1: function.
740  * - Args 2..n: path of strings and integers.
741  */
743 {
744  gui2::widget* w = find_widget(L, 2, true);
745 
746  scoped_dialog::callback_map &m = scoped_dialog::current->callbacks;
748  if(i != m.end()) {
751  lua_pushnil(L);
752  lua_rawseti(L, -2, i->second);
753  lua_pop(L, 1);
754  m.erase(i);
755  }
756 
757  if(lua_isnil(L, 1)) {
758  return 0;
759  }
760 
761  if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
762  static dialog_callback_wrapper wrapper;
763  c->connect_click_handler(std::bind(&dialog_callback_wrapper::forward, wrapper, w, _3, _4));
764  } else if(gui2::selectable_item* si = dynamic_cast<gui2::selectable_item*>(w)) {
765  connect_signal_notify_modified(dynamic_cast<gui2::widget&>(*si), std::bind(dialog_callback, _1));
766  } else if(gui2::integer_selector* is = dynamic_cast<gui2::integer_selector*>(w)) {
767  connect_signal_notify_modified(dynamic_cast<gui2::widget&>(*is), std::bind(dialog_callback, _1));
768  } else if(gui2::listbox* l = dynamic_cast<gui2::listbox*>(w)) {
769  static dialog_callback_wrapper wrapper;
770  connect_signal_notify_modified(*l, std::bind(&dialog_callback_wrapper::forward, wrapper, w, _3, _4));
771  } else if(gui2::tree_view* tv = dynamic_cast<gui2::tree_view*>(w)) {
772  connect_signal_notify_modified(*tv, std::bind(dialog_callback, _1));
773  } else {
774  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
775  }
776 
779  int n = lua_rawlen(L, -1) + 1;
780  m[w] = n;
781  lua_pushvalue(L, 1);
782  lua_rawseti(L, -2, n);
783  lua_pop(L, 1);
784 
785  return 0;
786 }
787 
788 /**
789  * Enables/disables Pango markup on the label of a widget of the current dialog.
790  * - Arg 1: boolean.
791  * - Args 2..n: path of strings and integers.
792  */
794 {
795  bool b = luaW_toboolean(L, 1);
796  gui2::widget* w = find_widget(L, 2, true);
797  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
798  if(!c) {
799  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
800  }
801 
802  c->set_use_markup(b);
803  return 0;
804 }
805 
806 /**
807  * Sets a canvas on a widget of the current dialog.
808  * - Arg 1: integer.
809  * - Arg 2: WML table.
810  * - Args 3..n: path of strings and integers.
811  */
813 {
814  int i = luaL_checkinteger(L, 1);
815  gui2::widget* w = find_widget(L, 3, true);
816  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
817  if(!c) {
818  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
819  }
820 
821  std::vector<gui2::canvas> &cv = c->get_canvases();
822  if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
823  return luaL_argerror(L, 1, "out of bounds");
824  }
825 
826  config cfg = luaW_checkconfig(L, 2);
827  cv[i - 1].set_cfg(cfg);
828  c->set_is_dirty(true);
829  return 0;
830 }
831 
832 /**
833  * Sets a widget to have the focus
834  * - Args 1..n: path of strings and integers.
835  */
837 {
838  gui2::widget* w = find_widget(L, 1, true);
839  scoped_dialog::current->window->keyboard_capture(w);
840  return 0;
841 }
842 
843 /**
844  * Sets a widget's state to active or inactive
845  * - Arg 1: boolean.
846  * - Args 2..n: path of strings and integers.
847  */
849 {
850  const bool b = luaW_toboolean(L, 1);
851  gui2::widget* w = find_widget(L, 2, true);
852  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
853  if(!c) {
854  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
855  }
856 
857  c->set_active(b);
858  return 0;
859 }
860 
861 /**
862  * Sets the visiblity of a widget in the current dialog.
863  * - Arg 1: boolean.
864  * - Args 2..n: path of strings and integers.
865  */
867 {
868  typedef gui2::styled_widget::visibility visibility;
869 
870  visibility flag = visibility::visible;
871 
872  switch(lua_type(L, 1)) {
873  case LUA_TBOOLEAN:
874  flag = luaW_toboolean(L, 1)
875  ? visibility::visible
876  : visibility::invisible;
877  break;
878  case LUA_TSTRING:
879  {
880  const std::string& str = lua_tostring(L, 1);
881  if(str == "visible") {
882  flag = visibility::visible;
883  } else if(str == "hidden") {
884  flag = visibility::hidden;
885  } else if(str == "invisible") {
886  flag = visibility::invisible;
887  } else {
888  return luaL_argerror(L, 1, "string must be one of: visible, hidden, invisible");
889  }
890  }
891  break;
892  default:
893  return luaW_type_error(L, 1, "boolean or string");
894  }
895 
896  gui2::widget* w = find_widget(L, 2, true);
897  gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
898  if(!c) {
899  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
900  }
901 
902  c->set_visible(flag);
903 
904  if(flag == visibility::hidden) {
905  // HACK: this is needed to force the widget to be repainted immediately
906  // to get rid of its ghost image.
907  scoped_dialog::current->window->invalidate_layout();
908  }
909 
910  return 0;
911 }
912 
914 {
916  return 0;
917 }
918 
919 int show_gamestate_inspector(const vconfig& cfg, const game_data& data, const game_state& state)
920 {
921  gui2::dialogs::gamestate_inspector::display(data.get_variables(), *state.events_manager_, state.board_, cfg["name"]);
922  return 0;
923 }
924 
925 /**
926  * Sets a widget's state to active or inactive
927  * - Arg 1: string, the type (id of [node_definition]) of the new node.
928  * - Arg 3: integer, where to insert the new node.
929  * - Args 3..n: path of strings and integers.
930  */
931 
933 {
934  const std::string node_type = luaL_checkstring(L, 1);
935  const int insert_pos = luaL_checkinteger(L, 2);
936  static const std::map<std::string, string_map> data;
937  gui2::widget* w = find_widget(L, 3, false);
938 
939  if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
940  twn->add_child(node_type, data, insert_pos);
941  } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
942  tw->get_root_node().add_child(node_type, data, insert_pos);
943  } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
944  mp->add_page(node_type, insert_pos, data);
945  } else {
946  return luaL_argerror(L, lua_gettop(L), "unsupported widget");
947  }
948  return 0;
949 }
950 
951 /**
952  * - Arg 1: string, widget type
953  * - Arg 3: string, id
954  * - Arg 3: config,
955  */
956 
958 {
959  std::string type = luaL_checkstring(L, 1);
960  std::string id = luaL_checkstring(L, 2);
961  try {
963  lua_kernel_base::get_lua_kernel<lua_kernel_base>(L).add_widget_definition(type, id);
964  }
965  } catch(const std::invalid_argument& e) {
966  return luaL_argerror(L, 1, e.what());
967  }
968  return 0;
969 }
970 
972  auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
973  lk.add_log("Adding gui module...\n");
974  static luaL_Reg const gui_callbacks[] = {
975  { "show_menu", &show_menu },
976  { "show_narration", &show_message_dialog },
977  { "show_popup", &show_popup_dialog },
978  { "show_story", &show_story },
979  { "show_prompt", &show_message_box },
980  { "add_widget_definition", &intf_add_widget_definition },
981  { nullptr, nullptr },
982  };
983  std::vector<lua_cpp::Reg> const cpp_gui_callbacks {
984  {"show_lua_console", std::bind(&lua_kernel_base::intf_show_lua_console, &lk, _1)},
985  {nullptr, nullptr}
986  };
987  lua_newtable(L);
988  luaL_setfuncs(L, gui_callbacks, 0);
989  lua_cpp::set_functions(L, cpp_gui_callbacks);
990  return 1;
991 }
992 
993 } // end namespace lua_gui2
int intf_set_dialog_active(lua_State *L)
Sets a widget&#39;s state to active or inactive.
Definition: lua_gui2.cpp:848
#define lua_isnoneornil(L, n)
Definition: lua.h:359
Parameter pack for message portrait.
Small abstract helper class.
LUA_API void lua_createtable(lua_State *L, int narray, int nrec)
Definition: lapi.cpp:684
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:152
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition: lua_gui2.cpp:913
bool text_input_was_specified
True when [text_input] appeared in [message].
int intf_add_dialog_tree_node(lua_State *L)
Sets a widget&#39;s state to active or inactive.
Definition: lua_gui2.cpp:932
static void display(const std::string &scenario_name, const config &story)
int chosen_option
The initially chosen option.
std::map< std::string, t_string > string_map
int show_story(lua_State *L)
Displays a story screen.
Definition: lua_gui2.cpp:368
LUA_API int lua_type(lua_State *L, int idx)
Definition: lapi.cpp:251
This class represents a single unit of a specific type.
Definition: unit.hpp:106
int dummy
Definition: lstrlib.cpp:1125
virtual widget * find(const std::string &id, const bool must_be_active)
Returns a widget with the wanted id.
Definition: widget.cpp:581
int luaW_type_error(lua_State *L, int narg, const char *tname)
std::string caption
The caption for the optional input text box.
LUA_API void lua_pushboolean(lua_State *L, int b)
Definition: lapi.cpp:557
LUA_API int lua_rawgeti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:658
std::string text
The initial text value.
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
int show_dialog(lua_State *L)
Displays a window.
Definition: lua_gui2.cpp:234
LUA_API void lua_rawseti(lua_State *L, int idx, lua_Integer n)
Definition: lapi.cpp:817
LUA_API int lua_gettop(lua_State *L)
Definition: lapi.cpp:167
This file contains the window object, this object is a top level container which has the event manage...
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup, const bool restore_background)
Shows a transient message to the user.
Base class for all widgets.
Definition: widget.hpp:47
#define lua_remove(L, idx)
Definition: lua.h:371
button_style
Selects the style of the buttons to be shown.
Definition: message.hpp:68
static gui2::widget * find_widget(lua_State *L, int i, bool readonly)
Definition: lua_gui2.cpp:113
#define lua_tointeger(L, i)
Definition: lua.h:342
Parameter pack for message list input options.
LUA_API int lua_rawget(lua_State *L, int idx)
Definition: lapi.cpp:647
unsigned maximum_length
The maximum length of the text.
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
const std::vector< std::string > items
const config & get_variables() const
Definition: game_data.hpp:35
int show_message_dialog(lua_State *L)
Displays a message window.
Definition: lua_gui2.cpp:265
int intf_remove_dialog_item(lua_State *L)
Removes an entry from a list.
Definition: lua_gui2.cpp:682
Simple push button.
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
int intf_set_dialog_markup(lua_State *L)
Enables/disables Pango markup on the label of a widget of the current dialog.
Definition: lua_gui2.cpp:793
bool show(const unsigned auto_close_time=0)
Shows the window.
Small abstract helper class.
Definitions for the interface to Wesnoth Markup Language (WML).
int luaW_open(lua_State *L)
Definition: lua_gui2.cpp:971
int intf_add_widget_definition(lua_State *L)
Definition: lua_gui2.cpp:957
std::pair< tree_view_node::ptr_t, int > remove_node(tree_view_node *node)
Removes the given node as a child of its parent node.
Definition: tree_view.cpp:62
A single unit type that the player may recruit.
Definition: types.hpp:42
const std::unique_ptr< game_events::manager > events_manager_
Definition: game_state.hpp:52
Class for a single line text area.
Definition: text_box.hpp:121
#define lua_pop(L, n)
Definition: lua.h:344
int intf_set_dialog_focus(lua_State *L)
Sets a widget to have the focus.
Definition: lua_gui2.cpp:836
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
std::size_t count_children() const
The number of children in this widget.
#define b
Pubic entry points for the MP workflow.
Definition: lobby_data.cpp:50
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:40
const config & options()
Definition: game.cpp:593
#define LUA_TSTRING
Definition: lua.h:68
int show_gamestate_inspector(const vconfig &cfg, const game_data &data, const game_state &state)
Definition: lua_gui2.cpp:919
LUALIB_API int luaL_argerror(lua_State *L, int arg, const char *extramsg)
Definition: lauxlib.cpp:164
void set_tooltip(const t_string &tooltip)
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:248
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:890
int intf_show_lua_console(lua_State *L)
unit * luaW_tounit(lua_State *L, int index, bool only_on_map)
Converts a Lua value to a unit pointer.
Definition: lua_unit.cpp:145
void set_visible(const visibility visible)
Definition: widget.cpp:473
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:463
t_string luaW_checktstring(lua_State *L, int index)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:620
Used by the menu_button widget.
std::string selected
This file contains the canvas object which is the part where the widgets draw (temporally) images on...
int intf_set_dialog_tooltip(lua_State *L)
Sets the value of a widget on the current dialog.
Definition: lua_gui2.cpp:573
LUA_API const char * lua_pushlstring(lua_State *L, const char *s, size_t len)
Definition: lapi.cpp:479
std::string path
Definition: game_config.cpp:39
int show_menu(lua_State *L)
Displays a popup menu at the current mouse position Best used from a [set_menu_item], to show a submenu.
Definition: lua_gui2.cpp:381
std::vector< canvas > & get_canvases()
virtual void set_use_markup(bool use_markup)
int intf_set_dialog_canvas(lua_State *L)
Sets a canvas on a widget of the current dialog.
Definition: lua_gui2.cpp:812
#define lua_newtable(L)
Definition: lua.h:346
LUA_API void lua_pushnil(lua_State *L)
Definition: lapi.cpp:450
LUA_API void lua_pushnumber(lua_State *L, lua_Number n)
Definition: lapi.cpp:458
LUALIB_API void luaL_checktype(lua_State *L, int arg, int t)
Definition: lauxlib.cpp:378
lu_byte right
Definition: lparser.cpp:1027
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
#define lua_isboolean(L, n)
Definition: lua.h:356
static lg::log_domain log_scripting_lua("scripting/lua")
bool luaW_toconfig(lua_State *L, int index, config &cfg)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:742
int show_popup_dialog(lua_State *L)
Displays a popup message.
Definition: lua_gui2.cpp:354
bool luaW_totstring(lua_State *L, int index, t_string &str)
Converts a scalar to a translatable string.
Definition: lua_common.cpp:595
#define lua_isnil(L, n)
Definition: lua.h:355
std::size_t i
Definition: function.cpp:933
int intf_set_dialog_value(lua_State *L)
Sets the value of a widget on the current dialog.
Definition: lua_gui2.cpp:444
LUALIB_API lua_Integer luaL_checkinteger(lua_State *L, int arg)
Definition: lauxlib.cpp:430
int show_wml_message(const std::string &title, const std::string &message, const wml_message_portrait *left, const wml_message_portrait *right, const wml_message_options &options, const wml_message_input &input)
Helper function to show a portrait.
LUA_API void lua_rawset(lua_State *L, int idx)
Definition: lapi.cpp:801
tree_view_node & get_child_at(int index)
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
Definition: lua_common.cpp:819
window * get_window()
Get the parent window.
Definition: widget.cpp:114
window * build(const builder_window::window_resolution *definition)
Builds a window.
Helper class for message options.
Definition: wml_message.hpp:27
lua_check_impl::remove_constref< T > lua_check(lua_State *L, int n)
Definition: push_check.hpp:357
#define lua_tostring(L, i)
Definition: lua.h:366
visibility
Visibility settings done by the user.
Definition: widget.hpp:57
int w
LUA_API void lua_pushvalue(lua_State *L, int idx)
Definition: lapi.cpp:237
Base class for all visible items.
LUA_API int lua_isnumber(lua_State *L, int idx)
Definition: lapi.cpp:276
#define lua_call(L, n, r)
Definition: lua.h:274
LUALIB_API int luaL_error(lua_State *L, const char *fmt,...)
Definition: lauxlib.cpp:223
static const char dlgclbkKey[]
Definition: lua_gui2.cpp:69
int intf_set_dialog_callback(lua_State *L)
Sets a callback on a widget of the current dialog.
Definition: lua_gui2.cpp:742
tree_view & get_tree_view()
LUA_API size_t lua_rawlen(lua_State *L, int idx)
Definition: lapi.cpp:392
#define LUA_REGISTRYINDEX
Definition: lua.h:42
std::vector< wml_message_option > option_list
A list of options to select in the dialog.
The multi page class.
Definition: multi_page.hpp:35
#define lua_istable(L, n)
Definition: lua.h:353
virtual void set_active(const bool active)=0
Sets the styled_widget&#39;s state.
map_location prev
Definition: astarsearch.cpp:65
lu_byte left
Definition: lparser.cpp:1026
A slider.
Definition: slider.hpp:33
game_board board_
Definition: game_state.hpp:46
A variable-expanding proxy for the config class.
Definition: variable.hpp:44
this module manages the cache of images.
Standard logging facilities (interface).
bool add_single_widget_definition(const std::string &widget_type, const std::string &definition_id, const config &cfg)
Adds a widget definition to the default GUI.
Small concept class.
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:805
#define e
int intf_get_dialog_value(lua_State *L)
Gets the value of a widget on the current dialog.
Definition: lua_gui2.cpp:591
Dialog was closed with the OK button.
Definition: retval.hpp:34
LUALIB_API void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup)
Definition: lauxlib.cpp:934
int show_message_box(lua_State *L)
Displays a simple message box.
Definition: lua_gui2.cpp:407
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
mock_char c
const std::string & str() const
Definition: tstring.hpp:186
LUA_API int lua_getfield(lua_State *L, int idx, const char *k)
Definition: lapi.cpp:622
#define LUA_TTABLE
Definition: lua.h:69
static map_location::DIRECTION n
LUA_API void lua_pushinteger(lua_State *L, lua_Integer n)
Definition: lapi.cpp:466
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
#define luaL_optstring(L, n, d)
Definition: lauxlib.h:125
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
LUA_API const char * lua_pushstring(lua_State *L, const char *s)
Definition: lapi.cpp:491
bool empty() const
Definition: config.cpp:884
#define LUA_TBOOLEAN
Definition: lua.h:65
int intf_set_dialog_visible(lua_State *L)
Sets the visiblity of a widget in the current dialog.
Definition: lua_gui2.cpp:866
LUA_API const char * lua_typename(lua_State *L, int t)
Definition: lapi.cpp:257
Parameter pack for message text input options.
void set_functions(lua_State *L, const std::vector< lua_cpp::Reg > &functions)
Analogous to lua_setfuncs, it registers a collection of function wrapper objects into a table...
#define luaL_checkstring(L, n)
Definition: lauxlib.h:124