The Battle for Wesnoth  1.19.3+dev
title_screen.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 "addon/manager_ui.hpp"
21 #include "filesystem.hpp"
22 #include "font/font_config.hpp"
23 #include "formula/string_utils.hpp"
24 #include "game_config.hpp"
25 #include "game_config_manager.hpp"
26 #include "game_launcher.hpp"
28 #include "gui/auxiliary/tips.hpp"
35 #include "gui/dialogs/message.hpp"
42 #include "language.hpp"
43 #include "log.hpp"
45 //#define DEBUG_TOOLTIP
46 #ifdef DEBUG_TOOLTIP
47 #include "gui/dialogs/tooltip.hpp"
48 #endif
49 #include "gui/widgets/button.hpp"
50 #include "gui/widgets/image.hpp"
51 #include "gui/widgets/label.hpp"
53 #include "gui/widgets/settings.hpp"
54 #include "gui/widgets/window.hpp"
55 #include "help/help.hpp"
56 #include "sdl/surface.hpp"
58 #include "video.hpp"
59 
60 #include <algorithm>
61 #include <functional>
62 
63 #include <boost/algorithm/string/erase.hpp>
64 
65 static lg::log_domain log_config("config");
66 #define ERR_CF LOG_STREAM(err, log_config)
67 #define WRN_CF LOG_STREAM(warn, log_config)
68 
69 namespace gui2::dialogs
70 {
71 
72 REGISTER_DIALOG(title_screen)
73 
75 
77  : modal_dialog(window_id())
78  , debug_clock_()
79  , game_(game)
80 {
81  set_allow_plugin_skip(false);
82  init_callbacks();
83 }
84 
86 {
87 }
88 
89 void title_screen::register_button(const std::string& id, hotkey::HOTKEY_COMMAND hk, std::function<void()> callback)
90 {
91  if(hk != hotkey::HOTKEY_NULL) {
92  register_hotkey(hk, std::bind(callback));
93  }
94 
95  try {
96  button& btn = find_widget<button>(this, id, false);
97  connect_signal_mouse_left_click(btn, std::bind(callback));
98  } catch(const wml_exception& e) {
99  ERR_GUI_P << e.user_message;
100  prefs::get().set_gui2_theme("default");
102  }
103 }
104 
105 static void launch_lua_console()
106 {
108 }
109 
110 static void make_screenshot()
111 {
112  surface screenshot = video::read_pixels();
113  if(screenshot) {
114  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
116  gui2::dialogs::screenshot_notification::display(filename, screenshot);
117  }
118 }
119 
120 #ifdef DEBUG_TOOLTIP
121 /*
122  * This function is used to test the tooltip placement algorithms as
123  * described in the »Tooltip placement« section in the GUI2 design
124  * document.
125  *
126  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
127  * - 0 to test with a normal tooltip placement.
128  * - 30 to test with a larger normal tooltip placement.
129  * - 60 to test with a huge tooltip placement.
130  * - 150 to test with a borderline to insanely huge tooltip placement.
131  * - 180 to test with an insanely huge tooltip placement.
132  */
133 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
134 {
135  std::string message = "Hello world.";
136 
137  for(int i = 0; i < 0; ++i) {
138  message += " More greetings.";
139  }
140 
142  gui2::tip::show("tooltip", message, coordinate);
143 
144  handled = true;
145 }
146 #endif
147 
149 {
150  set_click_dismiss(false);
151  set_enter_disabled(true);
152  set_escape_disabled(true);
153 
154 #ifdef DEBUG_TOOLTIP
155  connect_signal<event::SDL_MOUSE_MOTION>(
156  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
158 #endif
159 
160  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
161 
162  //
163  // General hotkeys
164  //
166  std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
167 
169  std::bind(&title_screen::show_achievements, this));
170 
173 
174  // A wrapper is needed here since the relevant display function is overloaded, and
175  // since the wrapper's signature doesn't exactly match what register_hotkey expects.
177 
179 
180  //
181  // Background and logo images
182  //
184  // game works just fine if just one of the background images are missing
185  ERR_CF << "No titlescreen background defined in game config";
186  }
187 
190 
191  find_widget<image>(this, "logo-bg", false).set_image(game_config::images::game_logo_background);
192  find_widget<image>(this, "logo", false).set_image(game_config::images::game_logo);
193 
194  //
195  // Tip-of-the-day browser
196  //
197  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
198 
199  if(tip_pages != nullptr) {
200  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
201  if(tips.empty()) {
202  WRN_CF << "There are no tips of day available.";
203  }
204  for(const auto& tip : tips) {
206  widget_data page;
207 
208  widget["use_markup"] = "true";
209 
210  // Use pango markup to insert drop cap
211  // Example: Lawful units -> <span ...>L</span>awful units
212  // If tip starts with a tag, we need to insert the <span> after it
213  // then insert the </span> tag after the first character of the text
214  // after markup. Assumes that the tags themselves don't
215  // contain non-ASCII characters.
216  // Example: <i>Lawful</i> units -> <i><span ...>L</span>awful</i> units
217  const std::string& script_font = font::get_font_families(font::FONT_SCRIPT);
218  std::string tip_text = tip.text().str();
219  std::size_t pos = 0;
220  while (pos < tip_text.size() && tip_text.at(pos) == '<') {
221  pos = tip_text.find_first_of(">", pos) + 1;
222  }
223  utf8::insert(tip_text, pos+1, "</span>");
224  utf8::insert(tip_text, pos, "<span font_family='" + script_font + "' font_size='xx-large'>");
225 
226  widget["label"] = tip_text;
227 
228  page.emplace("tip", widget);
229 
230  widget["label"] = tip.source();
231  page.emplace("source", widget);
232 
233  tip_pages->add_page(page);
234  }
235 
236  update_tip(true);
237  }
238 
240  std::bind(&title_screen::update_tip, this, true));
241 
243  std::bind(&title_screen::update_tip, this, false));
244 
245  //
246  // Help
247  //
248  register_button("help", hotkey::HOTKEY_HELP, []() {
249  if(gui2::new_widgets) {
250  gui2::dialogs::help_browser::display();
251  }
252 
253  help::show_help();
254  });
255 
256  //
257  // About
258  //
259  register_button("about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
260 
261  //
262  // Campaign
263  //
264  register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
265  try{
266  if(game_.new_campaign()) {
267  // Suspend drawing of the title screen,
268  // so it doesn't flicker in between loading screens.
269  hide();
270  set_retval(LAUNCH_GAME);
271  }
272  } catch (const config::error& e) {
273  gui2::show_error_message(e.what());
274  }
275  });
276 
277  //
278  // Multiplayer
279  //
282 
283  //
284  // Load game
285  //
286  register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
287  if(game_.load_game()) {
288  // Suspend drawing of the title screen,
289  // so it doesn't flicker in between loading screens.
290  hide();
291  set_retval(LAUNCH_GAME);
292  }
293  });
294 
295  //
296  // Addons
297  //
298  register_button("addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
299  if(manage_addons()) {
301  }
302  });
303 
304  //
305  // Editor
306  //
308 
309  //
310  // Cores
311  //
313  std::bind(&title_screen::button_callback_cores, this));
314 
315  //
316  // Language
317  //
318  register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
319  try {
320  if(game_.change_language()) {
321  on_resize();
322  update_static_labels();
323  }
324  } catch(const std::runtime_error& e) {
325  gui2::show_error_message(e.what());
326  }
327  });
328 
329  //
330  // Preferences
331  //
333  std::bind(&title_screen::show_preferences, this));
334 
335  //
336  // Achievements
337  //
339  std::bind(&title_screen::show_achievements, this));
340 
341  //
342  // Community
343  //
345  std::bind(&title_screen::show_community, this));
346 
347  //
348  // Quit
349  //
351  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
352  find_widget<button>(this, "quit", false, true);
353 
354  //
355  // Debug clock
356  //
358  std::bind(&title_screen::show_debug_clock_window, this));
359 
360  auto clock = find_widget<button>(this, "clock", false, false);
361  if(clock) {
363  }
364 
365  //
366  // GUI Test and Debug Window
367  //
368  register_button("test_dialog", hotkey::HOTKEY_NULL,
369  std::bind(&title_screen::show_gui_test_dialog, this));
370 
371  auto test_dialog = find_widget<button>(this, "test_dialog", false, false);
372  if(test_dialog) {
374  }
375 
376  //
377  // Static labels (version and language)
378  //
380 }
381 
383 {
384  //
385  // Version menu label
386  //
387  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
388 
389  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
390  version_label->set_label(version_string);
391  }
392 
393  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
394 
395  //
396  // Language menu label
397  //
398  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
399  const auto& locale = translation::get_effective_locale_info();
400  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
401  // and strip the charset from the Boost locale identifier.
402  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
403  const auto& langs = get_languages(true);
404 
405  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
406  return lang.localename == boost_name;
407  });
408 
409  if(lang_def != langs.end()) {
410  lang_button->set_label(lang_def->language.str());
411  } else if(boost_name == "c" || boost_name == "C") {
412  // HACK: sometimes System Default doesn't match anything on the list. If you fork
413  // Wesnoth and change the neutral language to something other than US English, you
414  // want to change this too.
415  lang_button->set_label("English (US)");
416  } else {
417  // If somehow the locale doesn't match a known translation, use the
418  // locale identifier as a last resort
419  lang_button->set_label(boost_name);
420  }
421  }
422 }
423 
425 {
427 }
428 
429 void title_screen::update_tip(const bool previous)
430 {
431  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
432  if(tip_pages == nullptr) {
433  return;
434  }
435  if(tip_pages->get_page_count() == 0) {
436  return;
437  }
438 
439  int page = tip_pages->get_selected_page();
440  if(previous) {
441  if(page <= 0) {
442  page = tip_pages->get_page_count();
443  }
444  --page;
445  } else {
446  ++page;
447  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
448  page = 0;
449  }
450  }
451 
452  tip_pages->select_page(page);
453 }
454 
456 {
457  assert(show_debug_clock_button);
458 
459  if(debug_clock_) {
460  debug_clock_.reset(nullptr);
461  } else {
462  debug_clock_.reset(new debug_clock());
463  debug_clock_->show(true);
464  }
465 }
466 
468 {
470 
471  std::vector<std::string> options;
472  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
473  if(!sc["is_unit_test"].to_bool(false)) {
474  options.emplace_back(sc["id"]);
475  }
476  }
477 
478  std::sort(options.begin(), options.end());
479 
480  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
481  dlg.show();
482 
483  int choice = dlg.selected_index();
484  if(choice >= 0) {
485  game_.set_test(options[choice]);
487  }
488 }
489 
491 {
493  ach.show();
494 }
495 
497 {
498  game_version dlg;
499  // shows the 5th tab, community, when the dialog is shown
500  dlg.display(4);
501 }
502 
504 {
505  gui2::dialogs::gui_test_dialog::execute();
506 }
507 
509 {
511  pref_dlg.show();
512  if (pref_dlg.get_retval() == RELOAD_UI) {
514  }
515 
516  // Currently blurred windows don't capture well if there is something
517  // on top of them at the time of blur. Resizing the game window in
518  // preferences will cause the title screen tip and menu panels to
519  // capture the prefs dialog in their blur. This workaround simply
520  // forces them to re-capture the blur after the dialog closes.
521  panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
522  if(tip_panel != nullptr) {
523  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
524  tip_panel->queue_redraw();
525  }
526  panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
527  if(menu_panel != nullptr) {
528  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
529  menu_panel->queue_redraw();
530  }
531 }
532 
534 {
535  while(true) {
537  dlg.show();
538 
539  if(dlg.get_retval() != gui2::retval::OK) {
540  return;
541  }
542 
543  const auto res = dlg.get_choice();
544 
545  if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
546  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
547  continue;
548  }
549  }
550 
551  switch(res) {
552  case decltype(dlg)::choice::JOIN:
553  game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
555  break;
556  case decltype(dlg)::choice::CONNECT:
559  break;
560  case decltype(dlg)::choice::HOST:
561  game_.select_mp_server("localhost");
563  break;
564  case decltype(dlg)::choice::LOCAL:
566  break;
567  }
568 
569  return;
570  }
571 }
572 
574 {
575  int current = 0;
576 
577  std::vector<config> cores;
578  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
579  cores.push_back(core);
580 
581  if(core["id"] == prefs::get().core()) {
582  current = cores.size() - 1;
583  }
584  }
585 
586  gui2::dialogs::core_selection core_dlg(cores, current);
587  if(core_dlg.show()) {
588  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
589 
590  prefs::get().set_core(core_id);
592  }
593 }
594 
595 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
static game_config_manager * get()
void load_game_config_for_create(bool is_mp, bool is_test=false)
bool change_language()
void select_mp_server(const std::string &server)
void set_test(const std::string &id)
Simple push button.
Definition: button.hpp:36
void set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:154
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:673
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
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.
int get_retval() const
Returns the cached window exit code.
window * get_window()
Returns a pointer to the dialog's window.
int selected_index() const
Returns the selected item index after displaying.
This class implements the title screen.
std::unique_ptr< modeless_dialog > debug_clock_
Holds the debug clock dialog.
void update_tip(const bool previous)
Updates the tip of day widget.
void register_button(const std::string &id, hotkey::HOTKEY_COMMAND hk, std::function< void()> callback)
void show_gui_test_dialog()
Shows the gui test window.
void update_static_labels()
Updates UI labels that are not t_string after a language change.
void show_debug_clock_window()
Shows the debug clock.
void show_preferences()
Shows the preferences dialog.
void register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:147
grid & add_page(const widget_item &item)
Adds single page to the grid.
Definition: multi_page.cpp:43
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:98
int get_selected_page() const
Returns the selected page.
Definition: multi_page.cpp:113
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:104
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: panel.cpp:61
canvas & get_canvas(const unsigned index)
Base class for all widgets.
Definition: widget.hpp:53
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:454
@ visible
The user sets the widget visible, that means:
@ 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 set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:397
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:338
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:414
static prefs & get()
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:965
static std::string _(const char *str)
Definition: gettext.hpp:93
#define ERR_GUI_P
Definition: log.hpp:69
This file contains the window object, this object is a top level container which has the event manage...
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:126
Standard logging facilities (interface).
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:228
std::string get_screenshot_dir()
std::string get_next_filename(const std::string &name, const std::string &extension)
Get the next free filename using "name + number (3 digits) + extension" maximum 1000 files then start...
Definition: filesystem.cpp:585
@ FONT_SCRIPT
const t_string & get_font_families(family_class fclass)
Returns the currently defined fonts.
std::string game_title_background
std::string game_title
std::string game_logo
std::string game_logo_background
Game configuration data as global variables.
Definition: build_info.cpp:61
const std::string revision
void show(const std::string &window_id, const t_string &message, const point &mouse, const SDL_Rect &source_rect)
Shows a tip.
Definition: tooltip.cpp:65
void remove()
Removes a tip.
Definition: tooltip.cpp:95
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:63
static void launch_lua_console()
static void make_screenshot()
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
REGISTER_DIALOG(editor_edit_unit)
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::vector< game_tip > tips
Definition: settings.cpp:55
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:47
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:34
std::map< std::string, t_string > widget_item
Definition: widget.hpp:31
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:23
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:203
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
void show_help(const std::string &show_topic, int xloc, int yloc)
Open the help browser, show topic with id show_topic.
Definition: help.cpp:140
@ HOTKEY_SCREENSHOT
@ HOTKEY_ACHIEVEMENTS
@ TITLE_SCREEN__MULTIPLAYER
@ TITLE_SCREEN__ADDONS
@ TITLE_SCREEN__EDITOR
@ HOTKEY_QUIT_TO_DESKTOP
@ HOTKEY_LOAD_GAME
@ TITLE_SCREEN__TEST
@ TITLE_SCREEN__PREVIOUS_TIP
@ HOTKEY_PREFERENCES
@ TITLE_SCREEN__CORES
@ TITLE_SCREEN__CAMPAIGN
@ TITLE_SCREEN__RELOAD_WML
@ TITLE_SCREEN__NEXT_TIP
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:576
std::string & insert(std::string &str, const std::size_t pos, const std::string &insert)
Insert a UTF-8 string at the specified position.
Definition: unicode.cpp:98
surface read_pixels(SDL_Rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:580
This file contains the settings handling of the widget library.
std::string filename
Filename.
Holds a 2D point.
Definition: point.hpp:25
Helper class, don't construct this directly.
#define ERR_CF
#define WRN_CF
static lg::log_domain log_config("config")
#define e