The Battle for Wesnoth  1.19.13+dev
gui_definition.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
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 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "config.hpp"
21 #include "formatter.hpp"
22 #include "gui/core/log.hpp"
24 #include "gui/widgets/settings.hpp"
25 #include "serialization/chrono.hpp"
26 #include "wml_exception.hpp"
27 
28 namespace gui2
29 {
30 using namespace std::chrono_literals;
31 
35 
37  : widget_types()
38  , window_types()
39  , id_(cfg["id"])
40  , description_(cfg["description"].t_str())
41  , settings_(cfg.mandatory_child("settings"))
42  , tips_(tip_of_the_day::load(cfg))
43 {
44  VALIDATE(!id_.empty(), missing_mandatory_wml_key("gui", "id"));
45  VALIDATE(!description_.empty(), missing_mandatory_wml_key("gui", "description"));
46 
47  DBG_GUI_P << "Parsing gui " << id_;
48 
49  //
50  // Widget parsing
51  //
52 
53  /** Parse widget definitions of each registered type. */
54  for(const auto& [type_id, widget_parser] : registered_widget_types()) {
55  auto& def_map = widget_types[type_id];
56 
57  const std::string key = widget_parser.key
58  ? widget_parser.key
59  : type_id + "_definition";
60 
61  bool found_default_def = false;
62 
63  for(const config& definition : cfg.child_range(key)) {
64  // Run the static parser to get a definition ptr.
65  styled_widget_definition_ptr def_ptr = widget_parser.parser(definition);
66 
67  const std::string& def_id = def_ptr->id;
68  auto [_, success] = def_map.emplace(def_id, std::move(def_ptr));
69 
70  if(!success) {
71  ERR_GUI_P << "Skipping duplicate definition '" << def_id << "' for '" << type_id << "'";
72  continue;
73  }
74 
75  if(def_id == "default") {
76  found_default_def = true;
77  }
78  }
79 
80  // Only the default GUI needs to ensure each widget has a default definition.
81  // Non-default ones can just fall back to the default definition in the default GUI.
82  if(id_ == "default") {
83  VALIDATE(found_default_def, "No default definition found for widget '" + type_id + "'");
84  }
85  }
86 
87  //
88  // Window parsing
89  //
90 
91  /** Parse each window. */
92  for(auto& w : cfg.child_range("window")) {
93  window_types.emplace(w["id"], builder_window(w));
94  }
95 
96  if(id_ == "default") {
97  // The default gui needs to define all window types since we're the
98  // fallback in case another gui doesn't define the window type.
99  for(const auto& window_type : registered_window_types()) {
100  const std::string error_msg(
101  "Window not defined in WML: '" + window_type + "'."
102  "Perhaps a mismatch between data and source versions. Try --data-dir <trunk-dir>");
103 
104  VALIDATE(window_types.find(window_type) != window_types.end(), error_msg);
105  }
106  }
107 }
108 
110  : popup_show_delay(chrono::parse_duration(cfg["popup_show_delay"], 0ms))
111  , popup_show_time(chrono::parse_duration(cfg["popup_show_time"], 0ms))
112  , help_show_time(chrono::parse_duration(cfg["help_show_time"], 0ms))
113  , double_click_time(chrono::parse_duration(cfg["double_click_time"], 0ms))
114  , repeat_button_repeat_time(chrono::parse_duration(cfg["repeat_button_repeat_time"], 0ms))
115  , sound_button_click(cfg["sound_button_click"])
116  , sound_toggle_button_click(cfg["sound_toggle_button_click"])
117  , sound_toggle_panel_click(cfg["sound_toggle_panel_click"])
118  , sound_slider_adjust(cfg["sound_slider_adjust"])
119  , has_helptip_message(cfg["has_helptip_message"].t_str())
120 {
122  missing_mandatory_wml_key("settings", "double_click_time"));
123 
125  missing_mandatory_wml_key("settings", "has_helptip_message"));
126 }
127 
129 {
141 
142  // Let SDL know to use the configured time value
143  auto hint_value = std::to_string(settings::double_click_time.count());
144  SDL_SetHint(SDL_HINT_MOUSE_DOUBLE_CLICK_TIME, hint_value.data());
145 }
146 
147 namespace
148 {
149 template<typename TList, typename TConv>
150 const typename TList::value_type& get_best_resolution(const TList& list, const TConv& get_size)
151 {
152  using resolution_t = const typename TList::value_type;
153 
154  resolution_t* best_resolution = nullptr;
155  int best_resolution_score = std::numeric_limits<int>::min();
156 
157  const int screen_w = settings::screen_width;
158  const int screen_h = settings::screen_height;
159 
160  for(const auto& res : list) {
161  point size = get_size(res);
162 
163  int w = size.x ? size.x : 1;
164  int h = size.y ? size.y : 1;
165  int score = 0;
166 
167  if(w <= screen_w && h <= screen_h) {
168  score = w * h;
169  } else {
170  // Negative score, only used in case none of the given resolution fits on the screen
171  // (workaround for a bug where the windows size can become < 800x600).
172  score = std::min(screen_w - w, 0) + std::min(screen_h - h, 0);
173  }
174 
175  if(score >= best_resolution_score) {
176  best_resolution = &res;
177  best_resolution_score = score;
178  }
179  }
180 
181  assert(best_resolution != nullptr);
182  return *best_resolution;
183 }
184 
185 } // namespace
186 
187 resolution_definition_ptr get_control(const std::string& control_type, const std::string& definition)
188 {
189  const auto& current_types = current_gui->second.widget_types;
190  const auto& default_types = default_gui->second.widget_types;
191 
192  const auto find_definition =
193  [&](const auto& widget_types) -> utils::optional<gui_definition::widget_definition_map_t::const_iterator>
194  {
195  // Get all possible definitions for the given widget type.
196  const auto widget_definitions = widget_types.find(control_type);
197 
198  // We don't have a fallback here since all types should be valid in all themes.
199  VALIDATE(widget_definitions != widget_types.end(),
200  formatter() << "Control: type '" << control_type << "' is unknown.");
201 
202  const auto& options = widget_definitions->second;
203 
204  // Out of all definitions for that type, find the requested one.
205  if(auto control = options.find(definition); control != options.end()) {
206  return control;
207  } else {
208  return utils::nullopt;
209  }
210  };
211 
212  auto control = find_definition(current_types);
213 
214  // Definition not found in the current theme, try the default theme.
215  if(!control && current_gui != default_gui) {
216  control = find_definition(default_types);
217  }
218 
219  // Still no match. Try the default definition.
220  if(!control && definition != "default") {
221  LOG_GUI_G << "Control: type '" << control_type << "' definition '" << definition
222  << "' not found, falling back to 'default'.";
223  return get_control(control_type, "default");
224  }
225 
226  VALIDATE(control,
227  formatter() << "Control: definition '" << definition << "' not found for styled_widget " << control_type);
228 
229  // Finally, resolve the appropriate resolution
230  const auto& resolutions = (*control)->second->resolutions;
231 
232  VALIDATE(!resolutions.empty(),
233  formatter() << "Control: type '" << control_type << "' definition '" << definition << "' has no resolutions.");
234 
235  return get_best_resolution(resolutions, [&](const resolution_definition_ptr& ptr) {
236  return point(
237  static_cast<int>(ptr->window_width),
238  static_cast<int>(ptr->window_height)
239  );
240  });
241 }
242 
244 {
246 
247  const auto& current_windows = current_gui->second.window_types;
248  const auto& default_windows = default_gui->second.window_types;
249 
250  auto iter = current_windows.find(type);
251 
252  if(iter == current_windows.end()) {
253  // Current GUI is the default one and no window type was found. Throw.
254  if(current_gui == default_gui) {
256  }
257 
258  // Else, try again to find the window, this time in the default GUI.
259  iter = default_windows.find(type);
260 
261  if(iter == default_windows.end()) {
263  }
264  }
265 
266  const auto& resolutions = iter->second.resolutions;
267 
268  VALIDATE(!resolutions.empty(), formatter() << "Window '" << type << "' has no resolutions.\n");
269 
270  return get_best_resolution(resolutions, [&](const builder_window::window_resolution& res) {
271  return point(
272  static_cast<int>(res.window_width),
273  static_cast<int>(res.window_height)
274  );
275  });
276 }
277 
278 bool add_single_widget_definition(const std::string& widget_type, const std::string& definition_id, const config& cfg)
279 {
280  auto& def_map = current_gui->second.widget_types[widget_type];
281  auto parser = registered_widget_types().find(widget_type);
282 
283  if(parser == registered_widget_types().end()) {
284  throw std::invalid_argument("widget '" + widget_type + "' doesn't exist");
285  }
286 
287  auto [_, success] = def_map.emplace(definition_id, parser->second.parser(cfg));
288  return success;
289 }
290 
291 void remove_single_widget_definition(const std::string& widget_type, const std::string& definition_id)
292 {
293  auto& definition_map = current_gui->second.widget_types[widget_type];
294 
295  auto it = definition_map.find(definition_id);
296  if(it != definition_map.end()) {
297  definition_map.erase(it);
298  }
299 }
300 
301 } // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
std::ostringstream wrapper.
Definition: formatter.hpp:40
std::map< std::string, builder_window > window_types
Map of all known windows (the builder class builds a window).
settings_helper settings_
std::map< std::string, widget_definition_map_t > widget_types
Map of each widget type, by id, and a sub-map of each of the type's definitions, also by id.
std::vector< game_tip > tips_
gui_definition(const config &cfg)
Private ctor.
void activate() const
Activates this GUI.
bool empty() const
Definition: tstring.hpp:197
Definitions for the interface to Wesnoth Markup Language (WML).
const config * cfg
static std::string _(const char *str)
Definition: gettext.hpp:97
Define the common log macros for the gui toolkit.
#define LOG_GUI_G
Definition: log.hpp:42
#define ERR_GUI_P
Definition: log.hpp:69
#define DBG_GUI_P
Definition: log.hpp:66
auto parse_duration(const config_attribute_value &val, const Duration &def=Duration{0})
Definition: chrono.hpp:71
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:214
t_string has_helptip_message
Definition: settings.cpp:55
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
std::vector< game_tip > tips
Definition: settings.cpp:57
std::chrono::milliseconds double_click_time
The interval between two clicks to be detected as double click.
Definition: settings.cpp:44
std::string sound_slider_adjust
Definition: settings.cpp:53
std::string sound_toggle_button_click
Definition: settings.cpp:51
std::chrono::milliseconds popup_show_delay
Delay before a popup shows.
Definition: settings.cpp:38
unsigned screen_height
Definition: settings.cpp:28
std::string sound_button_click
Definition: settings.cpp:50
std::chrono::milliseconds popup_show_time
Definition: settings.cpp:39
std::chrono::milliseconds help_show_time
Definition: settings.cpp:40
std::string sound_toggle_panel_click
Definition: settings.cpp:52
void update_screen_size_variables()
Update the size of the screen variables in settings.
Definition: settings.cpp:59
std::chrono::milliseconds repeat_button_repeat_time
How much a repeating button need to be held before the action repeats.
Definition: settings.cpp:48
std::vector< game_tip > load(const config &cfg)
Loads the tips from a config.
Definition: tips.cpp:37
Generic file dialog.
std::shared_ptr< styled_widget_definition > styled_widget_definition_ptr
bool add_single_widget_definition(const std::string &widget_type, const std::string &definition_id, const config &cfg)
Adds a widget definition to the default GUI.
void remove_single_widget_definition(const std::string &widget_type, const std::string &definition_id)
Removes a widget definition from the default GUI.
std::shared_ptr< resolution_definition > resolution_definition_ptr
const builder_window::window_resolution & get_window_builder(const std::string &type)
Returns an reference to the requested builder.
std::set< std::string > & registered_window_types()
Returns the list of registered windows.
gui_theme_map_t guis
Map of all known GUIs.
std::map< std::string, registered_widget_parser > & registered_widget_types()
Returns the list of registered widgets and their parsers.
std::map< std::string, gui_definition > gui_theme_map_t
gui_theme_map_t::iterator current_gui
Iterator pointing to the current GUI.
gui_theme_map_t::iterator default_gui
Iterator pointing to the default GUI.
resolution_definition_ptr get_control(const std::string &control_type, const std::string &definition)
Returns the appropriate config data for a widget instance fom the active GUI definition.
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:799
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
int w
Definition: pathfind.cpp:188
This file contains the settings handling of the widget library.
std::chrono::milliseconds popup_show_time
std::chrono::milliseconds double_click_time
std::chrono::milliseconds popup_show_delay
std::chrono::milliseconds repeat_button_repeat_time
std::chrono::milliseconds help_show_time
Helper struct to signal that get_window_builder failed.
Holds a 2D point.
Definition: point.hpp:25
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key (attribute).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define h