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"
35 #include "gui/dialogs/message.hpp"
41 #include "language.hpp"
42 #include "log.hpp"
43 #include "preferences/game.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"
56 #include "video.hpp"
57 
58 #include <algorithm>
59 #include <functional>
60 
61 #include <boost/algorithm/string/erase.hpp>
62 
63 static lg::log_domain log_config("config");
64 #define ERR_CF LOG_STREAM(err, log_config)
65 #define WRN_CF LOG_STREAM(warn, log_config)
66 
67 namespace gui2::dialogs
68 {
69 
70 REGISTER_DIALOG(title_screen)
71 
73 
75  : modal_dialog(window_id())
76  , debug_clock_()
77  , game_(game)
78 {
79  set_allow_plugin_skip(false);
80  init_callbacks();
81 }
82 
84 {
85 }
86 
87 using btn_callback = std::function<void()>;
88 
89 static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
90 {
91  if(hk != hotkey::HOTKEY_NULL) {
92  win.register_hotkey(hk, std::bind(callback));
93  }
94 
95  auto b = find_widget<button>(&win, id, false, false);
96  if(b != nullptr)
97  {
98  connect_signal_mouse_left_click(*b, std::bind(callback));
99  }
100 }
101 
102 static void launch_lua_console()
103 {
105 }
106 
107 static void make_screenshot()
108 {
109  surface screenshot = video::read_pixels();
110  if(screenshot) {
111  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
112  filename = filesystem::get_next_filename(filename, ".jpg");
113  gui2::dialogs::screenshot_notification::display(filename, screenshot);
114  }
115 }
116 
117 #ifdef DEBUG_TOOLTIP
118 /*
119  * This function is used to test the tooltip placement algorithms as
120  * described in the »Tooltip placement« section in the GUI2 design
121  * document.
122  *
123  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
124  * - 0 to test with a normal tooltip placement.
125  * - 30 to test with a larger normal tooltip placement.
126  * - 60 to test with a huge tooltip placement.
127  * - 150 to test with a borderline to insanely huge tooltip placement.
128  * - 180 to test with an insanely huge tooltip placement.
129  */
130 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
131 {
132  std::string message = "Hello world.";
133 
134  for(int i = 0; i < 0; ++i) {
135  message += " More greetings.";
136  }
137 
139  gui2::tip::show("tooltip", message, coordinate);
140 
141  handled = true;
142 }
143 #endif
144 
146 {
147  set_click_dismiss(false);
148  set_enter_disabled(true);
149  set_escape_disabled(true);
150 
151 #ifdef DEBUG_TOOLTIP
152  connect_signal<event::SDL_MOUSE_MOTION>(
153  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
155 #endif
156 
157  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
158 
159  //
160  // General hotkeys
161  //
163  std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
164 
166  std::bind(&title_screen::show_achievements, this));
167 
170 
171  // A wrapper is needed here since the relevant display function is overloaded, and
172  // since the wrapper's signature doesn't exactly match what register_hotkey expects.
174 
176 
177  //
178  // Background and logo images
179  //
180  if(game_config::images::game_title.empty()) {
181  ERR_CF << "No title image defined";
182  }
183 
185 
187  ERR_CF << "No title background image defined";
188  }
189 
191 
192  find_widget<image>(this, "logo-bg", false).set_image(game_config::images::game_logo_background);
193  find_widget<image>(this, "logo", false).set_image(game_config::images::game_logo);
194 
195  //
196  // Tip-of-the-day browser
197  //
198  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
199 
200  if(tip_pages != nullptr) {
201  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
202  if(tips.empty()) {
203  WRN_CF << "There are no tips of day available.";
204  }
205  for(const auto& tip : tips) {
207  widget_data page;
208 
209  widget["use_markup"] = "true";
210 
211  widget["label"] = tip.text();
212  page.emplace("tip", widget);
213 
214  widget["label"] = tip.source();
215  page.emplace("source", widget);
216 
217  tip_pages->add_page(page);
218  }
219 
220  update_tip(true);
221  }
222 
224  std::bind(&title_screen::update_tip, this, true));
225 
227  std::bind(&title_screen::update_tip, this, false));
228 
229  //
230  // Help
231  //
232  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
233  if(gui2::new_widgets) {
234  gui2::dialogs::help_browser::display();
235  }
236 
237  help::show_help();
238  });
239 
240  //
241  // About
242  //
243  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
244 
245  //
246  // Campaign
247  //
248  register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
249  try{
250  if(game_.new_campaign()) {
251  // Suspend drawing of the title screen,
252  // so it doesn't flicker in between loading screens.
253  hide();
254  set_retval(LAUNCH_GAME);
255  }
256  } catch (const config::error& e) {
257  gui2::show_error_message(e.what());
258  }
259  });
260 
261  //
262  // Multiplayer
263  //
266 
267  //
268  // Load game
269  //
270  register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
271  if(game_.load_game()) {
272  // Suspend drawing of the title screen,
273  // so it doesn't flicker in between loading screens.
274  hide();
275  set_retval(LAUNCH_GAME);
276  }
277  });
278 
279  //
280  // Addons
281  //
282  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
283  if(manage_addons()) {
285  }
286  });
287 
288  //
289  // Editor
290  //
291  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
292 
293  //
294  // Cores
295  //
297  std::bind(&title_screen::button_callback_cores, this));
298 
299  //
300  // Language
301  //
302  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
303  try {
304  if(game_.change_language()) {
305  on_resize();
306  update_static_labels();
307  }
308  } catch(const std::runtime_error& e) {
309  gui2::show_error_message(e.what());
310  }
311  });
312 
313  //
314  // Preferences
315  //
316  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, [this]() {
317  gui2::dialogs::preferences_dialog::display();
318 
319  // Currently blurred windows don't capture well if there is something
320  // on top of them at the time of blur. Resizing the game window in
321  // preferences will cause the title screen tip and menu panels to
322  // capture the prefs dialog in their blur. This workaround simply
323  // forces them to re-capture the blur after the dialog closes.
324  panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
325  if(tip_panel != nullptr) {
326  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
327  tip_panel->queue_redraw();
328  }
329  panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
330  if(menu_panel != nullptr) {
331  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
332  menu_panel->queue_redraw();
333  }
334  });
335 
336  //
337  // Achievements
338  //
339  register_button(*this, "achievements", hotkey::HOTKEY_ACHIEVEMENTS,
340  std::bind(&title_screen::show_achievements, this));
341 
342  //
343  // Community
344  //
345  register_button(*this, "community", hotkey::HOTKEY_NULL,
346  std::bind(&title_screen::show_community, this));
347 
348  //
349  // Credits
350  //
351  register_button(*this, "credits", hotkey::TITLE_SCREEN__CREDITS, [this]() { set_retval(SHOW_ABOUT); });
352 
353  //
354  // Quit
355  //
356  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
357  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
358  find_widget<button>(this, "quit", false, true);
359 
360  //
361  // Debug clock
362  //
363  register_button(*this, "clock", hotkey::HOTKEY_NULL,
364  std::bind(&title_screen::show_debug_clock_window, this));
365 
366  auto clock = find_widget<button>(this, "clock", false, false);
367  if(clock) {
369  }
370 
371  //
372  // Static labels (version and language)
373  //
375 }
376 
378 {
379  //
380  // Version menu label
381  //
382  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
383 
384  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
385  version_label->set_label(version_string);
386  }
387 
388  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
389 
390  //
391  // Language menu label
392  //
393  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
394  const auto& locale = translation::get_effective_locale_info();
395  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
396  // and strip the charset from the Boost locale identifier.
397  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
398  const auto& langs = get_languages(true);
399 
400  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
401  return lang.localename == boost_name;
402  });
403 
404  if(lang_def != langs.end()) {
405  lang_button->set_label(lang_def->language.str());
406  } else if(boost_name == "c" || boost_name == "C") {
407  // HACK: sometimes System Default doesn't match anything on the list. If you fork
408  // Wesnoth and change the neutral language to something other than US English, you
409  // want to change this too.
410  lang_button->set_label("English (US)");
411  } else {
412  // If somehow the locale doesn't match a known translation, use the
413  // locale identifier as a last resort
414  lang_button->set_label(boost_name);
415  }
416  }
417 }
418 
420 {
422 }
423 
424 void title_screen::update_tip(const bool previous)
425 {
426  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
427  if(tip_pages == nullptr) {
428  return;
429  }
430  if(tip_pages->get_page_count() == 0) {
431  return;
432  }
433 
434  int page = tip_pages->get_selected_page();
435  if(previous) {
436  if(page <= 0) {
437  page = tip_pages->get_page_count();
438  }
439  --page;
440  } else {
441  ++page;
442  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
443  page = 0;
444  }
445  }
446 
447  tip_pages->select_page(page);
448 }
449 
451 {
452  assert(show_debug_clock_button);
453 
454  if(debug_clock_) {
455  debug_clock_.reset(nullptr);
456  } else {
457  debug_clock_.reset(new debug_clock());
458  debug_clock_->show(true);
459  }
460 }
461 
463 {
465 
466  std::vector<std::string> options;
467  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
468  if(!sc["is_unit_test"].to_bool(false)) {
469  options.emplace_back(sc["id"]);
470  }
471  }
472 
473  std::sort(options.begin(), options.end());
474 
475  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
476  dlg.show();
477 
478  int choice = dlg.selected_index();
479  if(choice >= 0) {
480  game_.set_test(options[choice]);
482  }
483 }
484 
486 {
488  ach.show();
489 }
490 
492 {
493  community_dialog dlg;
494  dlg.show();
495 }
496 
498 {
499  while(true) {
501  dlg.show();
502 
503  if(dlg.get_retval() != gui2::retval::OK) {
504  return;
505  }
506 
507  const auto res = dlg.get_choice();
508 
509  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
510  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
511  continue;
512  }
513  }
514 
515  switch(res) {
516  case decltype(dlg)::choice::JOIN:
519  break;
520  case decltype(dlg)::choice::CONNECT:
523  break;
524  case decltype(dlg)::choice::HOST:
525  game_.select_mp_server("localhost");
527  break;
528  case decltype(dlg)::choice::LOCAL:
530  break;
531  }
532 
533  return;
534  }
535 }
536 
538 {
539  int current = 0;
540 
541  std::vector<config> cores;
542  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
543  cores.push_back(core);
544 
545  if(core["id"] == preferences::core_id()) {
546  current = cores.size() - 1;
547  }
548  }
549 
550  gui2::dialogs::core_selection core_dlg(cores, current);
551  if(core_dlg.show()) {
552  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
553 
556  }
557 }
558 
559 } // 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 a dialog displaying community links.
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:546
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.
REGISTER_DIALOG(editor_edit_unit)
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: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