The Battle for Wesnoth  1.17.23+dev
title_screen.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2023
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"
28 #include "gui/core/timer.hpp"
36 #include "gui/dialogs/message.hpp"
42 #include "language.hpp"
43 #include "log.hpp"
44 #include "preferences/game.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"
57 #include "sdl/utils.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 using btn_callback = std::function<void()>;
90 
91 static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
92 {
93  if(hk != hotkey::HOTKEY_NULL) {
94  win.register_hotkey(hk, std::bind(callback));
95  }
96 
97  auto b = find_widget<button>(&win, id, false, false);
98  if(b != nullptr)
99  {
100  connect_signal_mouse_left_click(*b, std::bind(callback));
101  }
102 }
103 
104 static void launch_lua_console()
105 {
107 }
108 
109 static void make_screenshot()
110 {
111  surface screenshot = video::read_pixels();
112  if(screenshot) {
113  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
114  filename = filesystem::get_next_filename(filename, ".jpg");
115  gui2::dialogs::screenshot_notification::display(filename, screenshot);
116  }
117 }
118 
119 #ifdef DEBUG_TOOLTIP
120 /*
121  * This function is used to test the tooltip placement algorithms as
122  * described in the »Tooltip placement« section in the GUI2 design
123  * document.
124  *
125  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
126  * - 0 to test with a normal tooltip placement.
127  * - 30 to test with a larger normal tooltip placement.
128  * - 60 to test with a huge tooltip placement.
129  * - 150 to test with a borderline to insanely huge tooltip placement.
130  * - 180 to test with an insanely huge tooltip placement.
131  */
132 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
133 {
134  std::string message = "Hello world.";
135 
136  for(int i = 0; i < 0; ++i) {
137  message += " More greetings.";
138  }
139 
141  gui2::tip::show("tooltip", message, coordinate);
142 
143  handled = true;
144 }
145 #endif
146 
148 {
149  set_click_dismiss(false);
150  set_enter_disabled(true);
151  set_escape_disabled(true);
152 
153 #ifdef DEBUG_TOOLTIP
154  connect_signal<event::SDL_MOUSE_MOTION>(
155  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
157 #endif
158 
159  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
160 
161  //
162  // General hotkeys
163  //
165  std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
166 
168  std::bind(&title_screen::show_achievements, this));
169 
172 
173  // A wrapper is needed here since the relevant display function is overloaded, and
174  // since the wrapper's signature doesn't exactly match what register_hotkey expects.
176 
178 
179  //
180  // Background and logo images
181  //
182  if(game_config::images::game_title.empty()) {
183  ERR_CF << "No title image defined";
184  }
185 
187 
189  ERR_CF << "No title background image defined";
190  }
191 
193 
194  find_widget<image>(this, "logo-bg", false).set_image(game_config::images::game_logo_background);
195  find_widget<image>(this, "logo", false).set_image(game_config::images::game_logo);
196 
197  //
198  // Tip-of-the-day browser
199  //
200  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
201 
202  if(tip_pages != nullptr) {
203  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
204  if(tips.empty()) {
205  WRN_CF << "There are no tips of day available.";
206  }
207  for(const auto& tip : tips) {
209  widget_data page;
210 
211  widget["use_markup"] = "true";
212 
213  widget["label"] = tip.text();
214  page.emplace("tip", widget);
215 
216  widget["label"] = tip.source();
217  page.emplace("source", widget);
218 
219  tip_pages->add_page(page);
220  }
221 
222  update_tip(true);
223  }
224 
226  std::bind(&title_screen::update_tip, this, true));
227 
229  std::bind(&title_screen::update_tip, this, false));
230 
231  //
232  // Help
233  //
234  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
235  if(gui2::new_widgets) {
236  gui2::dialogs::help_browser::display();
237  }
238 
239  help::show_help();
240  });
241 
242  //
243  // About
244  //
245  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
246 
247  //
248  // Campaign
249  //
250  register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
251  try{
252  if(game_.new_campaign()) {
253  // Suspend drawing of the title screen,
254  // so it doesn't flicker in between loading screens.
255  hide();
256  set_retval(LAUNCH_GAME);
257  }
258  } catch (const config::error& e) {
259  gui2::show_error_message(e.what());
260  }
261  });
262 
263  //
264  // Multiplayer
265  //
268 
269  //
270  // Load game
271  //
272  register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
273  if(game_.load_game()) {
274  // Suspend drawing of the title screen,
275  // so it doesn't flicker in between loading screens.
276  hide();
277  set_retval(LAUNCH_GAME);
278  }
279  });
280 
281  //
282  // Addons
283  //
284  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
285  if(manage_addons()) {
287  }
288  });
289 
290  //
291  // Editor
292  //
293  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
294 
295  //
296  // Cores
297  //
299  std::bind(&title_screen::button_callback_cores, this));
300 
301  //
302  // Language
303  //
304  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
305  try {
306  if(game_.change_language()) {
307  on_resize();
308  update_static_labels();
309  }
310  } catch(const std::runtime_error& e) {
311  gui2::show_error_message(e.what());
312  }
313  });
314 
315  //
316  // Preferences
317  //
318  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, [this]() {
319  gui2::dialogs::preferences_dialog::display();
320 
321  // Currently blurred windows don't capture well if there is something
322  // on top of them at the time of blur. Resizing the game window in
323  // preferences will cause the title screen tip and menu panels to
324  // capture the prefs dialog in their blur. This workaround simply
325  // forces them to re-capture the blur after the dialog closes.
326  panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
327  if(tip_panel != nullptr) {
328  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
329  tip_panel->queue_redraw();
330  }
331  panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
332  if(menu_panel != nullptr) {
333  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
334  menu_panel->queue_redraw();
335  }
336  });
337 
338  //
339  // Achievements
340  //
341  register_button(*this, "achievements", hotkey::HOTKEY_ACHIEVEMENTS,
342  std::bind(&title_screen::show_achievements, this));
343 
344  //
345  // Credits
346  //
347  register_button(*this, "credits", hotkey::TITLE_SCREEN__CREDITS, [this]() { set_retval(SHOW_ABOUT); });
348 
349  //
350  // Quit
351  //
352  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
353  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
354  find_widget<button>(this, "quit", false, true);
355 
356  //
357  // Debug clock
358  //
359  register_button(*this, "clock", hotkey::HOTKEY_NULL,
360  std::bind(&title_screen::show_debug_clock_window, this));
361 
362  auto clock = find_widget<button>(this, "clock", false, false);
363  if(clock) {
365  }
366 
367  //
368  // Static labels (version and language)
369  //
371 }
372 
374 {
375  //
376  // Version menu label
377  //
378  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
379 
380  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
381  version_label->set_label(version_string);
382  }
383 
384  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
385 
386  //
387  // Language menu label
388  //
389  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
390  const auto& locale = translation::get_effective_locale_info();
391  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
392  // and strip the charset from the Boost locale identifier.
393  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
394  const auto& langs = get_languages(true);
395 
396  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
397  return lang.localename == boost_name;
398  });
399 
400  if(lang_def != langs.end()) {
401  lang_button->set_label(lang_def->language.str());
402  } else if(boost_name == "c" || boost_name == "C") {
403  // HACK: sometimes System Default doesn't match anything on the list. If you fork
404  // Wesnoth and change the neutral language to something other than US English, you
405  // want to change this too.
406  lang_button->set_label("English (US)");
407  } else {
408  // If somehow the locale doesn't match a known translation, use the
409  // locale identifier as a last resort
410  lang_button->set_label(boost_name);
411  }
412  }
413 }
414 
416 {
418 }
419 
420 void title_screen::update_tip(const bool previous)
421 {
422  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
423  if(tip_pages == nullptr) {
424  return;
425  }
426  if(tip_pages->get_page_count() == 0) {
427  return;
428  }
429 
430  int page = tip_pages->get_selected_page();
431  if(previous) {
432  if(page <= 0) {
433  page = tip_pages->get_page_count();
434  }
435  --page;
436  } else {
437  ++page;
438  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
439  page = 0;
440  }
441  }
442 
443  tip_pages->select_page(page);
444 }
445 
447 {
448  assert(show_debug_clock_button);
449 
450  if(debug_clock_) {
451  debug_clock_.reset(nullptr);
452  } else {
453  debug_clock_.reset(new debug_clock());
454  debug_clock_->show(true);
455  }
456 }
457 
459 {
461 
462  std::vector<std::string> options;
463  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
464  if(!sc["is_unit_test"].to_bool(false)) {
465  options.emplace_back(sc["id"]);
466  }
467  }
468 
469  std::sort(options.begin(), options.end());
470 
471  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
472  dlg.show();
473 
474  int choice = dlg.selected_index();
475  if(choice >= 0) {
476  game_.set_test(options[choice]);
478  }
479 }
480 
482 {
484  ach.show();
485 }
486 
488 {
489  while(true) {
491  dlg.show();
492 
493  if(dlg.get_retval() != gui2::retval::OK) {
494  return;
495  }
496 
497  const auto res = dlg.get_choice();
498 
499  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
500  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
501  continue;
502  }
503  }
504 
505  switch(res) {
506  case decltype(dlg)::choice::JOIN:
509  break;
510  case decltype(dlg)::choice::CONNECT:
513  break;
514  case decltype(dlg)::choice::HOST:
515  game_.select_mp_server("localhost");
517  break;
518  case decltype(dlg)::choice::LOCAL:
520  break;
521  }
522 
523  return;
524  }
525 }
526 
528 {
529  int current = 0;
530 
531  std::vector<config> cores;
532  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
533  cores.push_back(core);
534 
535  if(core["id"] == preferences::core_id()) {
536  current = cores.size() - 1;
537  }
538  }
539 
540  gui2::dialogs::core_selection core_dlg(cores, current);
541  if(core_dlg.show()) {
542  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
543 
546  }
547 }
548 
549 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
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:154
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:564
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:51
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:149
A label displays text that can be wrapped but no scrollbars are provided.
Definition: label.hpp:57
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:44
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:99
int get_selected_page() const
Returns the selected page.
Definition: multi_page.cpp:114
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:105
A panel is a visible container to hold multiple widgets.
Definition: panel.hpp:59
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: panel.cpp:63
canvas & get_canvas(const unsigned index)
Base class for all widgets.
Definition: widget.hpp:54
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:456
@ 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:67
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:331
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:403
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:344
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:420
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:127
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
#define REGISTER_DIALOG(window_id)
Wrapper for REGISTER_DIALOG2.
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:548
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:63
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:81
void remove()
Removes a tip.
Definition: tooltip.cpp:111
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:79
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
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:179
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:35
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
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:204
@ 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:144
@ 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:491
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:356
std::string core_id()
Definition: general.cpp:332
const config & options()
Definition: game.cpp:555
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:578
surface read_pixels(SDL_Rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:584
This file contains the settings handling of the widget library.
Holds a 2D point.
Definition: point.hpp:25
Contains the gui2 timer routines.
#define ERR_CF
#define WRN_CF
static lg::log_domain log_config("config")
#define e
#define b