The Battle for Wesnoth  1.15.12+dev
game_load.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Jörg Hinrichs <joerg.hinrichs@alice-dsl.de>
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 
18 
19 #include "desktop/open.hpp"
20 #include "filesystem.hpp"
21 #include "font/pango/escape.hpp"
22 #include "formatter.hpp"
23 #include "formula/string_utils.hpp"
24 #include "game_classification.hpp"
25 #include "game_config.hpp"
26 #include "gettext.hpp"
27 #include "gui/auxiliary/field.hpp"
28 #include "gui/core/log.hpp"
30 #include "gui/widgets/button.hpp"
31 #include "gui/widgets/image.hpp"
32 #include "gui/widgets/label.hpp"
33 #include "gui/widgets/listbox.hpp"
34 #include "gui/widgets/minimap.hpp"
36 #include "gui/widgets/settings.hpp"
37 #include "gui/widgets/text_box.hpp"
39 #include "gui/widgets/window.hpp"
40 #include "language.hpp"
41 #include "picture.hpp"
42 #include "preferences/game.hpp"
44 #include "utils/general.hpp"
45 #include <functional>
46 #include "game_config_view.hpp"
47 
48 #include <cctype>
49 
50 static lg::log_domain log_gameloaddlg{"gui/dialogs/game_load_dialog"};
51 #define ERR_GAMELOADDLG LOG_STREAM(err, log_gameloaddlg)
52 #define WRN_GAMELOADDLG LOG_STREAM(warn, log_gameloaddlg)
53 #define LOG_GAMELOADDLG LOG_STREAM(info, log_gameloaddlg)
54 #define DBG_GAMELOADDLG LOG_STREAM(debug, log_gameloaddlg)
55 
56 namespace gui2::dialogs
57 {
58 
59 REGISTER_DIALOG(game_load)
60 
61 bool game_load::execute(const game_config_view& cache_config, savegame::load_game_metadata& data)
62 {
63  if(savegame::save_index_class::default_saves_dir()->get_saves_list().empty()) {
64  bool found_files = false;
65  for(const auto& dir : filesystem::find_other_version_saves_dirs()) {
66  if(!found_files) {
67  // this needs to be a shared_ptr because get_saves_list() uses shared_from_this
68  auto index = std::make_shared<savegame::save_index_class>(dir.path);
69  found_files = !index->get_saves_list().empty();
70  }
71  }
72 
73  if(!found_files) {
74  gui2::show_transient_message(_("No Saved Games"), _("There are no saved games to load."));
75  return false;
76  }
77  }
78 
79  return game_load(cache_config, data).show();
80 }
81 
83  : filename_(data.filename)
84  , save_index_manager_(data.manager)
85  , change_difficulty_(register_bool("change_difficulty", true, data.select_difficulty))
86  , show_replay_(register_bool("show_replay", true, data.show_replay))
87  , cancel_orders_(register_bool("cancel_orders", true, data.cancel_orders))
88  , summary_(data.summary)
89  , games_()
90  , cache_config_(cache_config)
91  , last_words_()
92 {
93 }
94 
96 {
97  // Allow deleting saves with the Delete key.
98  connect_signal_pre_key_press(window, std::bind(&game_load::key_press_callback, this, std::placeholders::_5));
99 
100  text_box* filter = find_widget<text_box>(&window, "txtFilter", false, true);
101 
102  filter->set_text_changed_callback(std::bind(&game_load::filter_text_changed, this, std::placeholders::_2));
103 
104  listbox& list = find_widget<listbox>(&window, "savegame_list", false);
105 
107 
108  window.keyboard_capture(filter);
109  window.add_to_keyboard_chain(&list);
110 
111  list.register_sorting_option(0, [this](const int i) { return games_[i].name(); });
112  list.register_sorting_option(1, [this](const int i) { return games_[i].modified(); });
113 
115 
116  connect_signal_mouse_left_click(find_widget<button>(&window, "delete", false),
117  std::bind(&game_load::delete_button_callback, this));
118 
119  connect_signal_mouse_left_click(find_widget<button>(&window, "browse_saves_folder", false),
120  std::bind(&game_load::browse_button_callback, this));
121 
122  menu_button& dir_list = find_widget<menu_button>(&window, "dirList", false);
123 
124  dir_list.set_use_markup(true);
125  set_save_dir_list(dir_list);
126 
128 
130 }
131 
133 {
134  const auto other_dirs = filesystem::find_other_version_saves_dirs();
135  if(other_dirs.empty()) {
137  return;
138  }
139 
140  std::vector<config> options;
141 
142  // The first option in the list is the current version's save dir
143  options.emplace_back("label", _("game_version^Current Version"), "path", "");
144 
145  for(const auto& known_dir : other_dirs) {
146  options.emplace_back(
147  "label", VGETTEXT("game_version^Wesnoth $version", utils::string_map{{"version", known_dir.version}}),
148  "path", known_dir.path
149  );
150  }
151 
152  dir_list.set_values(options);
153 }
154 
156 {
157  listbox& list = find_widget<listbox>(get_window(), "savegame_list", false);
158 
159  list.clear();
160 
161  games_ = save_index_manager_->get_saves_list();
162 
163  for(const auto& game : games_) {
164  std::map<std::string, string_map> data;
166 
167  std::string name = game.name();
168  utils::ellipsis_truncate(name, 40);
169  item["label"] = name;
170  data.emplace("filename", item);
171 
172  item["label"] = game.format_time_summary();
173  data.emplace("date", item);
174 
175  list.add_row(data);
176  }
177 
178  find_widget<button>(get_window(), "delete", false).set_active(!save_index_manager_->read_only());
179 }
180 
182 {
183  filename_ = game.name();
184  summary_ = game.summary();
185 
186  find_widget<minimap>(get_window(), "minimap", false)
187  .set_map_data(summary_["map_data"]);
188 
189  find_widget<label>(get_window(), "lblScenario", false)
190  .set_label(summary_["label"]);
191 
192  listbox& leader_list = find_widget<listbox>(get_window(), "leader_list", false);
193 
194  leader_list.clear();
195 
196  const std::string sprite_scale_mod = (formatter() << "~SCALE_INTO(" << game_config::tile_size << ',' << game_config::tile_size << ')').str();
197 
198  for(const auto& leader : summary_.child_range("leader")) {
199  std::map<std::string, string_map> data;
201 
202  // First, we evaluate whether the leader image as provided exists.
203  // If not, we try getting a binary path-independent path. If that still doesn't
204  // work, we fallback on unknown-unit.png.
205  std::string leader_image = leader["leader_image"].str();
206  if(!::image::exists(leader_image)) {
207  leader_image = filesystem::get_independent_binary_file_path("images", leader_image);
208 
209  // The leader TC modifier isn't appending if the independent image path can't
210  // be resolved during save_index entry creation, so we need to add it here.
211  if(!leader_image.empty()) {
212  leader_image += leader["leader_image_tc_modifier"].str();
213  }
214  }
215 
216  if(leader_image.empty()) {
217  leader_image = "units/unknown-unit.png" + leader["leader_image_tc_modifier"].str();
218  } else {
219  // Scale down any sprites larger than 72x72
220  leader_image += sprite_scale_mod;
221  }
222 
223  item["label"] = leader_image;
224  data.emplace("imgLeader", item);
225 
226  item["label"] = leader["leader_name"];
227  data.emplace("leader_name", item);
228 
229  item["label"] = leader["gold"];
230  data.emplace("leader_gold", item);
231 
232  item["label"] = leader["units"];
233  data.emplace("leader_troops", item);
234 
235  item["label"] = leader["recall_units"];
236  data.emplace("leader_reserves", item);
237 
238  leader_list.add_row(data);
239  }
240 
241  std::stringstream str;
242  str << game.format_time_local() << "\n";
244 
245  // The new label value may have more or less lines than the previous value, so invalidate the layout.
246  find_widget<scroll_label>(get_window(), "slblSummary", false).set_label(str.str());
248 
249  toggle_button& replay_toggle = dynamic_cast<toggle_button&>(*show_replay_->get_widget());
250  toggle_button& cancel_orders_toggle = dynamic_cast<toggle_button&>(*cancel_orders_->get_widget());
251  toggle_button& change_difficulty_toggle = dynamic_cast<toggle_button&>(*change_difficulty_->get_widget());
252 
253  const bool is_replay = savegame::loadgame::is_replay_save(summary_);
254  const bool is_scenario_start = summary_["turn"].empty();
255 
256  // Always toggle show_replay on if the save is a replay
257  replay_toggle.set_value(is_replay);
258  replay_toggle.set_active(!is_replay && !is_scenario_start);
259 
260  // Cancel orders doesn't make sense on replay saves or start-of-scenario saves
261  cancel_orders_toggle.set_active(!is_replay && !is_scenario_start);
262 
263  // Changing difficulty doesn't make sense on non-start-of-scenario saves
264  change_difficulty_toggle.set_active(!is_replay && is_scenario_start);
265 }
266 
267 // This is a wrapper that prevents a corrupted save file (if it happens to be
268 // the first in the list) from making the dialog fail to open.
270 {
271  bool successfully_displayed_a_game = false;
272 
273  try {
274  const int selected_row = find_widget<listbox>(get_window(), "savegame_list", false).get_selected_row();
275  if(selected_row < 0) {
276  find_widget<button>(get_window(), "delete", false).set_active(false);
277  } else {
278  find_widget<button>(get_window(), "delete", false).set_active(!save_index_manager_->read_only());
280  successfully_displayed_a_game = true;
281  }
282  } catch(const config::error& e) {
283  // Clear the UI widgets, show an error message.
284  const std::string preamble = _("The selected file is corrupt: ");
285  const std::string message = e.message.empty() ? "(no details)" : e.message;
286  ERR_GAMELOADDLG << preamble << message << "\n";
287  }
288 
289  if(!successfully_displayed_a_game) {
290  find_widget<minimap>(get_window(), "minimap", false).set_map_data("");
291  find_widget<label>(get_window(), "lblScenario", false)
292  .set_label("");
293  find_widget<scroll_label>(get_window(), "slblSummary", false)
294  .set_label("");
295 
296  listbox& leader_list = find_widget<listbox>(get_window(), "leader_list", false);
297  leader_list.clear();
298 
299  toggle_button& replay_toggle = dynamic_cast<toggle_button&>(*show_replay_->get_widget());
300  toggle_button& cancel_orders_toggle = dynamic_cast<toggle_button&>(*cancel_orders_->get_widget());
301  toggle_button& change_difficulty_toggle = dynamic_cast<toggle_button&>(*change_difficulty_->get_widget());
302 
303  replay_toggle.set_active(false);
304  cancel_orders_toggle.set_active(false);
305  change_difficulty_toggle.set_active(false);
306  }
307 
308  // Disable Load button if nothing is selected or if the currently selected file can't be loaded
309  find_widget<button>(get_window(), "ok", false).set_active(successfully_displayed_a_game);
310 
311  // Disable 'Enter' loading in the same circumstance
312  get_window()->set_enter_disabled(!successfully_displayed_a_game);
313 }
314 
315 void game_load::filter_text_changed(const std::string& text)
316 {
317  listbox& list = find_widget<listbox>(get_window(), "savegame_list", false);
318 
319  const std::vector<std::string> words = utils::split(text, ' ');
320 
321  if(words == last_words_)
322  return;
323  last_words_ = words;
324 
325  boost::dynamic_bitset<> show_items;
326  show_items.resize(list.get_item_count(), true);
327 
328  if(!text.empty()) {
329  for(unsigned int i = 0; i < list.get_item_count() && i < games_.size(); i++) {
330  bool found = false;
331  for(const auto & word : words)
332  {
333  found = std::search(games_[i].name().begin(),
334  games_[i].name().end(),
335  word.begin(),
336  word.end(),
338  != games_[i].name().end();
339 
340  if(!found) {
341  // one word doesn't match, we don't reach words.end()
342  break;
343  }
344  }
345 
346  show_items[i] = found;
347  }
348  }
349 
350  list.set_row_shown(show_items);
351 }
352 
353 void game_load::evaluate_summary_string(std::stringstream& str, const config& cfg_summary)
354 {
355  std::string difficulty_human_str = string_table[cfg_summary["difficulty"]];
356  if(cfg_summary["corrupt"].to_bool()) {
357  str << "\n<span color='#f00'>" << _("(Invalid)") << "</span>";
358  // \todo: this skips the catch() statement in display_savegame. Low priority, as the
359  // dialog's state is reasonable; the "load" button is inactive, the "delete" button is
360  // active, and (cosmetic bug) it leaves the "change difficulty" toggle active. Can be
361  // triggered by creating an empty file in the save directory.
362  return;
363  }
364 
365  const std::string& campaign_type = cfg_summary["campaign_type"];
366 
367  try {
368  switch(game_classification::CAMPAIGN_TYPE::string_to_enum(campaign_type).v) {
370  const std::string campaign_id = cfg_summary["campaign"];
371 
372  const config* campaign = nullptr;
373  if(!campaign_id.empty()) {
374  if(const config& c = cache_config_.find_child("campaign", "id", campaign_id)) {
375  campaign = &c;
376  }
377  }
378 
379  if (campaign != nullptr) {
380  try {
381  const config &difficulty = campaign->find_child("difficulty", "define", cfg_summary["difficulty"]);
382  std::ostringstream ss;
383  ss << difficulty["label"] << " (" << difficulty["description"] << ")";
384  difficulty_human_str = ss.str();
385  } catch(const config::error&) {
386  }
387  }
388 
389  utils::string_map symbols;
390  if(campaign != nullptr) {
391  symbols["campaign_name"] = (*campaign)["name"];
392  } else {
393  // Fallback to nontranslatable campaign id.
394  symbols["campaign_name"] = "(" + campaign_id + ")";
395  }
396 
397  str << VGETTEXT("Campaign: $campaign_name", symbols);
398 
399  // Display internal id for debug purposes if we didn't above
400  if(game_config::debug && (campaign != nullptr)) {
401  str << '\n' << "(" << campaign_id << ")";
402  }
403  break;
404  }
405  case game_classification::CAMPAIGN_TYPE::MULTIPLAYER:
406  str << _("Multiplayer");
407  break;
408  case game_classification::CAMPAIGN_TYPE::TUTORIAL:
409  str << _("Tutorial");
410  break;
411  case game_classification::CAMPAIGN_TYPE::TEST:
412  str << _("Test scenario");
413  break;
414  }
415  } catch(const bad_enum_cast&) {
416  str << campaign_type;
417  }
418 
419  str << "\n";
420 
421  if(savegame::loadgame::is_replay_save(cfg_summary)) {
422  str << _("Replay");
423  } else if(!cfg_summary["turn"].empty()) {
424  str << _("Turn") << " " << cfg_summary["turn"];
425  } else {
426  str << _("Scenario start");
427  }
428 
429  str << "\n" << _("Difficulty: ")
430  << difficulty_human_str;
431 
432  if(!cfg_summary["version"].empty()) {
433  str << "\n" << _("Version: ") << cfg_summary["version"];
434  }
435 
436  const std::vector<std::string>& active_mods = utils::split(cfg_summary["active_mods"]);
437  if(!active_mods.empty()) {
438  str << "\n" << _("Modifications: ");
439  for(const auto& mod_id : active_mods) {
440  std::string mod_name;
441  try {
442  mod_name = cache_config_.find_child("modification", "id", mod_id)["name"].str();
443  } catch(const config::error&) {
444  // Fallback to nontranslatable mod id.
445  mod_name = "(" + mod_id + ")";
446  }
447 
448  str << "\n" << font::unicode_bullet << " " << mod_name;
449  }
450  }
451 }
453 {
455 }
456 
458 {
459  listbox& list = find_widget<listbox>(get_window(), "savegame_list", false);
460 
461  const std::size_t index = std::size_t(list.get_selected_row());
462  if(index < games_.size()) {
463 
464  // See if we should ask the user for deletion confirmation
466  if(!gui2::dialogs::game_delete::execute()) {
467  return;
468  }
469  }
470 
471  // Delete the file
472  save_index_manager_->delete_game(games_[index].name());
473 
474  // Remove it from the list of saves
475  games_.erase(games_.begin() + index);
476 
477  list.remove_row(index);
478 
480  }
481 }
482 
483 void game_load::key_press_callback(const SDL_Keycode key)
484 {
485  //
486  // Don't delete games when we're typing in the textbox!
487  //
488  // I'm not sure if this check was necessary when I first added this feature
489  // (I didn't check at the time), but regardless, it's needed now. If it turns
490  // out I screwed something up in my refactoring, I'll remove this.
491  //
492  // - vultraz, 2017-08-28
493  //
494  if(find_widget<text_box>(get_window(), "txtFilter", false).get_state() == text_box_base::FOCUSED) {
495  return;
496  }
497 
498  if(key == SDLK_DELETE) {
500  }
501 }
502 
504 {
505  menu_button& dir_list = find_widget<menu_button>(get_window(), "dirList", false);
506 
507  const auto& path = dir_list.get_value_config()["path"].str();
508  if(path.empty()) {
510  } else {
511  save_index_manager_ = std::make_shared<savegame::save_index_class>(path);
512  }
513 
516 }
517 
518 } // namespace dialogs
Define the common log macros for the gui toolkit.
void set_text_changed_callback(std::function< void(text_box_base *textbox, const std::string text)> cb)
Set the text_changed callback.
const ::config & get_value_config() const
Returns the entire config object for the selected row.
Definition: menu_button.hpp:98
virtual void set_value(unsigned selected, bool fire_event=false) override
Inherited from selectable_item.
std::map< std::string, t_string > string_map
A menu_button is a styled_widget to choose an element from a list of elements.
Definition: menu_button.hpp:60
field_bool * change_difficulty_
Definition: game_load.hpp:80
config & find_child(config_key_type key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:860
Main class to show messages to the user.
Definition: message.hpp:34
This shows the dialog to select and load a savegame file.
Definition: game_load.hpp:48
#define ERR_GAMELOADDLG
Definition: game_load.cpp:51
static std::shared_ptr< save_index_class > default_saves_dir()
Returns an instance for managing saves in filesystem::get_saves_dir()
Definition: save_index.cpp:210
std::shared_ptr< savegame::save_index_class > & save_index_manager_
Definition: game_load.hpp:78
void populate_game_list()
Update (both internally and visually) the list of games.
Definition: game_load.cpp:155
const game_config_view & cache_config_
Definition: game_load.hpp:87
This file contains the window object, this object is a top level container which has the event manage...
child_itors child_range(config_key_type key)
Definition: config.cpp:356
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup, const bool restore_background)
Shows a transient message to the user.
void ellipsis_truncate(std::string &str, const std::size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis. ...
void key_press_callback(const SDL_Keycode key)
Definition: game_load.cpp:483
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:22
void set_save_dir_list(menu_button &dir_list)
Definition: game_load.cpp:132
window * get_window() const
Returns a pointer to the dialog&#39;s window.
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:276
Implements some helper classes to ease adding fields to a dialog and hide the synchronization needed...
static std::string _(const char *str)
Definition: gettext.hpp:92
bool show(const unsigned auto_close_time=0)
Shows the window.
std::vector< std::string > last_words_
Definition: game_load.hpp:89
game_load(const game_config_view &cache_config, savegame::load_game_metadata &data)
Definition: game_load.cpp:82
const config & summary() const
Definition: save_index.cpp:253
Class for a single line text area.
Definition: text_box.hpp:140
std::string get_independent_binary_file_path(const std::string &type, const std::string &filename)
Returns an asset path to filename for binary path-independent use in saved games. ...
std::string filename_
Definition: action_wml.cpp:562
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1010
The listbox class.
Definition: listbox.hpp:42
std::string format_time_local() const
Definition: save_index.cpp:258
const config & options()
Definition: game.cpp:563
Desktop environment interaction functions.
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:186
This file contains the settings handling of the widget library.
std::ostringstream wrapper.
Definition: formatter.hpp:38
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:126
void set_visible(const visibility visible)
Definition: widget.cpp:475
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:171
static bool is_replay_save(const config &cfg)
Definition: savegame.hpp:126
void set_values(const std::vector<::config > &values, unsigned selected=0)
std::string path
Definition: game_config.cpp:38
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:132
virtual void set_use_markup(bool use_markup)
std::vector< savegame::save_info > games_
Definition: game_load.hpp:86
const std::string & name() const
Definition: save_index.hpp:39
styled_widget * get_widget()
Definition: field.hpp:206
bool open_object([[maybe_unused]] const std::string &path_or_url)
Definition: open.cpp:46
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
Definition: game_load.cpp:95
std::size_t i
Definition: function.cpp:940
The user set the widget invisible, that means:
void filter_text_changed(const std::string &text)
Definition: game_load.cpp:315
std::map< std::string, t_string > string_map
Definition: widget.hpp:26
grid & add_row(const string_map &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:67
field_bool * show_replay_
Definition: game_load.hpp:81
Declarations for File-IO.
const bool & debug
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:71
const std::string unicode_bullet
Definition: constants.cpp:46
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
bool ask_delete_saves()
Definition: game.cpp:784
const config & find_child(config_key_type key, const std::string &name, const std::string &value) const
void display_savegame_internal(const savegame::save_info &game)
Part of display_savegame that might throw a config::error if the savegame data is corrupt...
Definition: game_load.cpp:181
field_bool * cancel_orders_
Definition: game_load.hpp:82
std::string & filename_
Definition: game_load.hpp:77
Filename and modification date for a file list.
Definition: save_index.hpp:26
void connect_signal_pre_key_press(dispatcher &dispatcher, const signal_keyboard_function &signal)
Connects the signal for &#39;snooping&#39; on the keypress.
Definition: dispatcher.cpp:166
std::vector< std::string > split(const config_attribute_value &val)
unsigned int tile_size
Definition: game_config.cpp:67
symbol_table string_table
Definition: language.cpp:64
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:87
std::string message
Definition: exceptions.hpp:29
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:796
static lg::log_domain log_gameloaddlg
Definition: game_load.cpp:50
#define e
std::vector< other_version_dir > find_other_version_saves_dirs()
Searches for directories containing saves created by other versions of Wesnoth.
Definition: filesystem.cpp:823
void register_sorting_option(const int col, const Func &f)
Definition: listbox.hpp:271
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
mock_char c
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:64
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:144
void evaluate_summary_string(std::stringstream &str, const config &cfg_summary)
Definition: game_load.cpp:353
bool empty() const
Definition: config.cpp:916
Class for a toggle button.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:409
virtual void set_active(const bool active) override
See styled_widget::set_active.
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:285