The Battle for Wesnoth  1.19.0-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 "formula/string_utils.hpp"
23 #include "game_config.hpp"
24 #include "game_config_manager.hpp"
25 #include "game_launcher.hpp"
27 #include "gui/auxiliary/tips.hpp"
34 #include "gui/dialogs/message.hpp"
40 #include "language.hpp"
41 #include "log.hpp"
42 #include "preferences/game.hpp"
43 //#define DEBUG_TOOLTIP
44 #ifdef DEBUG_TOOLTIP
45 #include "gui/dialogs/tooltip.hpp"
46 #endif
47 #include "gui/widgets/button.hpp"
48 #include "gui/widgets/image.hpp"
49 #include "gui/widgets/label.hpp"
51 #include "gui/widgets/settings.hpp"
52 #include "gui/widgets/window.hpp"
53 #include "help/help.hpp"
54 #include "sdl/surface.hpp"
55 #include "video.hpp"
56 
57 #include <algorithm>
58 #include <functional>
59 
60 #include <boost/algorithm/string/erase.hpp>
61 
62 static lg::log_domain log_config("config");
63 #define ERR_CF LOG_STREAM(err, log_config)
64 #define WRN_CF LOG_STREAM(warn, log_config)
65 
66 namespace gui2::dialogs
67 {
68 
69 REGISTER_DIALOG(title_screen)
70 
72 
74  : modal_dialog(window_id())
75  , debug_clock_()
76  , game_(game)
77 {
78  set_allow_plugin_skip(false);
79  init_callbacks();
80 }
81 
83 {
84 }
85 
86 using btn_callback = std::function<void()>;
87 
88 static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
89 {
90  if(hk != hotkey::HOTKEY_NULL) {
91  win.register_hotkey(hk, std::bind(callback));
92  }
93 
94  auto b = find_widget<button>(&win, id, false, false);
95  if(b != nullptr)
96  {
97  connect_signal_mouse_left_click(*b, std::bind(callback));
98  }
99 }
100 
101 static void launch_lua_console()
102 {
104 }
105 
106 static void make_screenshot()
107 {
108  surface screenshot = video::read_pixels();
109  if(screenshot) {
110  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
111  filename = filesystem::get_next_filename(filename, ".jpg");
112  gui2::dialogs::screenshot_notification::display(filename, screenshot);
113  }
114 }
115 
116 #ifdef DEBUG_TOOLTIP
117 /*
118  * This function is used to test the tooltip placement algorithms as
119  * described in the »Tooltip placement« section in the GUI2 design
120  * document.
121  *
122  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
123  * - 0 to test with a normal tooltip placement.
124  * - 30 to test with a larger normal tooltip placement.
125  * - 60 to test with a huge tooltip placement.
126  * - 150 to test with a borderline to insanely huge tooltip placement.
127  * - 180 to test with an insanely huge tooltip placement.
128  */
129 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
130 {
131  std::string message = "Hello world.";
132 
133  for(int i = 0; i < 0; ++i) {
134  message += " More greetings.";
135  }
136 
138  gui2::tip::show("tooltip", message, coordinate);
139 
140  handled = true;
141 }
142 #endif
143 
145 {
146  set_click_dismiss(false);
147  set_enter_disabled(true);
148  set_escape_disabled(true);
149 
150 #ifdef DEBUG_TOOLTIP
151  connect_signal<event::SDL_MOUSE_MOTION>(
152  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
154 #endif
155 
156  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
157 
158  //
159  // General hotkeys
160  //
162  std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
163 
165  std::bind(&title_screen::show_achievements, this));
166 
169 
170  // A wrapper is needed here since the relevant display function is overloaded, and
171  // since the wrapper's signature doesn't exactly match what register_hotkey expects.
173 
175 
176  //
177  // Background and logo images
178  //
179  if(game_config::images::game_title.empty()) {
180  ERR_CF << "No title image defined";
181  }
182 
184 
186  ERR_CF << "No title background image defined";
187  }
188 
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  widget["label"] = tip.text();
211  page.emplace("tip", widget);
212 
213  widget["label"] = tip.source();
214  page.emplace("source", widget);
215 
216  tip_pages->add_page(page);
217  }
218 
219  update_tip(true);
220  }
221 
223  std::bind(&title_screen::update_tip, this, true));
224 
226  std::bind(&title_screen::update_tip, this, false));
227 
228  //
229  // Help
230  //
231  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
232  if(gui2::new_widgets) {
233  gui2::dialogs::help_browser::display();
234  }
235 
236  help::show_help();
237  });
238 
239  //
240  // About
241  //
242  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
243 
244  //
245  // Campaign
246  //
247  register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
248  try{
249  if(game_.new_campaign()) {
250  // Suspend drawing of the title screen,
251  // so it doesn't flicker in between loading screens.
252  hide();
253  set_retval(LAUNCH_GAME);
254  }
255  } catch (const config::error& e) {
256  gui2::show_error_message(e.what());
257  }
258  });
259 
260  //
261  // Multiplayer
262  //
265 
266  //
267  // Load game
268  //
269  register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
270  if(game_.load_game()) {
271  // Suspend drawing of the title screen,
272  // so it doesn't flicker in between loading screens.
273  hide();
274  set_retval(LAUNCH_GAME);
275  }
276  });
277 
278  //
279  // Addons
280  //
281  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
282  if(manage_addons()) {
284  }
285  });
286 
287  //
288  // Editor
289  //
290  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
291 
292  //
293  // Cores
294  //
296  std::bind(&title_screen::button_callback_cores, this));
297 
298  //
299  // Language
300  //
301  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
302  try {
303  if(game_.change_language()) {
304  on_resize();
305  update_static_labels();
306  }
307  } catch(const std::runtime_error& e) {
308  gui2::show_error_message(e.what());
309  }
310  });
311 
312  //
313  // Preferences
314  //
315  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, [this]() {
316  gui2::dialogs::preferences_dialog::display();
317 
318  // Currently blurred windows don't capture well if there is something
319  // on top of them at the time of blur. Resizing the game window in
320  // preferences will cause the title screen tip and menu panels to
321  // capture the prefs dialog in their blur. This workaround simply
322  // forces them to re-capture the blur after the dialog closes.
323  panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
324  if(tip_panel != nullptr) {
325  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
326  tip_panel->queue_redraw();
327  }
328  panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
329  if(menu_panel != nullptr) {
330  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
331  menu_panel->queue_redraw();
332  }
333  });
334 
335  //
336  // Achievements
337  //
338  register_button(*this, "achievements", hotkey::HOTKEY_ACHIEVEMENTS,
339  std::bind(&title_screen::show_achievements, this));
340 
341  //
342  // Credits
343  //
344  register_button(*this, "credits", hotkey::TITLE_SCREEN__CREDITS, [this]() { set_retval(SHOW_ABOUT); });
345 
346  //
347  // Quit
348  //
349  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
350  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
351  find_widget<button>(this, "quit", false, true);
352 
353  //
354  // Debug clock
355  //
356  register_button(*this, "clock", hotkey::HOTKEY_NULL,
357  std::bind(&title_screen::show_debug_clock_window, this));
358 
359  auto clock = find_widget<button>(this, "clock", false, false);
360  if(clock) {
362  }
363 
364  //
365  // Static labels (version and language)
366  //
368 }
369 
371 {
372  //
373  // Version menu label
374  //
375  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
376 
377  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
378  version_label->set_label(version_string);
379  }
380 
381  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
382 
383  //
384  // Language menu label
385  //
386  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
387  const auto& locale = translation::get_effective_locale_info();
388  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
389  // and strip the charset from the Boost locale identifier.
390  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
391  const auto& langs = get_languages(true);
392 
393  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
394  return lang.localename == boost_name;
395  });
396 
397  if(lang_def != langs.end()) {
398  lang_button->set_label(lang_def->language.str());
399  } else if(boost_name == "c" || boost_name == "C") {
400  // HACK: sometimes System Default doesn't match anything on the list. If you fork
401  // Wesnoth and change the neutral language to something other than US English, you
402  // want to change this too.
403  lang_button->set_label("English (US)");
404  } else {
405  // If somehow the locale doesn't match a known translation, use the
406  // locale identifier as a last resort
407  lang_button->set_label(boost_name);
408  }
409  }
410 }
411 
413 {
415 }
416 
417 void title_screen::update_tip(const bool previous)
418 {
419  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
420  if(tip_pages == nullptr) {
421  return;
422  }
423  if(tip_pages->get_page_count() == 0) {
424  return;
425  }
426 
427  int page = tip_pages->get_selected_page();
428  if(previous) {
429  if(page <= 0) {
430  page = tip_pages->get_page_count();
431  }
432  --page;
433  } else {
434  ++page;
435  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
436  page = 0;
437  }
438  }
439 
440  tip_pages->select_page(page);
441 }
442 
444 {
445  assert(show_debug_clock_button);
446 
447  if(debug_clock_) {
448  debug_clock_.reset(nullptr);
449  } else {
450  debug_clock_.reset(new debug_clock());
451  debug_clock_->show(true);
452  }
453 }
454 
456 {
458 
459  std::vector<std::string> options;
460  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
461  if(!sc["is_unit_test"].to_bool(false)) {
462  options.emplace_back(sc["id"]);
463  }
464  }
465 
466  std::sort(options.begin(), options.end());
467 
468  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
469  dlg.show();
470 
471  int choice = dlg.selected_index();
472  if(choice >= 0) {
473  game_.set_test(options[choice]);
475  }
476 }
477 
479 {
481  ach.show();
482 }
483 
485 {
486  while(true) {
488  dlg.show();
489 
490  if(dlg.get_retval() != gui2::retval::OK) {
491  return;
492  }
493 
494  const auto res = dlg.get_choice();
495 
496  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
497  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
498  continue;
499  }
500  }
501 
502  switch(res) {
503  case decltype(dlg)::choice::JOIN:
506  break;
507  case decltype(dlg)::choice::CONNECT:
510  break;
511  case decltype(dlg)::choice::HOST:
512  game_.select_mp_server("localhost");
514  break;
515  case decltype(dlg)::choice::LOCAL:
517  break;
518  }
519 
520  return;
521  }
522 }
523 
525 {
526  int current = 0;
527 
528  std::vector<config> cores;
529  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
530  cores.push_back(core);
531 
532  if(core["id"] == preferences::core_id()) {
533  current = cores.size() - 1;
534  }
535  }
536 
537  gui2::dialogs::core_selection core_dlg(cores, current);
538  if(core_dlg.show()) {
539  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
540 
543  }
544 }
545 
546 } // 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)
void set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:153
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:571
This shows a dialog displaying achievements.
This shows the dialog which allows the user to choose which core to play.
Clock to test the draw events.
Definition: debug_clock.hpp:50
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.
This shows the dialog to select the kind of MP game the user wants to play.
A simple one-column listbox with OK and Cancel buttons.
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 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 register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:147
A label displays text that can be wrapped but no scrollbars are provided.
Definition: label.hpp:56
A multi page is a control that contains several 'pages' of which only one is visible.
Definition: multi_page.hpp:50
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
A panel is a visible container to hold multiple widgets.
Definition: panel.hpp:55
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:455
@ 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:63
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:327
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:399
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:340
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:416
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
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...
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:545
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:60
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:79
void remove()
Removes a tip.
Definition: tooltip.cpp:109
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:77
static void launch_lua_console()
static void register_button(window &win, const std::string &id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
static void make_screenshot()
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
std::function< void()> btn_callback
REGISTER_DIALOG(tod_new_schedule)
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__CREDITS
@ 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
void set_core_id(const std::string &core_id)
Definition: general.cpp:338
int mp_server_warning_disabled()
Definition: game.cpp:488
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:353
std::string core_id()
Definition: general.cpp:332
const config & options()
Definition: game.cpp:552
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:580
This file contains the settings handling of the widget library.
Holds a 2D point.
Definition: point.hpp:25
#define ERR_CF
#define WRN_CF
static lg::log_domain log_config("config")
#define e
#define b