The Battle for Wesnoth  1.19.5+dev
lua_widget_attributes.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2014 - 2024
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 
19 #include "gui/widgets/combobox.hpp"
20 #include "gui/widgets/label.hpp"
21 #include "gui/widgets/listbox.hpp"
28 #include "gui/widgets/slider.hpp"
30 #include "gui/widgets/text_box.hpp"
34 #include "gui/widgets/widget.hpp"
35 #include "gui/widgets/window.hpp"
36 #include "log.hpp"
37 #include "scripting/lua_common.hpp"
39 #include "scripting/lua_unit.hpp"
41 #include "scripting/push_check.hpp"
42 #include "scripting/lua_widget.hpp"
46 
47 #include <functional>
48 
49 #include <boost/preprocessor/cat.hpp>
50 
51 #include <map>
52 #include <utility>
53 #include <vector>
54 
55 static lg::log_domain log_scripting_lua("scripting/lua");
56 #define ERR_LUA LOG_STREAM(err, log_scripting_lua)
57 
59 {
60  if(auto window = w.get_window()) {
61  window->invalidate_layout();
62  }
63 }
64 
66 {
67  assert(i > 0);
68  if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(&w)) {
69  int n = list->get_item_count();
70  if(i > n) {
71  for(; n < i; ++n) {
72  list->add_row(gui2::widget_item{});
73  }
74  }
75  return list->get_row_grid(i - 1);
76  } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(&w)) {
77  int n = multi_page->get_page_count();
78  if(i > n) {
79  for(; n < i; ++n) {
80  multi_page->add_page(gui2::widget_item{});
81  }
82  }
83  return &multi_page->page_grid(i - 1);
84  } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(&w)) {
85  gui2::tree_view_node& tvn = tree_view->get_root_node();
86  int n = tvn.count_children();
87  if(i > n) {
88  throw std::invalid_argument("out of range");
89  }
90  return &tvn.get_child_at(i - 1);
91  } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(&w)) {
92  int n = tree_view_node->count_children();
93  if(i > n) {
94  throw std::invalid_argument("out of range");
95  }
96  return &tree_view_node->get_child_at(i - 1);
97  } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(&w)) {
98  int n = stacked_widget->get_layer_count();
99  if(i > n) {
100  throw std::invalid_argument("out of range");
101  }
102  return stacked_widget->get_layer_grid(i - 1);
103  }
104  return nullptr;
105 }
106 
107 static gui2::widget* find_child_by_name(gui2::widget& w, const std::string& m)
108 {
109  return w.find(m, false);
110 }
111 
112 using tgetters = std::map<std::string, std::vector<std::function<bool(lua_State*, gui2::widget&, bool)>>>;
114 
115 using tsetters = std::map<std::string, std::vector<std::function<bool(lua_State*, int, gui2::widget&, bool)>>>;
117 
118 template<typename widget_type, typename value_type, typename action_type, bool setter>
119 void register_widget_attribute(const char* name)
120 {
121  utils::split_foreach(name, ',', 0, [](std::string_view name_part) {
122  using map_type = std::conditional_t<setter, tsetters, tgetters>;
123  using list_type = typename map_type::mapped_type;
124  using callback_type = typename list_type::value_type;
125  map_type* map;
126  callback_type fcn;
127  if constexpr(setter) {
128  map = &setters;
129  fcn = [action = action_type()](lua_State* L, int idx, gui2::widget& w, bool nop) {
130  if(widget_type* pw = dynamic_cast<widget_type*>(&w)) {
131  if(!nop) action.set(L, *pw, lua_check<value_type>(L, idx));
132  return true;
133  }
134  return false;
135  };
136  } else {
137  map = &getters;
138  fcn = [action = action_type()](lua_State* L, gui2::widget& w, bool nop) {
139  if(widget_type* pw = dynamic_cast<widget_type*>(&w)) {
140  if(!nop) lua_push(L, action.get(L, *pw));
141  return true;
142  }
143  return false;
144  };
145  }
146  list_type& list = (*map)[std::string(name_part)];
147  list.push_back(fcn);
148  });
149 }
150 
151 #define WIDGET_GETTER4(name, value_type, widgt_type, id) \
152 struct BOOST_PP_CAT(getter_, id) : public lua_getter<widgt_type, value_type> { \
153  value_type get(lua_State* L, const widgt_type& w) const override; \
154 }; \
155 struct BOOST_PP_CAT(getter_adder_, id) { \
156  BOOST_PP_CAT(getter_adder_, id) () \
157  { \
158  register_widget_attribute<widgt_type, value_type, BOOST_PP_CAT(getter_, id), false>(name); \
159  } \
160 }; \
161 static BOOST_PP_CAT(getter_adder_, id) BOOST_PP_CAT(getter_adder_instance_, id) ; \
162 value_type BOOST_PP_CAT(getter_, id)::get([[maybe_unused]] lua_State* L, const widgt_type& w) const
163 
164 
165 #define WIDGET_SETTER4(name, value_type, widgt_type, id) \
166 struct BOOST_PP_CAT(setter_, id) : public lua_setter<widgt_type, value_type> { \
167  void set(lua_State* L, widgt_type& w, const value_type& value) const override; \
168 }; \
169 struct BOOST_PP_CAT(setter_adder_, id) { \
170  BOOST_PP_CAT(setter_adder_, id) ()\
171  { \
172  register_widget_attribute<widgt_type, value_type, BOOST_PP_CAT(setter_, id), true>(name); \
173  } \
174 }; \
175 static BOOST_PP_CAT(setter_adder_, id) BOOST_PP_CAT(setter_adder_instance_, id); \
176 void BOOST_PP_CAT(setter_, id)::set([[maybe_unused]] lua_State* L, widgt_type& w, const value_type& value) const
177 
178 
179 /**
180  * @param name: string comma seperated list
181  * @param value_type: the type of the attribute, for example int or std::string
182  * @param widgt_type: the type of the widget, for example gui2::listbox
183  */
184 #define WIDGET_GETTER(name, value_type, widgt_type) WIDGET_GETTER4(name, value_type, widgt_type, __LINE__)
185 
186 #define WIDGET_SETTER(name, value_type, widgt_type) WIDGET_SETTER4(name, value_type, widgt_type, __LINE__)
187 
188 
189 /// CLASSIC
190 
191 WIDGET_GETTER("value_compat,selected_index", int, gui2::listbox)
192 {
193  return w.get_selected_row() + 1;
194 }
195 
196 WIDGET_SETTER("value_compat,selected_index", int, gui2::listbox)
197 {
198  w.select_row(value - 1);
199 }
200 
201 WIDGET_GETTER("value_compat,selected_index", int, gui2::multi_page)
202 {
203  return w.get_selected_page() + 1;
204 }
205 
206 WIDGET_SETTER("value_compat,selected_index", int, gui2::multi_page)
207 {
208  w.select_page(value -1);
209 }
210 
211 WIDGET_GETTER("value_compat,selected_index", int, gui2::stacked_widget)
212 {
213  return w.current_layer() + 1;
214 }
215 
216 WIDGET_SETTER("value_compat,selected_index", int, gui2::stacked_widget)
217 {
218  w.select_layer(value - 1);
219 }
220 
221 WIDGET_GETTER("selected_index", int, gui2::selectable_item)
222 {
223  return w.get_value() + 1;
224 }
225 
226 WIDGET_SETTER("selected_index", int, gui2::selectable_item)
227 {
228  if(value > int(w.num_states())) {
229  throw std::invalid_argument("invalid index");
230  }
231  w.set_value(value - 1);
232 }
233 
234 WIDGET_GETTER("value_compat,selected", bool, gui2::selectable_item)
235 {
236  if(w.num_states() == 2) {
237  return w.get_value_bool();
238  }
239  throw std::invalid_argument("invalid widget");
240 }
241 
242 WIDGET_SETTER("value_compat,selected", bool, gui2::selectable_item)
243 {
244  w.set_value_bool(value);
245 }
246 
247 WIDGET_GETTER("value_compat,text", std::string, gui2::text_box)
248 {
249  return w.get_value();
250 }
251 
252 WIDGET_SETTER("value_compat,text", std::string, gui2::text_box)
253 {
254  w.set_value(value);
255 }
256 
257 WIDGET_GETTER("value_compat,value", int, gui2::slider)
258 {
259  return w.get_value();
260 }
261 
262 WIDGET_SETTER("value_compat,value", int, gui2::slider)
263 {
264  w.set_value(value);
265 }
266 
267 WIDGET_GETTER("best_slider_length", int, gui2::slider)
268 {
269  return w.get_best_slider_length();
270 }
271 
272 WIDGET_SETTER("best_slider_length", int, gui2::slider)
273 {
274  if(value < 0) {
275  throw std::invalid_argument("best_slider_length must be >= 0");
276  }
277  w.set_best_slider_length(value);
278 }
279 
280 WIDGET_GETTER("max_value", int, gui2::slider)
281 {
282  return w.get_maximum_value();
283 }
284 
285 WIDGET_SETTER("max_value", int, gui2::slider)
286 {
287  w.set_value_range(w.get_minimum_value(), value);
288 }
289 
290 WIDGET_GETTER("maximum_value_label", t_string, gui2::slider)
291 {
292  return w.get_maximum_value_label();
293 }
294 
295 WIDGET_SETTER("maximum_value_label", t_string, gui2::slider)
296 {
297  w.set_maximum_value_label(value);
299 }
300 
301 WIDGET_GETTER("min_value", int, gui2::slider)
302 {
303  return w.get_minimum_value();
304 }
305 
306 WIDGET_SETTER("min_value", int, gui2::slider)
307 {
308  w.set_value_range(value, w.get_maximum_value());
309 }
310 
311 WIDGET_GETTER("minimum_value_label", t_string, gui2::slider)
312 {
313  return w.get_minimum_value_label();
314 }
315 
316 WIDGET_SETTER("minimum_value_label", t_string, gui2::slider)
317 {
318  w.set_minimum_value_label(value);
320 }
321 
322 WIDGET_GETTER("value_compat,percentage", int, gui2::progress_bar)
323 {
324  return w.get_percentage();
325 }
326 
327 WIDGET_SETTER("value_compat,percentage", int, gui2::progress_bar)
328 {
329  w.set_percentage(value);
330 }
331 
332 WIDGET_GETTER("value_compat,selected_item_path", std::vector<int>, gui2::tree_view)
333 {
334  auto res = w.selected_item()->describe_path();
335  for(int& a : res) { ++a;}
336  return res;
337 }
338 
339 WIDGET_GETTER("path", std::vector<int>, gui2::tree_view_node)
340 {
341  auto res = w.describe_path();
342  for(int& a : res) { ++a;}
343  return res;
344 }
345 
346 WIDGET_GETTER("unfolded", bool, gui2::tree_view)
347 {
348  return !w.get_root_node().is_folded();
349 }
350 
351 WIDGET_SETTER("value_compat,unfolded", bool, gui2::tree_view)
352 {
353  if(value) {
354  w.get_root_node().unfold();
355  } else {
356  w.get_root_node().fold();
357  }
358 }
359 
361 {
362  return !w.is_folded();
363 }
364 
365 WIDGET_SETTER("value_compat,unfolded", bool, gui2::tree_view_node)
366 {
367  if(value) {
368  w.unfold();
369  } else {
370  w.fold();
371  }
372 }
373 
375 {
376  if(const unit_type* ut = luaW_tounittype(L, value.index)) {
377  w.set_displayed_type(*ut);
378  } else if(unit* u = luaW_tounit(L, value.index)) {
379  w.set_displayed_unit(*u);
380  } else {
381  luaW_type_error(L, value.index, "unit or unit type");
382  }
383 }
384 
385 WIDGET_GETTER("item_count", int, gui2::combobox)
386 {
387  return w.get_item_count();
388 }
389 
390 WIDGET_GETTER("item_count", int, gui2::listbox)
391 {
392  return w.get_item_count();
393 }
394 
395 WIDGET_GETTER("item_count", int, gui2::multi_page)
396 {
397  return w.get_page_count();
398 }
399 
401 {
402  return w.get_layer_count();
403 }
404 
405 WIDGET_GETTER("item_count", int, gui2::tree_view)
406 {
407  const gui2::tree_view_node& tvn = w.get_root_node();
408  return tvn.count_children();
409 }
410 
412 {
413  return w.count_children();
414 }
415 
416 WIDGET_GETTER("use_markup", bool, gui2::styled_widget)
417 {
418  return w.get_use_markup();
419 }
420 
421 WIDGET_SETTER("use_markup", bool, gui2::styled_widget)
422 {
423  w.set_use_markup(value);
424 }
425 
426 //TODO: while i think this shortcut is useful, i'm not that happy about
427 // the name since it changes 'label' and not 'text', the first one
428 // is the label that is part of most widgets (like checkboxes), the
429 // later is specific to input textboxes.
431 {
432  w.set_use_markup(true);
433  w.set_label(value);
434 }
435 
436 WIDGET_GETTER("characters_per_line", int, gui2::label)
437 {
438  return w.get_characters_per_line();
439 }
440 
441 WIDGET_SETTER("characters_per_line", int, gui2::label)
442 {
443  if(value < 0) {
444  throw std::invalid_argument("characters_per_line must be >= 0");
445  }
446  w.set_characters_per_line(value);
448 }
449 
450 WIDGET_GETTER("editable", bool, gui2::text_box)
451 {
452  return w.is_editable();
453 }
454 
455 WIDGET_SETTER("editable", bool, gui2::text_box)
456 {
457  w.set_editable(value);
458 }
459 
460 WIDGET_GETTER("ellipsize_mode", std::string, gui2::styled_widget)
461 {
462  std::string s;
463 
464  switch(w.get_text_ellipse_mode()) {
465  case(PangoEllipsizeMode::PANGO_ELLIPSIZE_NONE):
466  s = "none";
467  break;
468  case(PangoEllipsizeMode::PANGO_ELLIPSIZE_START):
469  s = "start";
470  break;
471  case(PangoEllipsizeMode::PANGO_ELLIPSIZE_MIDDLE):
472  s = "middle";
473  break;
474  case(PangoEllipsizeMode::PANGO_ELLIPSIZE_END):
475  s = "end";
476  }
477 
478  return s;
479 }
480 
481 WIDGET_SETTER("ellipsize_mode", std::string, gui2::styled_widget)
482 {
483  if(value == "none") {
484  w.set_text_ellipse_mode(PangoEllipsizeMode::PANGO_ELLIPSIZE_NONE);
485  } else if(value == "start") {
486  w.set_text_ellipse_mode(PangoEllipsizeMode::PANGO_ELLIPSIZE_START);
487  } else if(value == "middle") {
488  w.set_text_ellipse_mode(PangoEllipsizeMode::PANGO_ELLIPSIZE_MIDDLE);
489  } else if(value == "end") {
490  w.set_text_ellipse_mode(PangoEllipsizeMode::PANGO_ELLIPSIZE_END);
491  } else {
492  throw std::invalid_argument("ellipsize_mode must be one of <none,start,middle,end>");
493  }
495 }
496 
498 {
499  return w.get_active();
500 }
501 
503 {
504  w.set_active(value);
505 }
506 
508 {
509  return w.help_message();
510 }
511 
513 {
514  w.set_help_message(value);
515 }
516 
517 WIDGET_GETTER("hint_image", std::string, gui2::combobox)
518 {
519  return w.get_hint_image();
520 }
521 
522 WIDGET_SETTER("hint_image", std::string, gui2::combobox)
523 {
524  w.set_hint_image(value);
526 }
527 
529 {
530  return w.get_hint_text();
531 }
532 
534 {
535  w.set_hint_text(value);
537 }
538 
539 WIDGET_GETTER("hint_image", std::string, gui2::text_box)
540 {
541  return w.get_hint_image();
542 }
543 
544 WIDGET_SETTER("hint_image", std::string, gui2::text_box)
545 {
546  w.set_hint_image(value);
548 }
549 
551 {
552  return w.get_hint_text();
553 }
554 
556 {
557  w.set_hint_text(value);
559 }
560 
561 WIDGET_SETTER("history", std::string, gui2::text_box)
562 {
563  w.set_history(value);
564 }
565 
566 WIDGET_GETTER("indentation_step_size", int, gui2::tree_view)
567 {
568  return w.get_indentation_step_size();
569 }
570 
571 WIDGET_SETTER("indentation_step_size", int, gui2::tree_view)
572 {
573  if(value < 0) {
574  throw std::invalid_argument("indentation_step_size must be >= 0");
575  }
576  w.set_indentation_step_size(value);
578 }
579 
580 WIDGET_GETTER("link_aware", bool, gui2::label)
581 {
582  return w.get_link_aware();
583 }
584 
585 WIDGET_SETTER("link_aware", bool, gui2::label)
586 {
587  w.set_link_aware(value);
589 }
590 
591 WIDGET_GETTER("link_aware", bool, gui2::rich_label)
592 {
593  return w.get_link_aware();
594 }
595 
596 WIDGET_SETTER("link_aware", bool, gui2::rich_label)
597 {
598  w.set_link_aware(value);
600 }
601 
602 WIDGET_GETTER("link_aware", bool, gui2::scroll_label)
603 {
604  return w.get_link_aware();
605 }
606 
607 WIDGET_SETTER("link_aware", bool, gui2::scroll_label)
608 {
609  w.set_link_aware(value);
611 }
612 
613 WIDGET_GETTER("link_aware", bool, gui2::scroll_text)
614 {
615  return w.get_link_aware();
616 }
617 
618 WIDGET_SETTER("link_aware", bool, gui2::scroll_text)
619 {
620  w.set_link_aware(value);
622 }
623 
624 WIDGET_GETTER("link_color", std::string, gui2::label)
625 {
626  return w.get_link_color().to_hex_string();
627 }
628 
629 WIDGET_GETTER("link_color", std::string, gui2::rich_label)
630 {
631  return w.get_link_color().to_hex_string();
632 }
633 
634 WIDGET_GETTER("max_input_length", int, gui2::combobox)
635 {
636  return w.get_max_input_length();
637 }
638 
639 WIDGET_SETTER("max_input_length", int, gui2::combobox)
640 {
641  if(value < 0) {
642  throw std::invalid_argument("max_input_length must be >= 0");
643  }
644  w.set_max_input_length(value);
646 }
647 
648 WIDGET_GETTER("max_input_length", int, gui2::text_box)
649 {
650  return w.get_max_input_length();
651 }
652 
653 WIDGET_SETTER("max_input_length", int, gui2::text_box)
654 {
655  if(value < 0) {
656  throw std::invalid_argument("max_input_length must be >= 0");
657  }
658  w.set_max_input_length(value);
660 }
661 
662 WIDGET_GETTER("step_size", int, gui2::slider)
663 {
664  return w.get_step_size();
665 }
666 
667 WIDGET_SETTER("step_size", int, gui2::slider)
668 {
669  if(value < 0) {
670  throw std::invalid_argument("step_size must be >= 0");
671  }
672  w.set_step_size(value);
673 }
674 
675 WIDGET_GETTER("text_alignment", std::string, gui2::styled_widget)
676 {
677  std::string s;
678 
679  switch(w.get_text_alignment()) {
680  case(PangoAlignment::PANGO_ALIGN_LEFT):
681  s = "left";
682  break;
683  case(PangoAlignment::PANGO_ALIGN_RIGHT):
684  s = "right";
685  break;
686  case(PangoAlignment::PANGO_ALIGN_CENTER):
687  s = "center";
688  }
689 
690  return s;
691 }
692 
693 WIDGET_SETTER("text_alignment", std::string, gui2::styled_widget)
694 {
695  if(value == "left") {
696  w.set_text_alignment(PangoAlignment::PANGO_ALIGN_LEFT);
697  } else if(value == "right") {
698  w.set_text_alignment(PangoAlignment::PANGO_ALIGN_RIGHT);
699  } else if(value == "center") {
700  w.set_text_alignment(PangoAlignment::PANGO_ALIGN_CENTER);
701  } else {
702  throw std::invalid_argument("text_alignment must be one of <left,center,right>");
703  }
704 }
705 
707 {
708  return w.tooltip();
709 }
710 
712 {
713  w.set_tooltip(value);
714 }
715 
716 WIDGET_GETTER("overflow_to_tooltip", bool, gui2::styled_widget)
717 {
718  return w.get_use_tooltip_on_label_overflow();
719 }
720 
721 WIDGET_SETTER("overflow_to_tooltip", bool, gui2::styled_widget)
722 {
723  w.set_use_tooltip_on_label_overflow(value);
725 }
726 
728 {
729  return w.can_wrap();
730 }
731 
733 {
734  w.set_can_wrap(value);
735 }
736 
738 {
739  return w.can_wrap();
740 }
741 
743 {
744  w.set_can_wrap(value);
745 }
746 
748 {
749  if(!luaW_getglobal(L, "gui", "widget", "set_callback")) {
750  ERR_LUA << "gui.widget.set_callback didn't exist";
751  }
752  luaW_pushwidget(L, w);
753  lua_pushvalue(L, value.index);
754  lua_call(L, 2, 0);
755 }
756 
757 WIDGET_GETTER("visible", std::string, gui2::styled_widget)
758 {
759  std::string s;
760  switch(w.get_visible()) {
762  s = "visible";
763  break;
765  s = "hidden";
766  break;
768  s = "invisible";
769  }
770 
771  return s;
772 }
773 
774 
776 {
777 
778  typedef gui2::styled_widget::visibility visibility;
779 
780  visibility flag = visibility::visible;
781 
782  switch(lua_type(L, value.index)) {
783  case LUA_TBOOLEAN:
784  flag = luaW_toboolean(L, value.index)
785  ? visibility::visible
786  : visibility::invisible;
787  break;
788  case LUA_TSTRING:
789  {
790  const std::string& str = lua_tostring(L, value.index);
791  if(str == "visible") {
792  flag = visibility::visible;
793  } else if(str == "hidden") {
794  flag = visibility::hidden;
795  } else if(str == "invisible") {
796  flag = visibility::invisible;
797  } else {
798  luaL_argerror(L, value.index, "string must be one of: visible, hidden, invisible");
799  }
800  }
801  break;
802  default:
803  luaW_type_error(L, value.index, "boolean or string");
804  }
805 
806  w.set_visible(flag);
807 
808  if(flag == visibility::hidden) {
809  // HACK: this is needed to force the widget to be repainted immediately
810  // to get rid of its ghost image.
811  gui2::window* window = w.get_window();
812  if(window) {
813  window->invalidate_layout();
814  }
815  }
816 }
817 
819 {
820  return w.get_label();
821 }
822 
823 //must be last
825 {
826  gui2::window* window = w.get_window();
827  if(window) {
828  window->invalidate_layout();
829  }
830  w.set_label(value);
831 }
832 
833 WIDGET_GETTER("type", std::string, gui2::widget)
834 {
835  if(const gui2::styled_widget* sw = dynamic_cast<const gui2::styled_widget*>(&w)) {
836  return sw->get_control_type();
837  }
838  else if(dynamic_cast<const gui2::tree_view_node*>(&w)) {
839  return "tree_view_node";
840  }
841  else if(dynamic_cast<const gui2::grid*>(&w)) {
842  return "grid";
843  }
844  else {
845  return "";
846  }
847 }
848 
849 ///////////////////////////////////////////////////////
850 ////////////////////// CALLBACKS //////////////////////
851 ///////////////////////////////////////////////////////
852 namespace {
853 
854 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
855 {
856  gui2::widget* w = wp.get_ptr();
857  if(!w) {
858  ERR_LUA << "widget was deleted";
859  return;
860  }
861  gui2::window* wd = w->get_window();
862  if(!wd) {
863  ERR_LUA << "cannot find window in widget callback";
864  return;
865  }
866  luaW_callwidgetcallback(L, w, wd, id);
867 }
868 
870 {
871  gui2::window* wd = w.get_window();
872  if(!wd) {
873  throw std::invalid_argument("the widget has no window assigned");
874  }
875  lua_pushvalue(L, value.index);
876  if (!luaW_setwidgetcallback(L, &w, wd, "on_modified")) {
877  connect_signal_notify_modified(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_modified"));
878  }
879 }
880 
881 WIDGET_SETTER("on_left_click", lua_index_raw, gui2::widget)
882 {
883  gui2::window* wd = w.get_window();
884  if(!wd) {
885  throw std::invalid_argument("the widget has no window assigned");
886  }
887  lua_pushvalue(L, value.index);
888  if (!luaW_setwidgetcallback(L, &w, wd, "on_left_click")) {
889  connect_signal_mouse_left_click(w, std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_left_click"));
890  }
891 }
892 
893 WIDGET_SETTER("on_button_click", lua_index_raw, gui2::widget)
894 {
895  gui2::window* wd = w.get_window();
896  gui2::clickable_item* cl = dynamic_cast<gui2::clickable_item*>(&w);
897 
898  if(!wd) {
899  throw std::invalid_argument("the widget has no window assigned");
900  }
901  if(!cl) {
902  throw std::invalid_argument("unsupported widget");
903  }
904  lua_pushvalue(L, value.index);
905  if (!luaW_setwidgetcallback(L, &w, wd, "on_button_click")) {
906  cl->connect_click_handler(std::bind(&dialog_callback, L, lua_ptr<gui2::widget>(w), "on_button_click"));
907  }
908 }
909 
910 }
911 
912 namespace lua_widget {
913 
914 int impl_widget_get(lua_State* L)
915 {
917  if(lua_isinteger(L, 2)) {
918 
919  if(auto pwidget = find_child_by_index(w, luaL_checkinteger(L, 2))) {
920  luaW_pushwidget(L, *pwidget);
921  return 1;
922  }
923 
924  }
925  std::string_view str = lua_check<std::string_view>(L, 2);
926 
927  tgetters::iterator it = getters.find(std::string(str));
928  if(it != getters.end()) {
929  for(const auto& func : it->second) {
930  if(func(L, w, false)) {
931  return 1;
932  }
933  }
934  }
935  if(luaW_getglobal(L, "gui", "widget", std::string(str).c_str())) {
936  return 1;
937  }
938  if(auto pwidget = find_child_by_name(w, std::string(str))) {
939  luaW_pushwidget(L, *pwidget);
940  return 1;
941  }
942  ERR_LUA << "invalid property of '" << typeid(w).name()<< "' widget :" << str;
943  std::string err = "invalid property of widget: ";
944  err += str;
945  return luaL_argerror(L, 2, err.c_str());
946 }
947 
948 int impl_widget_set(lua_State* L)
949 {
951  std::string_view str = lua_check<std::string_view>(L, 2);
952 
953 
954  tsetters::iterator it = setters.find(std::string(str));
955  if(it != setters.end()) {
956  for(const auto& func : it->second) {
957  if(func(L, 3, w, false)) {
958  return 0;
959  }
960  }
961  ERR_LUA << "none of "<< it->second.size() << " setters matched";
962  }
963  else {
964  ERR_LUA << "unknown property id : " << str << " #known properties=" << setters.size();
965 
966  }
967  ERR_LUA << "invalid modifiable property of '" << typeid(w).name()<< "' widget:" << str;
968  std::string err = "invalid modifiable property of widget: ";
969  err += str;
970  return luaL_argerror(L, 2, err.c_str());
971 }
972 
973 int impl_widget_dir(lua_State* L)
974 {
976  std::vector<std::string> keys;
977  // Add any readable keys
978  for(const auto& [key, funcs] : getters) {
979  if(key == "value_compat") continue;
980  for(const auto& func : funcs) {
981  if(func(L, w, true)){
982  keys.push_back(key);
983  break;
984  }
985  }
986  }
987  // Add any writable keys
988  for(const auto& [key, funcs] : setters) {
989  if(key == "value_compat") continue;
990  if(key == "callback") continue;
991  for(const auto& func : funcs) {
992  if(func(L, 0, w, true)){
993  keys.push_back(key);
994  break;
995  }
996  }
997  }
998  // Add any nested widget IDs
1000  for(auto child = iter_t(w); !child.at_end(); child.next()) {
1001  const auto& key = child->id();
1002  if(!key.empty() && key != w.id()) {
1003  keys.push_back(key);
1004  }
1005  }
1006  // Add the gui.widget methods
1007  luaW_getglobal(L, "gui", "widget");
1008  auto methods = luaW_get_attributes(L, -1);
1009  keys.insert(keys.end(), methods.begin(), methods.end());
1010  lua_push(L, keys);
1011  return 1;
1012 }
1013 }
Small concept class.
virtual void connect_click_handler(const event::signal &signal)=0
Connects a signal handler for a 'click' event.
Class for a combobox.
Definition: combobox.hpp:37
Base container class.
Definition: grid.hpp:32
The iterator class.
Definition: iterator.hpp:37
The listbox class.
Definition: listbox.hpp:43
A rich_label takes marked up text and shows it correctly formatted and wrapped but no scrollbars are ...
Definition: rich_label.hpp:40
Small abstract helper class.
A widget that allows the user to input text in single line.
Definition: text_box.hpp:125
tree_view_node & get_child_at(int index)
std::size_t count_children() const
The number of children in this widget.
Base class for all widgets.
Definition: widget.hpp:55
const std::string & id() const
Definition: widget.cpp:110
visibility
Visibility settings done by the user.
Definition: widget.hpp:65
@ visible
The user sets the widget visible, that means:
@ invisible
The user set the widget invisible, that means:
@ hidden
The user sets the widget hidden, that means:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:761
Tmust inherit enable_lua_ptr<T>
Definition: lua_ptr.hpp:34
T * get_ptr()
Definition: lua_ptr.hpp:37
A single unit type that the player may recruit.
Definition: types.hpp:43
This class represents a single unit of a specific type.
Definition: unit.hpp:133
std::size_t i
Definition: function.cpp:1028
int w
This file contains the window object, this object is a top level container which has the event manage...
Contains the base iterator class for the gui2 widgets.
Standard logging facilities (interface).
bool luaW_toboolean(lua_State *L, int n)
Definition: lua_common.cpp:998
int luaW_type_error(lua_State *L, int narg, const char *tname)
bool luaW_getglobal(lua_State *L, const std::vector< std::string > &path)
Pushes the value found by following the variadic names (char *), if the value is not nil.
Definition: lua_common.cpp:979
std::vector< std::string > luaW_get_attributes(lua_State *L, int idx)
This function does the actual work of grabbing all the attribute names.
unit * luaW_tounit(lua_State *L, int index, bool only_on_map)
Converts a Lua value to a unit pointer.
Definition: lua_unit.cpp:142
const unit_type * luaW_tounittype(lua_State *L, int idx)
Test if a stack element is a unit type, and return it if so.
void luaW_pushwidget(lua_State *L, gui2::widget &w)
Definition: lua_widget.cpp:35
gui2::widget & luaW_checkwidget(lua_State *L, int n)
Definition: lua_widget.cpp:41
bool luaW_setwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
returns true if a callback already existed.
Definition: lua_widget.cpp:140
void luaW_callwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
callas a widgets callback [-0, +0, e]
Definition: lua_widget.cpp:174
std::map< std::string, std::vector< std::function< bool(lua_State *, int, gui2::widget &, bool)> >> tsetters
#define WIDGET_GETTER(name, value_type, widgt_type)
#define ERR_LUA
static lg::log_domain log_scripting_lua("scripting/lua")
static gui2::widget * find_child_by_index(gui2::widget &w, int i)
#define WIDGET_SETTER(name, value_type, widgt_type)
std::map< std::string, std::vector< std::function< bool(lua_State *, gui2::widget &, bool)> >> tgetters
static tsetters setters
static gui2::widget * find_child_by_name(gui2::widget &w, const std::string &m)
void register_widget_attribute(const char *name)
static void try_invalidate_layout(gui2::widget &w)
static tgetters getters
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:203
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:177
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
logger & err()
Definition: log.cpp:307
int impl_widget_set(lua_State *L)
int impl_widget_get(lua_State *L)
int impl_widget_dir(lua_State *L)
void split_foreach(std::string_view s, char sep, const int flags, const F &f)
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
void lua_push(lua_State *L, const T &val)
Definition: push_check.hpp:425
static map_location::direction n
static map_location::direction sw
static map_location::direction s