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