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