The Battle for Wesnoth  1.19.9+dev
title_screen.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 "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"
27 #include "gui/auxiliary/tips.hpp"
33 #include "gui/dialogs/message.hpp"
41 #include "language.hpp"
42 #include "log.hpp"
44 //#define DEBUG_TOOLTIP
45 #ifdef DEBUG_TOOLTIP
46 #include "gui/dialogs/tooltip.hpp"
47 #endif
48 #include "gui/widgets/button.hpp"
49 #include "gui/widgets/image.hpp"
50 #include "gui/widgets/label.hpp"
52 #include "gui/widgets/settings.hpp"
53 #include "gui/widgets/window.hpp"
54 #include "help/help.hpp"
55 #include "sdl/surface.hpp"
57 #include "video.hpp"
58 #include "wml_exception.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, const 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>(id);
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>("logo-bg").set_image(game_config::images::game_logo_background);
192  find_widget<image>("logo").set_image(game_config::images::game_logo);
193 
194  //
195  // Tip-of-the-day browser
196  //
197  if(auto tip_pages = find_widget<multi_page>("tips", false, false)) {
199  tip_pages->add_page({
200  { "tip", {
201  { "use_markup", "true" },
202  { "label", tip.text() }
203  }},
204  { "source", {
205  { "use_markup", "true" },
206  { "label", tip.source() }
207  }}
208  });
209  }
210 
211  update_tip(true);
212  }
213 
215  std::bind(&title_screen::update_tip, this, true));
216 
218  std::bind(&title_screen::update_tip, this, false));
219 
220  // Tip panel visiblity and close button
221  panel& tip_panel = find_widget<panel>("tip_panel");
222 
223  tip_panel.set_visible(prefs::get().show_tips()
226 
227  if(auto toggle_tips = find_widget<button>("toggle_tip_panel", false, false)) {
228  connect_signal_mouse_left_click(*toggle_tips, [&tip_panel](auto&&...) {
229  const bool currently_hidden = tip_panel.get_visible() == widget::visibility::hidden;
230 
231  tip_panel.set_visible(currently_hidden
234 
235  // If previously hidden, will now be visible, so we can reuse the same value
236  prefs::get().set_show_tips(currently_hidden);
237  });
238  }
239 
240  //
241  // Help
242  //
243  register_button("help", hotkey::HOTKEY_HELP, []() {
245  help::show_help();
246  });
247 
248  //
249  // About
250  //
251  register_button("about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
252 
253  //
254  // Campaign
255  //
256  register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
257  try{
258  if(game_.new_campaign()) {
259  // Suspend drawing of the title screen,
260  // so it doesn't flicker in between loading screens.
261  hide();
262  set_retval(LAUNCH_GAME);
263  }
264  } catch (const config::error& e) {
265  gui2::show_error_message(e.what());
266  }
267  });
268 
269  //
270  // Multiplayer
271  //
274 
275  //
276  // Load game
277  //
278  register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
279  if(game_.load_game()) {
280  // Suspend drawing of the title screen,
281  // so it doesn't flicker in between loading screens.
282  hide();
283  set_retval(LAUNCH_GAME);
284  }
285  });
286 
287  //
288  // Addons
289  //
290  register_button("addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
291  if(manage_addons()) {
293  }
294  });
295 
296  //
297  // Editor
298  //
300 
301  //
302  // Cores
303  //
305  std::bind(&title_screen::button_callback_cores, this));
306 
307  //
308  // Language
309  //
310  register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
311  try {
312  if(game_.change_language()) {
313  on_resize();
314  update_static_labels();
315  }
316  } catch(const std::runtime_error& e) {
317  gui2::show_error_message(e.what());
318  }
319  });
320 
321  //
322  // Preferences
323  //
325  std::bind(&title_screen::show_preferences, this));
326 
327  //
328  // Achievements
329  //
331  std::bind(&title_screen::show_achievements, this));
332 
333  //
334  // Community
335  //
337  std::bind(&title_screen::show_community, this));
338 
339  //
340  // Quit
341  //
343  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
344  find_widget<button>("quit", false, true);
345 
346  //
347  // Debug clock
348  //
350  std::bind(&title_screen::show_debug_clock_window, this));
351 
352  auto clock = find_widget<button>("clock", false, false);
353  if(clock) {
354  clock->set_visible(show_debug_clock_button);
355  }
356 
357  //
358  // GUI Test and Debug Window
359  //
360  register_button("test_dialog", hotkey::HOTKEY_NULL,
361  std::bind(&title_screen::show_gui_test_dialog, this));
362 
363  auto test_dialog = find_widget<button>("test_dialog", false, false);
364  if(test_dialog) {
365  test_dialog->set_visible(show_debug_clock_button);
366  }
367 
368  //
369  // Static labels (version and language)
370  //
372 }
373 
375 {
376  //
377  // Version menu label
378  //
379  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
380 
381  if(label* version_label = find_widget<label>("revision_number", false, false)) {
382  version_label->set_label(version_string);
383  }
384 
385  get_canvas(1).set_variable("revision_number", wfl::variant(version_string));
386 
387  //
388  // Language menu label
389  //
390  if(auto* lang_button = find_widget<button>("language", false, false); lang_button) {
391  const auto& locale = translation::get_effective_locale_info();
392  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
393  // and strip the charset from the Boost locale identifier.
394  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
395  const auto& langs = get_languages(true);
396 
397  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
398  return lang.localename == boost_name;
399  });
400 
401  if(lang_def != langs.end()) {
402  lang_button->set_label(lang_def->language.str());
403  } else if(boost_name == "c" || boost_name == "C") {
404  // HACK: sometimes System Default doesn't match anything on the list. If you fork
405  // Wesnoth and change the neutral language to something other than US English, you
406  // want to change this too.
407  lang_button->set_label("English (US)");
408  } else {
409  // If somehow the locale doesn't match a known translation, use the
410  // locale identifier as a last resort
411  lang_button->set_label(boost_name);
412  }
413  }
414 }
415 
417 {
419 }
420 
421 void title_screen::update_tip(const bool previous)
422 {
423  multi_page* tip_pages = find_widget<multi_page>("tips", false, false);
424  if(tip_pages == nullptr) {
425  return;
426  }
427  if(tip_pages->get_page_count() == 0) {
428  return;
429  }
430 
431  int page = tip_pages->get_selected_page();
432  if(previous) {
433  if(page <= 0) {
434  page = tip_pages->get_page_count();
435  }
436  --page;
437  } else {
438  ++page;
439  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
440  page = 0;
441  }
442  }
443 
444  tip_pages->select_page(page);
445 }
446 
448 {
449  assert(show_debug_clock_button);
450 
451  if(debug_clock_) {
452  debug_clock_.reset(nullptr);
453  } else {
454  debug_clock_.reset(new debug_clock());
455  debug_clock_->show(true);
456  }
457 }
458 
460 {
462 
463  std::vector<std::string> options;
464  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
465  if(!sc["is_unit_test"].to_bool(false)) {
466  options.emplace_back(sc["id"]);
467  }
468  }
469 
470  std::sort(options.begin(), options.end());
471 
472  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
473  dlg.show();
474 
475  int choice = dlg.selected_index();
476  if(choice >= 0) {
477  game_.set_test(options[choice]);
479  }
480 }
481 
483 {
485  ach.show();
486 }
487 
489 {
490  game_version dlg;
491  // shows the 5th tab, community, when the dialog is shown
492  dlg.display(4);
493 }
494 
496 {
497  gui2::dialogs::gui_test_dialog::execute();
498 }
499 
501 {
503  pref_dlg.show();
504  if (pref_dlg.get_retval() == RELOAD_UI) {
506  }
507 
508  // Currently blurred windows don't capture well if there is something
509  // on top of them at the time of blur. Resizing the game window in
510  // preferences will cause the title screen tip and menu panels to
511  // capture the prefs dialog in their blur. This workaround simply
512  // forces them to re-capture the blur after the dialog closes.
513  panel* tip_panel = find_widget<panel>("tip_panel", false, false);
514  if(tip_panel != nullptr) {
515  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
516  tip_panel->queue_redraw();
517  }
518  panel* menu_panel = find_widget<panel>("menu_panel", false, false);
519  if(menu_panel != nullptr) {
520  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
521  menu_panel->queue_redraw();
522  }
523 }
524 
526 {
527  while(true) {
529  dlg.show();
530 
531  if(dlg.get_retval() != gui2::retval::OK) {
532  return;
533  }
534 
535  const auto res = dlg.get_choice();
536 
537  if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
538  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
539  continue;
540  }
541  }
542 
543  switch(res) {
544  case decltype(dlg)::choice::JOIN:
545  game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
547  break;
548  case decltype(dlg)::choice::CONNECT:
551  break;
552  case decltype(dlg)::choice::HOST:
553  game_.select_mp_server("localhost");
555  break;
556  case decltype(dlg)::choice::LOCAL:
558  break;
559  }
560 
561  return;
562  }
563 }
564 
566 {
567  int current = 0;
568 
569  std::vector<config> cores;
570  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
571  cores.push_back(core);
572 
573  if(core["id"] == prefs::get().core()) {
574  current = cores.size() - 1;
575  }
576  }
577 
578  gui2::dialogs::core_selection core_dlg(cores, current);
579  if(core_dlg.show()) {
580  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
581 
582  prefs::get().set_core(core_id);
584  }
585 }
586 
587 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
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:644
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 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 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_button(const std::string &id, hotkey::HOTKEY_COMMAND hk, const std::function< void()> &callback)
void register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:147
The tips of day structure.
Definition: tips.hpp:54
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:113
int get_selected_page() const
Returns the selected page.
Definition: multi_page.cpp:128
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:119
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: panel.cpp:61
canvas & get_canvas(const unsigned index)
void set_visible(const visibility visible)
Definition: widget.cpp:479
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
visibility get_visible() const
Definition: widget.cpp:506
@ visible
The user sets the widget visible, that means:
@ hidden
The user sets the widget hidden, 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:321
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:395
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:334
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:412
int get_retval()
Definition: window.hpp:402
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:1029
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:587
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:64
void remove()
Removes a tip.
Definition: tooltip.cpp:94
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:62
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:47
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:48
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
void show_help(const std::string &show_topic)
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
surface read_pixels(SDL_Rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:592
This file contains the settings handling of the widget library.
std::string filename
Filename.
The help implementation caches data parsed from the game_config.
Definition: help.hpp:39
Holds a 2D point.
Definition: point.hpp:25
Helper class, don't construct this directly.
#define ERR_CF
static lg::log_domain log_config("config")
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e