wesnoth/src/gui/auxiliary/window_builder.cpp
Mark de Wever 75a0839402 Added new experimental list box implementation.
The code is very experimental and incomplete and therefore ifdeffed with
GUI2_EXPERIMENTAL_LISTBOX.
2010-05-02 12:17:38 +00:00

539 lines
17 KiB
C++

/* $Id$ */
/*
Copyright (C) 2008 - 2010 by Mark de Wever <koraq@xs4all.nl>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2
or at your option any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "gui/auxiliary/window_builder_private.hpp"
#include "asserts.hpp"
#include "foreach.hpp"
#include "gettext.hpp"
#include "gui/auxiliary/log.hpp"
#include "gui/auxiliary/window_builder/helper.hpp"
#if 1 // See the #if in create_builder_widget.
#include "gui/auxiliary/window_builder/scrollbar_panel.hpp"
#include "gui/auxiliary/window_builder/horizontal_scrollbar.hpp"
#include "gui/auxiliary/window_builder/repeating_button.hpp"
#include "gui/auxiliary/window_builder/stacked_widget.hpp"
#include "gui/auxiliary/window_builder/vertical_scrollbar.hpp"
#include "gui/auxiliary/window_builder/label.hpp"
#include "gui/auxiliary/window_builder/image.hpp"
#include "gui/auxiliary/window_builder/toggle_button.hpp"
#include "gui/auxiliary/window_builder/slider.hpp"
#include "gui/auxiliary/window_builder/scroll_label.hpp"
#include "gui/auxiliary/window_builder/minimap.hpp"
#endif
#include "gui/widgets/settings.hpp"
#include "gui/widgets/window.hpp"
#include "formula_string_utils.hpp"
#include <boost/bind.hpp>
namespace gui2 {
namespace {
static std::map<std::string, boost::function<tbuilder_widget_ptr(config)> >&
builder_widget_lookup()
{
static std::map<std::string, boost::function<tbuilder_widget_ptr(config)> >
result;
return result;
}
tbuilder_widget_ptr create_builder_widget(const config& cfg)
{
config::all_children_itors children = cfg.all_children_range();
size_t nb_children = std::distance(children.first, children.second);
if (nb_children != 1) {
ERR_GUI_P << "Grid cell has " << nb_children
<< " children instead of 1, aborting. Config :\n"
<< cfg;
assert(false);
}
typedef
std::pair<
std::string
, boost::function<tbuilder_widget_ptr(config)> >
thack;
foreach(const thack& item, builder_widget_lookup()) {
if(item.first == "window" || item.first == "tooltip") {
continue;
}
if(const config &c = cfg.child(item.first)) {
return item.second(c);
}
}
if(const config &c = cfg.child("grid")) {
return new tbuilder_grid(c);
}
/*
* This is rather odd, when commented out the classes no longer seem to be in
* the executable, no real idea why, except maybe of an overzealous optimizer
* while linking. It seems that all these classes aren't explicitly
* instantiated but only implicitly. Also when looking at the symbols in
* libwesnoth-game.a the repeating button is there regardless of this #if but
* in the final binary only if the #if is enabled.
*
* If this code is executed, which it will cause an assertion failure.
*/
#if 1
#define TRY(name) \
do { \
if(const config &c = cfg.child(#name)) { \
tbuilder_widget_ptr p = new implementation::tbuilder_##name(c);\
assert(false); \
} \
} while (0)
TRY(stacked_widget);
TRY(scrollbar_panel);
TRY(horizontal_scrollbar);
TRY(repeating_button);
TRY(vertical_scrollbar);
TRY(label);
TRY(image);
TRY(toggle_button);
TRY(slider);
TRY(scroll_label);
TRY(minimap);
#undef TRY
#endif
std::cerr << cfg;
ERROR_LOG(false);
}
} // namespace
/*WIKI
* @page = GUIWidgetInstanceWML
* @order = 1
*
* THIS PAGE IS AUTOMATICALLY GENERATED, DO NOT MODIFY DIRECTLY !!!
*
* = Widget instance =
*
* Inside a grid (which is inside all container widgets) a widget is
* instantiated. With this instantiation some more variables of a widget can
* be tuned. This page will describe what can be tuned.
*
*/
twindow* build(CVideo& video, const std::string& type)
{
std::vector<twindow_builder::tresolution>::const_iterator
definition = get_window_builder(type);
// We set the values from the definition since we can only determine the
// best size (if needed) after all widgets have been placed.
twindow* window = new twindow(video
, definition->x
, definition->y
, definition->width
, definition->height
, definition->automatic_placement
, definition->horizontal_placement
, definition->vertical_placement
, definition->maximum_width
, definition->maximum_height
, definition->definition);
assert(window);
window->set_id(type);
foreach(const twindow_builder::tresolution::tlinked_group& lg,
definition->linked_groups) {
if(window->has_linked_size_group(lg.id)) {
utils::string_map symbols;
symbols["id"] = lg.id;
t_string msg = vgettext(
"Linked '$id' group has multiple definitions."
, symbols);
VALIDATE(false, msg);
}
window->init_linked_size_group(
lg.id, lg.fixed_width, lg.fixed_height);
}
window->set_click_dismiss(definition->click_dismiss);
boost::intrusive_ptr<const twindow_definition::tresolution> conf =
boost::dynamic_pointer_cast<
const twindow_definition::tresolution>(window->config());
assert(conf);
if(conf->grid) {
window->init_grid(conf->grid);
window->finalize(definition->grid);
} else {
window->init_grid(definition->grid);
}
window->add_to_keyboard_chain(window);
return window;
}
void register_builder_widget(const std::string& id
, boost::function<tbuilder_widget_ptr(config)> functor)
{
builder_widget_lookup().insert(std::make_pair(id, functor));
}
const std::string& twindow_builder::read(const config& cfg)
{
/*WIKI
* @page = GUIToolkitWML
* @order = 1_window
*
* = Window definition =
*
* A window defines how a window looks in the game.
*
* @start_table = config
* id (string) Unique id for this window.
* description (t_string) Unique translatable name for this window.
*
* resolution (section) The definitions of the window in various
* resolutions.
* @end_table
*
*/
id_ = cfg["id"];
description_ = cfg["description"];
VALIDATE(!id_.empty(), missing_mandatory_wml_key("window", "id"));
VALIDATE(!description_.empty(), missing_mandatory_wml_key("window", "description"));
DBG_GUI_P << "Window builder: reading data for window " << id_ << ".\n";
config::const_child_itors cfgs = cfg.child_range("resolution");
VALIDATE(cfgs.first != cfgs.second, _("No resolution defined."));
foreach (const config &i, cfgs) {
resolutions.push_back(tresolution(i));
}
return id_;
}
twindow_builder::tresolution::tresolution(const config& cfg) :
window_width(lexical_cast_default<unsigned>(cfg["window_width"])),
window_height(lexical_cast_default<unsigned>(cfg["window_height"])),
automatic_placement(utils::string_bool(cfg["automatic_placement"], true)),
x(cfg["x"]),
y(cfg["y"]),
width(cfg["width"]),
height(cfg["height"]),
vertical_placement(
implementation::get_v_align(cfg["vertical_placement"])),
horizontal_placement(
implementation::get_h_align(cfg["horizontal_placement"])),
maximum_width(lexical_cast_default<unsigned>(cfg["maximum_width"])),
maximum_height(lexical_cast_default<unsigned>(cfg["maximum_height"])),
click_dismiss(utils::string_bool(cfg["click_dismiss"])),
definition(cfg["definition"]),
linked_groups(),
grid(0)
{
/*WIKI
* @page = GUIToolkitWML
* @order = 1_window
*
* == Resolution ==
*
* @start_table = config
* window_width (unsigned = 0) Width of the application window.
* window_height (unsigned = 0) Height of the application window.
*
* automatic_placement (bool = true)
* Automatically calculate the best size for
* the window and place it. If automatically
* placed ''vertical_placement'' and
* ''horizontal_placement'' can be used to
* modify the final placement. If not
* automatically placed the ''width'' and
* ''height'' are mandatory.
*
* x (f_unsigned = 0) X coordinate of the window to show.
* y (f_unsigned = 0) Y coordinate of the window to show.
* width (f_unsigned = 0) Width of the window to show.
* height (f_unsigned = 0) Height of the window to show.
*
* vertical_placement (v_align = "")
* The vertical placement of the window.
* horizontal_placement (h_align = "")
* The horizontal placement of the window.
*
* maximum_width (unsigned = 0) The maximum width of the window (only
* used for automatic placement).
* maximum_height (unsigned = 0) The maximum height of the window (only
* used for automatic placement).
*
* click_dismiss (bool = false) Does the window need click dismiss
* behaviour? Click dismiss behaviour means
* that any mouse click will close the
* dialog. Note certain widgets will
* automatically disable this behaviour since
* they need to process the clicks as well,
* for example buttons do need a click and a
* misclick on button shouldn't close the
* dialog. NOTE with some widgets this
* behaviour depends on their contents (like
* scrolling labels) so the behaviour might
* get changed depending on the data in the
* dialog. NOTE the default behaviour might
* be changed since it will be disabled when
* can't be used due to widgets which use the
* mouse, including buttons, so it might be
* wise to set the behaviour explicitly when
* not wanted and no mouse using widgets are
* available. This means enter, escape or an
* external source needs to be used to close
* the dialog (which is valid).
*
* definition (string = "default")
* Definition of the window which we want to
* show.
*
* linked_group (sections = []) A group of linked widget sections.
*
* grid (grid) The grid with the widgets to show.
* @end_table
*
* A linked_group section has the following fields:
* @start_table = config
* id (string) The unique id of the group (unique in this
* window).
* fixed_width (bool = false) Should widget in this group have the same
* width.
* fixed_height (bool = false) Should widget in this group have the same
* height.
* @end_table
*
* A linked group needs to have at least one size fixed.
*/
const config &c = cfg.child("grid");
VALIDATE(c, _("No grid defined."));
grid = new tbuilder_grid(c);
if(!automatic_placement) {
VALIDATE(width.has_formula() || width(),
missing_mandatory_wml_key("resolution", "width"));
VALIDATE(height.has_formula() || height(),
missing_mandatory_wml_key("resolution", "height"));
}
DBG_GUI_P << "Window builder: parsing resolution "
<< window_width << ',' << window_height << '\n';
if(definition.empty()) {
definition = "default";
}
foreach (const config &lg, cfg.child_range("linked_group")) {
tlinked_group linked_group;
linked_group.id = lg["id"];
linked_group.fixed_width = utils::string_bool(lg["fixed_width"]);
linked_group.fixed_height = utils::string_bool(lg["fixed_height"]);
VALIDATE(!linked_group.id.empty()
, missing_mandatory_wml_key("linked_group", "id"));
if(!(linked_group.fixed_width || linked_group.fixed_height)) {
utils::string_map symbols;
symbols["id"] = linked_group.id;
t_string msg = vgettext(
"Linked '$id' group needs a 'fixed_width' or "
"'fixed_height' key."
, symbols);
VALIDATE(false, msg);
}
linked_groups.push_back(linked_group);
}
}
tbuilder_grid::tbuilder_grid(const config& cfg) :
tbuilder_widget(cfg),
id(cfg["id"]),
linked_group(cfg["linked_group"]),
rows(0),
cols(0),
row_grow_factor(),
col_grow_factor(),
flags(),
border_size(),
widgets()
{
/*WIKI
* @page = GUIToolkitWML
* @order = 2_cell
*
* = Cell =
*
* Every grid cell has some cell configuration values and one widget in the grid
* cell. Here we describe the what is available more information about the usage
* can be found here [[GUILayout]].
*
* == Row values ==
*
* For every row the following variables are available:
*
* @start_table = config
* grow_factor (unsigned = 0) The grow factor for a row.
* @end_table
*
* == Cell values ==
*
* For every column the following variables are available:
* @start_table = config
* grow_factor (unsigned = 0) The grow factor for a column, this value
* is only read for the first row.
*
* border_size (unsigned = 0) The border size for this grid cell.
* border (border = "") Where to place the border in this grid
* cell.
*
* vertical_alignment (v_align = "")
* The vertical alignment of the widget in
* the grid cell. (This value is ignored if
* vertical_grow is true.)
* horizontal_alignment (h_align = "")
* The horizontal alignment of the widget in
* the grid cell.(This value is ignored if
* horizontal_grow is true.)
*
* vertical_grow (bool = false) Does the widget grow in vertical
* direction when the grid cell grows in the
* vertical direction. This is used if the
* grid cell is wider as the best width for
* the widget.
* horizontal_grow (bool = false) Does the widget grow in horizontal
* direction when the grid cell grows in the
* horizontal direction. This is used if the
* grid cell is higher as the best width for
* the widget.
* @end_table
*
*/
log_scope2(log_gui_parse, "Window builder: parsing a grid");
foreach (const config &row, cfg.child_range("row"))
{
unsigned col = 0;
row_grow_factor.push_back(lexical_cast_default<unsigned>(row["grow_factor"]));
foreach (const config &c, row.child_range("column"))
{
flags.push_back(implementation::read_flags(c));
border_size.push_back(lexical_cast_default<unsigned>(c["border_size"]));
if(rows == 0) {
col_grow_factor.push_back(lexical_cast_default<unsigned>(c["grow_factor"]));
}
widgets.push_back(create_builder_widget(c));
++col;
}
++rows;
if (rows == 1) {
cols = col;
} else {
VALIDATE(col, _("A row must have a column."));
VALIDATE(col == cols, _("Number of columns differ."));
}
}
DBG_GUI_P << "Window builder: grid has "
<< rows << " rows and " << cols << " columns.\n";
}
tbuilder_gridcell::tbuilder_gridcell(const config& cfg) :
tbuilder_widget(cfg),
flags(implementation::read_flags(cfg)),
border_size(lexical_cast_default<unsigned>((cfg)["border_size"])),
widget(create_builder_widget(cfg))
{
}
twidget* tbuilder_grid::build() const
{
return build(new tgrid());
}
twidget* tbuilder_grid::build (tgrid* grid) const
{
grid->set_id(id);
grid->set_linked_group(linked_group);
grid->set_rows_cols(rows, cols);
log_scope2(log_gui_general, "Window builder: building grid");
DBG_GUI_G << "Window builder: grid '" << id
<< "' has " << rows << " rows and "
<< cols << " columns.\n";
for(unsigned x = 0; x < rows; ++x) {
grid->set_row_grow_factor(x, row_grow_factor[x]);
for(unsigned y = 0; y < cols; ++y) {
if(x == 0) {
grid->set_column_grow_factor(y, col_grow_factor[y]);
}
DBG_GUI_G << "Window builder: adding child at " << x << ',' << y << ".\n";
twidget* widget = widgets[x * cols + y]->build();
grid->set_child(widget, x, y, flags[x * cols + y], border_size[x * cols + y]);
}
}
return grid;
}
} // namespace gui2
/*WIKI
* @page = GUIToolkitWML
* @order = ZZZZZZ_footer
*
* [[Category: WML Reference]]
* [[Category: GUI WML Reference]]
* [[Category: Generated]]
*
*/
/*WIKI
* @page = GUIWidgetInstanceWML
* @order = ZZZZZZ_footer
*
* [[Category: WML Reference]]
* [[Category: GUI WML Reference]]
* [[Category: Generated]]
*
*/