The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
lua_widget_methods.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2020 - 2025
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
16#include "config.hpp"
17#include "gui/core/canvas.hpp"
30#include "log.hpp"
33#include "scripting/lua_ptr.hpp"
37#include "utils/scope_exit.hpp"
38#include <functional>
39
40#include <vector>
41
42
43static lg::log_domain log_scripting_lua("scripting/lua");
44#define ERR_LUA LOG_STREAM(err, log_scripting_lua)
45
46/**
47 * Displays a window.
48 * - Arg 1: WML table describing the window.
49 * - Arg 2: function called at pre-show.
50 * - Arg 3: function called at post-show.
51 * - Ret 1: integer.
52 */
53int intf_show_dialog(lua_State* L)
54{
55 config def_cfg = luaW_checkconfig(L, 1);
57
58 auto wp = std::make_unique<gui2::window>(def);
59
60 if(!lua_isnoneornil(L, 2)) {
61 lua_pushvalue(L, 2);
62 luaW_pushwidget(L, *wp);
63 lua_call(L, 1, 0);
64 }
65
66 int v = wp->show();
67
68 if (!lua_isnoneornil(L, 3)) {
69 lua_pushvalue(L, 3);
70 luaW_pushwidget(L, *wp);
71 lua_call(L, 1, 0);
72 }
73 luaW_clearwindowtable(L, wp.get());
74 lua_pushinteger(L, v);
75 return 1;
76}
77
78static gui2::widget* find_widget_impl(lua_State* L, gui2::widget* w, int i, bool readonly)
79{
80 assert(w);
81
82 for(; !lua_isnoneornil(L, i); ++i)
83 {
84 if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w))
85 {
86 int v = lua_tointeger(L, i);
87 if(v < 1) {
88 throw std::invalid_argument("negative index");
89 }
90 int n = list->get_item_count();
91 if(v > n) {
92 if(readonly) {
93 throw std::invalid_argument("index out of range");
94 }
96 for(; n < v; ++n) {
97 list->add_row(dummy);
98 }
99 }
100 w = list->get_row_grid(v - 1);
101 } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
102 int v = lua_tointeger(L, i);
103 if(v < 1) {
104 throw std::invalid_argument("negative index");
105 }
106 int n = multi_page->get_page_count();
107 if(v > n) {
108 if(readonly) {
109 throw std::invalid_argument("index out of range");
110 }
112 for(; n < v; ++n) {
113 multi_page->add_page(dummy);
114 }
115 }
116 w = &multi_page->page_grid(v - 1);
117 } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
118 gui2::tree_view_node& tvn = tree_view->get_root_node();
119 if(lua_isnumber(L, i)) {
120 int v = lua_tointeger(L, i);
121 if(v < 1) {
122 throw std::invalid_argument("negative index");
123 }
124 int n = tvn.count_children();
125 if(v > n) {
126 throw std::invalid_argument("index out of range");
127 }
128 w = &tvn.get_child_at(v - 1);
129
130 } else {
131 std::string m = luaL_checkstring(L, i);
132 w = tvn.find(m, false);
133 }
134 } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
135 if(lua_isnumber(L, i)) {
136 int v = lua_tointeger(L, i);
137 if(v < 1) {
138 throw std::invalid_argument("negative index");
139 }
140 int n = tree_view_node->count_children();
141 if(v > n) {
142 throw std::invalid_argument("index out of range");
143 }
144 w = &tree_view_node->get_child_at(v - 1);
145
146 } else {
147 std::string m = luaL_checkstring(L, i);
148 w = tree_view_node->find(m, false);
149 }
150 } else if(gui2::stacked_widget* stacked_widget = dynamic_cast<gui2::stacked_widget*>(w)) {
151 if(lua_isnumber(L, i)) {
152 int v = lua_tointeger(L, i);
153 if(v < 1) {
154 throw std::invalid_argument("negative index");
155 }
156 int n = stacked_widget->get_layer_count();
157 if(v > n) {
158 throw std::invalid_argument("index out of range");
159 }
160 w = stacked_widget->get_layer_grid(v - 1);
161 } else {
162 std::string m = luaL_checkstring(L, i);
163 w = stacked_widget->find(m, false);
164 }
165 } else {
166 char const *m = lua_tostring(L, i);
167 if(!m) {
168 throw std::invalid_argument("expected a string");
169 }
170 w = w->find(m, false);
171 }
172 if(!w) {
173 throw std::invalid_argument("widget not found");
174 }
175 }
176
177 return w;
178}
179
180static int intf_find_widget(lua_State* L)
181{
183 auto pw = find_widget_impl(L, w, 2, false);
184 if(pw) {
185 luaW_pushwidget(L, *pw);
186 return 1;
187 }
188 return 0;
189}
190
191
192namespace
193{
194 int number_of_items(gui2::listbox& mp)
195 {
196 return mp.get_item_count();
197 }
198 int number_of_items(gui2::multi_page& mp)
199 {
200 return mp.get_page_count();
201 }
202
203 int number_of_items(gui2::tree_view_node& mp)
204 {
205 return mp.count_children();
206 }
207
208 int number_of_items(gui2::tree_view& mp)
209 {
210 return number_of_items(mp.get_root_node());
211 }
212
213 // converts a 1-based index given as lua paraemter to a 0-based index to be used in the c++ api.
214 // and checks that it is in range
215 template<typename TWidget>
216 int check_index(lua_State* L, int arg, TWidget& w, bool for_insertion, utils::optional<int>& index)
217 {
218 int nitems = number_of_items(w);
219
220 // index == nitems + 1 -> insert at the end.
221 int max = for_insertion ? nitems + 1 : nitems;
222 if(!index) {
223 index = max;
224 }
225
226 if(*index <= 0 || *index > max) {
227 luaL_argerror(L, arg, "widget child index out of range");
228 }
229 return *index - 1;
230 }
231
232 void remove_treeview_node(gui2::tree_view_node& node, std::size_t pos, int number)
233 {
234 //Not tested yet.
235 gui2::tree_view& tv = node.get_tree_view();
236 if(pos >= node.count_children()) {
237 return;
238 }
239 if(number <= 0 || number + pos > node.count_children()) {
240 number = node.count_children() - pos;
241 }
242 for(int i = 0; i < number; ++i) {
243 tv.remove_node(&node.get_child_at(pos));
244 }
245 }
246}
247
248/**
249 * Removes an entry from a list.
250 * - Arg 1: widget
251 * - Arg 2: number (optional), index of the element to delete.
252 * - Arg 3: number (optional), number of the elements to delete. (0 to delete all elements after index)
253 */
254static int intf_remove_dialog_item(lua_State* L)
255{
257 utils::optional<int> pos = lua_check<utils::optional<int>>(L, 2);
258 int number = lua_check<utils::optional<int>>(L, 3).value_or(1);
259
260 if(gui2::listbox* list = dynamic_cast<gui2::listbox*>(w)) {
261 int realpos = check_index(L, 2, *list, false, pos);
262 list->remove_row(realpos, number);
263 } else if(gui2::multi_page* multi_page = dynamic_cast<gui2::multi_page*>(w)) {
264 int realpos = check_index(L, 2, *multi_page,false, pos);
265 multi_page->remove_page(realpos, number);
266 } else if(gui2::tree_view* tree_view = dynamic_cast<gui2::tree_view*>(w)) {
267 int realpos = check_index(L, 2, *tree_view, false, pos);
268 remove_treeview_node(tree_view->get_root_node(), realpos, number);
269 } else if(gui2::tree_view_node* tree_view_node = dynamic_cast<gui2::tree_view_node*>(w)) {
270 int realpos = check_index(L, 2, *tree_view_node, false, pos);
271 remove_treeview_node(*tree_view_node, realpos, number);
272 } else {
273 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
274 }
275
276 return 0;
277}
278
279/**
280 * Removes all entries from a list.
281 * - Arg 1: widget
282*/
283static int intf_clear_items(lua_State* L)
284{
286
287 if(auto* lb = dynamic_cast<gui2::listbox*>(w)) {
288 lb->clear();
289 } else if(auto* mp = dynamic_cast<gui2::multi_page*>(w)) {
290 mp->clear();
291 } else if(auto* tv = dynamic_cast<gui2::tree_view*>(w)) {
292 tv->clear();
293 } else if(auto* tvn = dynamic_cast<gui2::tree_view_node*>(w)) {
294 tvn->clear();
295 } else {
296 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
297 }
298
299 return 0;
300}
301
302namespace { // helpers of intf_set_dialog_callback()
303 void dialog_callback(lua_State* L, lua_ptr<gui2::widget>& wp, const std::string& id)
304 {
305 gui2::widget* w = wp.get_ptr();
306 if(!w) {
307 ERR_LUA << "widget was deleted";
308 return;
309 }
310 gui2::window* wd = w->get_window();
311 if(!wd) {
312 ERR_LUA << "cannot find window in widget callback";
313 return;
314 }
315 luaW_callwidgetcallback(L, w, wd, id);
316 }
317}//unnamed namespace for helpers of intf_set_dialog_callback()
318
319/**
320 * Sets a callback on a widget of the current dialog.
321 * - Arg 1: widget.
322 * - Arg 2: function.
323 */
324static int intf_set_dialog_callback(lua_State* L)
325{
327 gui2::widget* w = wp.get_ptr();
328 assert(w);
329 gui2::window* wd = w->get_window();
330 if(!wd) {
331 throw std::invalid_argument("the widget has no window assigned");
332 }
333 if(!lua_isfunction(L, 2)) {
334 return luaL_argerror(L, 2, "callback must be a function");
335 }
336
337 lua_pushvalue(L, 2);
338 bool already_exists = luaW_setwidgetcallback(L, w, wd, "callback");
339 if(already_exists) {
340 return 0;
341 }
342
343 // TODO: i am not sure whether it is 100% safe to bind the lua_state here,
344 // (meaning whether it can happen that the lus state is destroyed)
345 // when a widgets callback is called.
346 if(gui2::clickable_item* c = dynamic_cast<gui2::clickable_item*>(w)) {
347 c->connect_click_handler(std::bind(&dialog_callback, L, wp, "callback"));
348 } else if( dynamic_cast<gui2::selectable_item*>(w)) {
349 connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
350 } else if(dynamic_cast<gui2::integer_selector*>(w)) {
351 connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
352 } else if(dynamic_cast<gui2::listbox*>(w)) {
353 connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
354 } else if(dynamic_cast<gui2::tree_view*>(w)) {
355 connect_signal_notify_modified(*w, std::bind(&dialog_callback, L, wp, "callback"));
356 } else {
357 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
358 };
359
360 return 0;
361}
362
363
364/**
365 * Sets a canvas on a widget of the current dialog.
366 * - Arg 1: widget.
367 * - Arg 2: integer.
368 * - Arg 3: WML table.
369 */
370static int intf_set_dialog_canvas(lua_State* L)
371{
373 int i = luaL_checkinteger(L, 2);
374 gui2::styled_widget* c = dynamic_cast<gui2::styled_widget*>(w);
375 if(!c) {
376 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
377 }
378
379 std::vector<gui2::canvas> &cv = c->get_canvases();
380 if(i < 1 || static_cast<unsigned>(i) > cv.size()) {
381 return luaL_argerror(L, 2, "out of bounds");
382 }
383
385 cv[i - 1].set_shapes(cfg);
386 c->queue_redraw();
387 return 0;
388}
389
390/**
391 * Sets a widget to have the focus
392 * - Arg 1: widget.
393 */
394static int intf_set_dialog_focus(lua_State* L)
395{
397 if(gui2::window* wd = w->get_window()) {
398 wd->keyboard_capture(w);
399 }
400 return 0;
401}
402
403
404/**
405 * Adds an item to a container widget that supports different types of items, for example a treeview.
406 * - Arg 1: widget.
407 * - Arg 2: string, the type (id of [node_definition]) of the new item.
408 * - Arg 3: integer (optional), where to insert the new item.
409 */
410static int intf_add_item_of_type(lua_State* L)
411{
413 gui2::widget* res = nullptr;
414 const std::string node_type = luaL_checkstring(L, 2);
415 utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 3);
416 static const gui2::widget_data data;
417
418 if(gui2::tree_view_node* twn = dynamic_cast<gui2::tree_view_node*>(w)) {
419 int realpos = check_index(L, 2, *twn, true, insert_pos);
420 res = &twn->add_child(node_type, data, realpos);
421 } else if(gui2::tree_view* tw = dynamic_cast<gui2::tree_view*>(w)) {
422 int realpos = check_index(L, 2, *tw, true, insert_pos);
423 res = &tw->get_root_node().add_child(node_type, data, realpos);
424 } else if(gui2::multi_page* mp = dynamic_cast<gui2::multi_page*>(w)) {
425 int realpos = check_index(L, 2, *mp, true, insert_pos);
426 res = &mp->add_page(node_type, realpos, data);
427 } else {
428 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
429 }
430 if(res) {
431 luaW_pushwidget(L, *res);
432 lua_push(L, insert_pos.value());
433 return 2;
434 }
435 return 0;
436}
437/**
438 * Adds an item to a container widget, for example a listbox
439 * - Arg 1: widget.
440 * - Arg 2: integer (optional), where to insert the new item.
441 */
442static int intf_add_dialog_item(lua_State* L)
443{
445 utils::optional<int> insert_pos = lua_check<utils::optional<int>>(L, 2);
446
447 gui2::widget* res = nullptr;
448 static const gui2::widget_data data;
449
450 if(gui2::listbox* lb = dynamic_cast<gui2::listbox*>(w)) {
451 int realpos = check_index(L, 2, *lb, true, insert_pos);
452 res = &lb->add_row(data, realpos);
453 } else {
454 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
455 }
456 if(res) {
457 luaW_pushwidget(L, *res);
458 lua_push(L, insert_pos.value());
459 return 2;
460 }
461 return 0;
462}
463
464/** Closes a window */
465static int intf_dialog_close(lua_State* L)
466{
468 if(gui2::window* wd = dynamic_cast<gui2::window*>(w)) {
469 wd->close();
470 return 0;
471 } else {
472 return luaL_argerror(L, lua_gettop(L), "unsupported widget");
473 }
474}
475namespace lua_widget {
476int luaW_open(lua_State* L)
477{
478 auto& lk = lua_kernel_base::get_lua_kernel<lua_kernel_base>(L);
479 lk.add_log("Adding widgets module...\n");
480 static luaL_Reg const gui_callbacks[] = {
481 //TODO: the naming is a bit arbitrary: widgets with different
482 // types of elements use add_node, widgets with only
483 // one type of element use add_element
484 { "add_item_of_type", &intf_add_item_of_type },
485 { "add_item", &intf_add_dialog_item },
486 { "focus", &intf_set_dialog_focus },
487 { "set_canvas", &intf_set_dialog_canvas },
488 { "set_callback", &intf_set_dialog_callback },
489 { "remove_items_at", &intf_remove_dialog_item },
490 { "clear_items", &intf_clear_items },
491 { "find", &intf_find_widget },
492 { "close", &intf_dialog_close },
493 { nullptr, nullptr },
494 };
495 lua_newtable(L);
496 luaL_setfuncs(L, gui_callbacks, 0);
497 return 1;
498}
499}
static auto & dummy
This file contains the canvas object which is the part where the widgets draw (temporally) images on.
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
Small concept class.
Small abstract helper class.
The listbox class.
Definition listbox.hpp:41
Small abstract helper class.
tree_view_node & get_child_at(int index)
tree_view & get_tree_view()
widget * find(const std::string_view id, const bool must_be_active) override
See widget::find.
std::size_t count_children() const
The number of children in this widget.
std::pair< std::shared_ptr< tree_view_node >, int > remove_node(tree_view_node *node)
Removes the given node as a child of its parent node.
Definition tree_view.cpp:63
Base class for all widgets.
Definition widget.hpp:55
window * get_window()
Get the parent window.
Definition widget.cpp:117
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition window.hpp:59
void keyboard_capture(widget *widget)
Definition window.cpp:1197
void close()
Requests to close the window.
Definition window.hpp:216
T must inherit enable_lua_ptr<T>
Definition lua_ptr.hpp:66
T * get_ptr()
Definition lua_ptr.hpp:69
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
This file contains the window object, this object is a top level container which has the event manage...
Standard logging facilities (interface).
config luaW_checkconfig(lua_State *L, int index)
Converts an optional table or vconfig to a config object.
void luaW_pushwidget(lua_State *L, gui2::widget &w)
void luaW_clearwindowtable(lua_State *L, gui2::window *owner)
[-0, +0, -]
gui2::widget & luaW_checkwidget(lua_State *L, int n)
bool luaW_setwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
returns true if a callback already existed.
void luaW_callwidgetcallback(lua_State *L, gui2::widget *wg, gui2::window *owner, std::string_view name)
callas a widgets callback [-0, +0, e]
lua_ptr< gui2::widget > & luaW_checkwidget_ptr(lua_State *L, int n)
static gui2::widget * find_widget_impl(lua_State *L, gui2::widget *w, int i, bool readonly)
#define ERR_LUA
static lg::log_domain log_scripting_lua("scripting/lua")
static int intf_add_dialog_item(lua_State *L)
Adds an item to a container widget, for example a listbox.
static int intf_add_item_of_type(lua_State *L)
Adds an item to a container widget that supports different types of items, for example a treeview.
static int intf_dialog_close(lua_State *L)
Closes a window.
static int intf_clear_items(lua_State *L)
Removes all entries from a list.
static int intf_set_dialog_callback(lua_State *L)
Sets a callback on a widget of the current dialog.
static int intf_set_dialog_canvas(lua_State *L)
Sets a canvas on a widget of the current dialog.
int intf_show_dialog(lua_State *L)
Displays a window.
static int intf_remove_dialog_item(lua_State *L)
Removes an entry from a list.
static int intf_set_dialog_focus(lua_State *L)
Sets a widget to have the focus.
static int intf_find_widget(lua_State *L)
std::map< std::string, widget_item > widget_data
Definition widget.hpp:36
std::map< std::string, t_string > widget_item
Definition widget.hpp:33
int luaW_open(lua_State *L)
Main entry points of multiplayer mode.
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition unicode.cpp:70
int w
Definition pathfind.cpp:188
std::string_view data
Definition picture.cpp:188
void lua_push(lua_State *L, const T &val)
static map_location::direction n
#define i
#define c