1 /*
2    Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
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 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "gui/core/window_builder.hpp"
18 
19 #include "formula/string_utils.hpp"
20 #include "gettext.hpp"
21 #include "gui/core/log.hpp"
22 #include "gui/core/gui_definition.hpp"
23 #include "gui/core/static_registry.hpp"
24 #include "gui/core/window_builder/helper.hpp"
25 #include "gui/core/window_builder/instance.hpp"
26 #include "gui/widgets/pane.hpp"
27 #include "gui/widgets/settings.hpp"
28 #include "gui/widgets/viewport.hpp"
29 #include "gui/widgets/window.hpp"
30 #include "wml_exception.hpp"
31 
32 #include "utils/functional.hpp"
33 
34 namespace gui2
35 {
36 /*WIKI
37  * @page = GUIWidgetInstanceWML
38  * @order = 1
39  *
40  * {{Autogenerated}}
41  *
42  * = Widget instance =
43  *
44  * Inside a grid (which is inside all container widgets) a widget is
45  * instantiated. With this instantiation some more variables of a widget can
46  * be tuned. This page will describe what can be tuned.
47  *
48  */
build(const builder_window::window_resolution * definition)49 window* build(const builder_window::window_resolution* definition)
50 {
51 	// We set the values from the definition since we can only determine the
52 	// best size (if needed) after all widgets have been placed.
53 	window* win = new window(definition);
54 	assert(win);
55 
56 	for(const auto& lg : definition->linked_groups) {
57 		if(win->has_linked_size_group(lg.id)) {
58 			t_string msg = VGETTEXT("Linked '$id' group has multiple definitions.", {{"id", lg.id}});
59 
60 			FAIL(msg);
61 		}
62 
63 		win->init_linked_size_group(lg.id, lg.fixed_width, lg.fixed_height);
64 	}
65 
66 	win->set_click_dismiss(definition->click_dismiss);
67 
68 	const auto conf = win->cast_config_to<window_definition>();
69 	assert(conf);
70 
71 	if(conf->grid) {
72 		win->init_grid(conf->grid);
73 		win->finalize(definition->grid);
74 	} else {
75 		win->init_grid(definition->grid);
76 	}
77 
78 	win->add_to_keyboard_chain(win);
79 
80 	return win;
81 }
82 
build(const std::string & type)83 window* build(const std::string& type)
84 {
85 	const builder_window::window_resolution& definition = get_window_builder(type);
86 	window* window = build(&definition);
87 	window->set_id(type);
88 	return window;
89 }
90 
builder_widget(const config & cfg)91 builder_widget::builder_widget(const config& cfg)
92 	: id(cfg["id"])
93 	, linked_group(cfg["linked_group"])
94 	, debug_border_mode(cfg["debug_border_mode"])
95 	, debug_border_color(decode_color(cfg["debug_border_color"]))
96 {
97 }
98 
create_widget_builder(const config & cfg)99 builder_widget_ptr create_widget_builder(const config& cfg)
100 {
101 	config::const_all_children_itors children = cfg.all_children_range();
102 	VALIDATE(children.size() == 1, "Grid cell does not have exactly 1 child.");
103 
104 	if(const config& grid = cfg.child("grid")) {
105 		return std::make_shared<builder_grid>(grid);
106 	}
107 
108 	if(const config& instance = cfg.child("instance")) {
109 		return std::make_shared<implementation::builder_instance>(instance);
110 	}
111 
112 	if(const config& pane = cfg.child("pane")) {
113 		return std::make_shared<implementation::builder_pane>(pane);
114 	}
115 
116 	if(const config& viewport = cfg.child("viewport")) {
117 		return std::make_shared<implementation::builder_viewport>(viewport);
118 	}
119 
120 	for(const auto& item : widget_builder_lookup()) {
121 		if(item.first == "window" || item.first == "tooltip") {
122 			continue;
123 		}
124 
125 		if(const config& c = cfg.child(item.first)) {
126 			return item.second(c);
127 		}
128 	}
129 
130 	// FAIL() doesn't return
131 	//
132 	// To fix this: add your new widget to source-lists/libwesnoth_widgets and rebuild.
133 
134 	FAIL("Unknown widget type " + cfg.ordered_begin()->key);
135 }
136 
build_single_widget_instance_helper(const std::string & type,const config & cfg)137 widget* build_single_widget_instance_helper(const std::string& type, const config& cfg)
138 {
139 	const auto& iter = widget_builder_lookup().find(type);
140 	VALIDATE(iter != widget_builder_lookup().end(), "Invalid widget type '" + type + "'");
141 
142 	widget_builder_func_t& builder = iter->second;
143 	return builder(cfg)->build();
144 }
145 
146 /*WIKI
147  * @page = GUIToolkitWML
148  * @order = 1_window
149  * @begin{parent}{name="gui/"}
150  * = Window definition =
151  * @begin{tag}{name="window"}{min="0"}{max="-1"}
152  *
153  * A window defines how a window looks in the game.
154  *
155  * @begin{table}{config}
156  *     id & string & &               Unique id for this window. $
157  *     description & t_string & &    Unique translatable name for this
158  *                                   window. $
159  *
160  *     resolution & section & &      The definitions of the window in various
161  *                                   resolutions. $
162  * @end{table}
163  * @end{tag}{name="window"}
164  * @end{parent}{name="gui/"}
165  *
166  *
167  */
read(const config & cfg)168 void builder_window::read(const config& cfg)
169 {
170 	VALIDATE(!id_.empty(), missing_mandatory_wml_key("window", "id"));
171 	VALIDATE(!description_.empty(), missing_mandatory_wml_key("window", "description"));
172 
173 	DBG_GUI_P << "Window builder: reading data for window " << id_ << ".\n";
174 
175 	config::const_child_itors cfgs = cfg.child_range("resolution");
176 	VALIDATE(!cfgs.empty(), _("No resolution defined."));
177 
178 	for(const auto& i : cfgs) {
179 		resolutions.emplace_back(i);
180 	}
181 }
182 
183 /*WIKI
184  * @page = GUIToolkitWML
185  * @order = 1_window
186  * @begin{parent}{name=gui/window/}
187  * == Resolution ==
188  * @begin{tag}{name="resolution"}{min="0"}{max="-1"}
189  * @begin{table}{config}
190  * window_width & unsigned & 0 &   Width of the application window. $
191  * window_height & unsigned & 0 &  Height of the application window. $
192  *
193  *
194  * automatic_placement & bool & true &
195  *     Automatically calculate the best size for the window and place it. If
196  *     automatically placed ''vertical_placement'' and ''horizontal_placement''
197  *     can be used to modify the final placement. If not automatically placed
198  *     the ''width'' and ''height'' are mandatory. $
199  *
200  *
201  * x & f_unsigned & 0 &            X coordinate of the window to show. $
202  * y & f_unsigned & 0 &            Y coordinate of the window to show. $
203  * width & f_unsigned & 0 &        Width of the window to show. $
204  * height & f_unsigned & 0 &       Height of the window to show. $
205  *
206  * reevaluate_best_size & f_bool & false &
207  *     The foo $
208  *
209  * functions & function & "" &
210  *     The function definitions s available for the formula fields in window. $
211  *
212  * vertical_placement & v_align & "" &
213  *     The vertical placement of the window. $
214  *
215  * horizontal_placement & h_align & "" &
216  *     The horizontal placement of the window. $
217  *
218  *
219  * maximum_width & unsigned & 0 &
220  *     The maximum width of the window (only used for automatic placement). $
221  *
222  * maximum_height & unsigned & 0 &
223  *     The maximum height of the window (only used for automatic placement). $
224  *
225  *
226  * click_dismiss & bool & false &
227  *     Does the window need click dismiss behavior? Click dismiss behavior
228  *     means that any mouse click will close the dialog. Note certain widgets
229  *     will automatically disable this behavior since they need to process the
230  *     clicks as well, for example buttons do need a click and a misclick on
231  *     button shouldn't close the dialog. NOTE with some widgets this behavior
232  *     depends on their contents (like scrolling labels) so the behavior might
233  *     get changed depending on the data in the dialog. NOTE the default
234  *     behavior might be changed since it will be disabled when can't be used
235  *     due to widgets which use the mouse, including buttons, so it might be
236  *     wise to set the behavior explicitly when not wanted and no mouse using
237  *     widgets are available. This means enter, escape or an external source
238  *     needs to be used to close the dialog (which is valid). $
239  *
240  *
241  * definition & string & "default" &
242  *     Definition of the window which we want to show. $
243  *
244  *
245  * linked_group & sections & [] &  A group of linked widget sections. $
246  *
247  *
248  * tooltip & section & &
249  *     Information regarding the tooltip for this window. $
250  *
251  * helptip & section & &
252  *     Information regarding the helptip for this window. $
253  *
254  *
255  * grid & grid & &                 The grid with the widgets to show. $
256  * @end{table}
257  * @begin{tag}{name="linked_group"}{min=0}{max=-1}
258  * A linked_group section has the following fields:
259  * @begin{table}{config}
260  *     id & string & &               The unique id of the group (unique in this
261  *                                   window). $
262  *     fixed_width & bool & false &  Should widget in this group have the same
263  *                                   width. $
264  *     fixed_height & bool & false & Should widget in this group have the same
265  *                                   height. $
266  * @end{table}
267  * @end{tag}{name="linked_group"}
268  * A linked group needs to have at least one size fixed.
269  * @begin{tag}{name="tooltip"}{min=0}{max=1}
270  * A tooltip and helptip section have the following field:
271  * @begin{table}{config}
272  *     id & string & &               The id of the tip to show.
273  * Note more fields will probably be added later on.
274  * @end{table}{config}
275  * @end{tag}{name=tooltip}
276  * @begin{tag}{name="foreground"}{min=0}{max=1}
277  * @end{tag}{name="foreground"}
278  * @begin{tag}{name="background"}{min=0}{max=1}
279  * @end{tag}{name="background"}
280  * @end{tag}{name="resolution"}
281  * @end{parent}{name=gui/window/}
282  * @begin{parent}{name=gui/window/resolution/}
283  * @begin{tag}{name="helptip"}{min=0}{max=1}{super="gui/window/resolution/tooltip"}
284  * @end{tag}{name="helptip"}
285  * @end{parent}{name=gui/window/resolution/}
286  */
window_resolution(const config & cfg)287 builder_window::window_resolution::window_resolution(const config& cfg)
288 	: window_width(cfg["window_width"])
289 	, window_height(cfg["window_height"])
290 	, automatic_placement(cfg["automatic_placement"].to_bool(true))
291 	, x(cfg["x"])
292 	, y(cfg["y"])
293 	, width(cfg["width"])
294 	, height(cfg["height"])
295 	, reevaluate_best_size(cfg["reevaluate_best_size"])
296 	, functions()
297 	, vertical_placement(implementation::get_v_align(cfg["vertical_placement"]))
298 	, horizontal_placement(implementation::get_h_align(cfg["horizontal_placement"]))
299 	, maximum_width(cfg["maximum_width"])
300 	, maximum_height(cfg["maximum_height"])
301 	, click_dismiss(cfg["click_dismiss"].to_bool())
302 	, definition(cfg["definition"])
303 	, linked_groups()
304 	, tooltip(cfg.child_or_empty("tooltip"), "tooltip")
305 	, helptip(cfg.child_or_empty("helptip"), "helptip")
306 	, grid(nullptr)
307 {
308 	if(!cfg["functions"].empty()) {
309 		wfl::formula(cfg["functions"], &functions).evaluate();
310 	}
311 
312 	const config& c = cfg.child("grid");
313 
314 	VALIDATE(c, _("No grid defined."));
315 
316 	grid = std::make_shared<builder_grid>(builder_grid(c));
317 
318 	if(!automatic_placement) {
319 		VALIDATE(width.has_formula() || width(), missing_mandatory_wml_key("resolution", "width"));
320 		VALIDATE(height.has_formula() || height(), missing_mandatory_wml_key("resolution", "height"));
321 	}
322 
323 	DBG_GUI_P << "Window builder: parsing resolution " << window_width << ',' << window_height << '\n';
324 
325 	if(definition.empty()) {
326 		definition = "default";
327 	}
328 
329 	linked_groups = parse_linked_group_definitions(cfg);
330 }
331 
tooltip_info(const config & cfg,const std::string & tagname)332 builder_window::window_resolution::tooltip_info::tooltip_info(const config& cfg, const std::string& tagname)
333 	: id(cfg["id"])
334 {
335 	VALIDATE(!id.empty(), missing_mandatory_wml_key("[window][resolution][" + tagname + "]", "id"));
336 }
337 
338 /*WIKI
339  * @page = GUIToolkitWML
340  * @order = 2_cell
341  * @begin{parent}{name="gui/window/resolution/"}
342  * = Cell =
343  * @begin{tag}{name="grid"}{min="1"}{max="1"}
344  * @begin{table}{config}
345  *     id & string & "" &      A grid is a widget and can have an id. This isn't
346  *                                      used that often, but is allowed. $
347  *     linked_group & string & 0 &       $
348  * @end{table}
349  *
350  * Every grid cell has some cell configuration values and one widget in the grid
351  * cell. Here we describe the what is available more information about the usage
352  * can be found here [[GUILayout]].
353  *
354  * == Row values ==
355  * @begin{tag}{name="row"}{min="0"}{max="-1"}
356  * For every row the following variables are available:
357  *
358  * @begin{table}{config}
359  *     grow_factor & unsigned & 0 &      The grow factor for a row. $
360  * @end{table}
361  *
362  * == Cell values ==
363  * @begin{tag}{name="column"}{min="0"}{max="-1"}
364  * @allow{link}{name="gui/window/resolution/grid"}
365  * For every column the following variables are available:
366  * @begin{table}{config}
367  *     grow_factor & unsigned & 0 &    The grow factor for a column, this
368  *                                     value is only read for the first row. $
369  *
370  *     border_size & unsigned & 0 &    The border size for this grid cell. $
371  *     border & border & "" &          Where to place the border in this grid
372  *                                     cell. $
373  *
374  *     vertical_alignment & v_align & "" &
375  *                                     The vertical alignment of the widget in
376  *                                     the grid cell. (This value is ignored if
377  *                                     vertical_grow is true.) $
378  *     horizontal_alignment & h_align & "" &
379  *                                     The horizontal alignment of the widget in
380  *                                     the grid cell.(This value is ignored if
381  *                                     horizontal_grow is true.) $
382  *
383  *     vertical_grow & bool & false &    Does the widget grow in vertical
384  *                                     direction when the grid cell grows in the
385  *                                     vertical direction. This is used if the
386  *                                     grid cell is wider as the best width for
387  *                                     the widget. $
388  *     horizontal_grow & bool & false &  Does the widget grow in horizontal
389  *                                     direction when the grid cell grows in the
390  *                                     horizontal direction. This is used if the
391  *                                     grid cell is higher as the best width for
392  *                                     the widget. $
393  * @end{table}
394  * @end{tag}{name="column"}
395  * @end{tag}{name="row"}
396  * @end{tag}{name="grid"}
397  * @end{parent}{name="gui/window/resolution/"}
398  *
399  */
builder_grid(const config & cfg)400 builder_grid::builder_grid(const config& cfg)
401 	: builder_widget(cfg)
402 	, rows(0)
403 	, cols(0)
404 	, row_grow_factor()
405 	, col_grow_factor()
406 	, flags()
407 	, border_size()
408 	, widgets()
409 {
410 	log_scope2(log_gui_parse, "Window builder: parsing a grid");
411 
412 	for(const auto& row : cfg.child_range("row")) {
413 		unsigned col = 0;
414 
415 		row_grow_factor.push_back(row["grow_factor"]);
416 
417 		for(const auto& c : row.child_range("column")) {
418 			flags.push_back(implementation::read_flags(c));
419 			border_size.push_back(c["border_size"]);
420 			if(rows == 0) {
421 				col_grow_factor.push_back(c["grow_factor"]);
422 			}
423 
424 			widgets.push_back(create_widget_builder(c));
425 
426 			++col;
427 		}
428 
429 		++rows;
430 		if(rows == 1) {
431 			cols = col;
432 		} else {
433 			VALIDATE(col, _("A row must have a column."));
434 			VALIDATE(col == cols, _("Number of columns differ."));
435 		}
436 	}
437 
438 	DBG_GUI_P << "Window builder: grid has " << rows << " rows and " << cols << " columns.\n";
439 }
440 
build() const441 grid* builder_grid::build() const
442 {
443 	return build(new grid());
444 }
445 
build(const replacements_map & replacements) const446 widget* builder_grid::build(const replacements_map& replacements) const
447 {
448 	grid* result = new grid();
449 	build(*result, replacements);
450 	return result;
451 }
452 
build(grid * grid) const453 grid* builder_grid::build(grid* grid) const
454 {
455 	grid->set_id(id);
456 	grid->set_linked_group(linked_group);
457 	grid->set_rows_cols(rows, cols);
458 
459 	log_scope2(log_gui_general, "Window builder: building grid");
460 
461 	DBG_GUI_G << "Window builder: grid '" << id << "' has " << rows << " rows and " << cols << " columns.\n";
462 
463 	for(unsigned x = 0; x < rows; ++x) {
464 		grid->set_row_grow_factor(x, row_grow_factor[x]);
465 
466 		for(unsigned y = 0; y < cols; ++y) {
467 			if(x == 0) {
468 				grid->set_column_grow_factor(y, col_grow_factor[y]);
469 			}
470 
471 			DBG_GUI_G << "Window builder: adding child at " << x << ',' << y << ".\n";
472 
473 			const unsigned int i = x * cols + y;
474 
475 			widget* widget = widgets[i]->build();
476 			grid->set_child(widget, x, y, flags[i], border_size[i]);
477 		}
478 	}
479 
480 	return grid;
481 }
482 
build(grid & grid,const replacements_map & replacements) const483 void builder_grid::build(grid& grid, const replacements_map& replacements) const
484 {
485 	grid.set_id(id);
486 	grid.set_linked_group(linked_group);
487 	grid.set_rows_cols(rows, cols);
488 
489 	log_scope2(log_gui_general, "Window builder: building grid");
490 
491 	DBG_GUI_G << "Window builder: grid '" << id << "' has " << rows << " rows and " << cols << " columns.\n";
492 
493 	for(unsigned x = 0; x < rows; ++x) {
494 		grid.set_row_grow_factor(x, row_grow_factor[x]);
495 
496 		for(unsigned y = 0; y < cols; ++y) {
497 			if(x == 0) {
498 				grid.set_column_grow_factor(y, col_grow_factor[y]);
499 			}
500 
501 			DBG_GUI_G << "Window builder: adding child at " << x << ',' << y << ".\n";
502 
503 			const unsigned int i = x * cols + y;
504 			grid.set_child(widgets[i]->build(replacements), x, y, flags[i], border_size[i]);
505 		}
506 	}
507 }
508 
509 } // namespace gui2
510 
511 /*WIKI
512  * @page = GUIToolkitWML
513  * @order = ZZZZZZ_footer
514  *
515  * [[Category: WML Reference]]
516  * [[Category: GUI WML Reference]]
517  */
518 
519 /*WIKI
520  * @page = GUIWidgetInstanceWML
521  * @order = ZZZZZZ_footer
522  *
523  * [[Category: WML Reference]]
524  * [[Category: GUI WML Reference]]
525  *
526  */
527