The Battle for Wesnoth  1.19.2+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 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  //
183  // game works just fine if just one of the background images are missing
184  ERR_CF << "No titlescreen background defined in game config";
185  }
186 
189 
190  find_widget<image>(this, "logo-bg", false).set_image(game_config::images::game_logo_background);
191  find_widget<image>(this, "logo", false).set_image(game_config::images::game_logo);
192 
193  //
194  // Tip-of-the-day browser
195  //
196  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
197 
198  if(tip_pages != nullptr) {
199  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
200  if(tips.empty()) {
201  WRN_CF << "There are no tips of day available.";
202  }
203  for(const auto& tip : tips) {
205  widget_data page;
206 
207  widget["use_markup"] = "true";
208 
209  // Use pango markup to insert drop cap
210  // Example: Lawful units -> <span ...>L</span>awful units
211  // If tip starts with a tag, we need to insert the <span> after it
212  // then insert the </span> tag after the first character of the text
213  // after markup. Assumes that the tags themselves don't
214  // contain non-ASCII characters.
215  // Example: <i>Lawful</i> units -> <i><span ...>L</span>awful</i> units
216  const std::string& script_font = font::get_font_families(font::FONT_SCRIPT);
217  std::string tip_text = tip.text().str();
218  std::size_t pos = 0;
219  while (pos < tip_text.size() && tip_text.at(pos) == '<') {
220  pos = tip_text.find_first_of(">", pos) + 1;
221  }
222  utf8::insert(tip_text, pos+1, "</span>");
223  utf8::insert(tip_text, pos, "<span font_family='" + script_font + "' font_size='xx-large'>");
224 
225  widget["label"] = tip_text;
226 
227  page.emplace("tip", widget);
228 
229  widget["label"] = tip.source();
230  page.emplace("source", widget);
231 
232  tip_pages->add_page(page);
233  }
234 
235  update_tip(true);
236  }
237 
239  std::bind(&title_screen::update_tip, this, true));
240 
242  std::bind(&title_screen::update_tip, this, false));
243 
244  //
245  // Help
246  //
247  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
248  if(gui2::new_widgets) {
249  gui2::dialogs::help_browser::display();
250  }
251 
252  help::show_help();
253  });
254 
255  //
256  // About
257  //
258  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
259 
260  //
261  // Campaign
262  //
263  register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
264  try{
265  if(game_.new_campaign()) {
266  // Suspend drawing of the title screen,
267  // so it doesn't flicker in between loading screens.
268  hide();
269  set_retval(LAUNCH_GAME);
270  }
271  } catch (const config::error& e) {
272  gui2::show_error_message(e.what());
273  }
274  });
275 
276  //
277  // Multiplayer
278  //
281 
282  //
283  // Load game
284  //
285  register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
286  if(game_.load_game()) {
287  // Suspend drawing of the title screen,
288  // so it doesn't flicker in between loading screens.
289  hide();
290  set_retval(LAUNCH_GAME);
291  }
292  });
293 
294  //
295  // Addons
296  //
297  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
298  if(manage_addons()) {
300  }
301  });
302 
303  //
304  // Editor
305  //
306  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
307 
308  //
309  // Cores
310  //
312  std::bind(&title_screen::button_callback_cores, this));
313 
314  //
315  // Language
316  //
317  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
318  try {
319  if(game_.change_language()) {
320  on_resize();
321  update_static_labels();
322  }
323  } catch(const std::runtime_error& e) {
324  gui2::show_error_message(e.what());
325  }
326  });
327 
328  //
329  // Preferences
330  //
331  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, [this]() {
332  gui2::dialogs::preferences_dialog::display();
333 
334  // Currently blurred windows don't capture well if there is something
335  // on top of them at the time of blur. Resizing the game window in
336  // preferences will cause the title screen tip and menu panels to
337  // capture the prefs dialog in their blur. This workaround simply
338  // forces them to re-capture the blur after the dialog closes.
339  panel* tip_panel = find_widget<panel>(this, "tip_panel", false, false);
340  if(tip_panel != nullptr) {
341  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
342  tip_panel->queue_redraw();
343  }
344  panel* menu_panel = find_widget<panel>(this, "menu_panel", false, false);
345  if(menu_panel != nullptr) {
346  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
347  menu_panel->queue_redraw();
348  }
349  });
350 
351  //
352  // Achievements
353  //
354  register_button(*this, "achievements", hotkey::HOTKEY_ACHIEVEMENTS,
355  std::bind(&title_screen::show_achievements, this));
356 
357  //
358  // Community
359  //
360  register_button(*this, "community", hotkey::HOTKEY_NULL,
361  std::bind(&title_screen::show_community, this));
362 
363  //
364  // Quit
365  //
366  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
367  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
368  find_widget<button>(this, "quit", false, true);
369 
370  //
371  // Debug clock
372  //
373  register_button(*this, "clock", hotkey::HOTKEY_NULL,
374  std::bind(&title_screen::show_debug_clock_window, this));
375 
376  auto clock = find_widget<button>(this, "clock", false, false);
377  if(clock) {
379  }
380 
381  //
382  // GUI Test and Debug Window
383  //
384  register_button(*this, "test_dialog", hotkey::HOTKEY_NULL,
385  std::bind(&title_screen::show_gui_test_dialog, this));
386 
387  auto test_dialog = find_widget<button>(this, "test_dialog", false, false);
388  if(test_dialog) {
390  }
391 
392  //
393  // Static labels (version and language)
394  //
396 }
397 
399 {
400  //
401  // Version menu label
402  //
403  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
404 
405  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
406  version_label->set_label(version_string);
407  }
408 
409  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
410 
411  //
412  // Language menu label
413  //
414  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
415  const auto& locale = translation::get_effective_locale_info();
416  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
417  // and strip the charset from the Boost locale identifier.
418  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
419  const auto& langs = get_languages(true);
420 
421  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
422  return lang.localename == boost_name;
423  });
424 
425  if(lang_def != langs.end()) {
426  lang_button->set_label(lang_def->language.str());
427  } else if(boost_name == "c" || boost_name == "C") {
428  // HACK: sometimes System Default doesn't match anything on the list. If you fork
429  // Wesnoth and change the neutral language to something other than US English, you
430  // want to change this too.
431  lang_button->set_label("English (US)");
432  } else {
433  // If somehow the locale doesn't match a known translation, use the
434  // locale identifier as a last resort
435  lang_button->set_label(boost_name);
436  }
437  }
438 }
439 
441 {
443 }
444 
445 void title_screen::update_tip(const bool previous)
446 {
447  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
448  if(tip_pages == nullptr) {
449  return;
450  }
451  if(tip_pages->get_page_count() == 0) {
452  return;
453  }
454 
455  int page = tip_pages->get_selected_page();
456  if(previous) {
457  if(page <= 0) {
458  page = tip_pages->get_page_count();
459  }
460  --page;
461  } else {
462  ++page;
463  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
464  page = 0;
465  }
466  }
467 
468  tip_pages->select_page(page);
469 }
470 
472 {
473  assert(show_debug_clock_button);
474 
475  if(debug_clock_) {
476  debug_clock_.reset(nullptr);
477  } else {
478  debug_clock_.reset(new debug_clock());
479  debug_clock_->show(true);
480  }
481 }
482 
484 {
485  gui2::dialogs::gui_test_dialog::execute();
486 }
487 
489 {
491 
492  std::vector<std::string> options;
493  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
494  if(!sc["is_unit_test"].to_bool(false)) {
495  options.emplace_back(sc["id"]);
496  }
497  }
498 
499  std::sort(options.begin(), options.end());
500 
501  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
502  dlg.show();
503 
504  int choice = dlg.selected_index();
505  if(choice >= 0) {
506  game_.set_test(options[choice]);
508  }
509 }
510 
512 {
514  ach.show();
515 }
516 
518 {
519  game_version dlg;
520  // shows the 5th tab, community, when the dialog is shown
521  dlg.display(4);
522 }
523 
525 {
526  while(true) {
528  dlg.show();
529 
530  if(dlg.get_retval() != gui2::retval::OK) {
531  return;
532  }
533 
534  const auto res = dlg.get_choice();
535 
536  if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
537  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
538  continue;
539  }
540  }
541 
542  switch(res) {
543  case decltype(dlg)::choice::JOIN:
544  game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
546  break;
547  case decltype(dlg)::choice::CONNECT:
550  break;
551  case decltype(dlg)::choice::HOST:
552  game_.select_mp_server("localhost");
554  break;
555  case decltype(dlg)::choice::LOCAL:
557  break;
558  }
559 
560  return;
561  }
562 }
563 
565 {
566  int current = 0;
567 
568  std::vector<config> cores;
569  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
570  cores.push_back(core);
571 
572  if(core["id"] == prefs::get().core_id()) {
573  current = cores.size() - 1;
574  }
575  }
576 
577  gui2::dialogs::core_selection core_dlg(cores, current);
578  if(core_dlg.show()) {
579  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
580 
581  prefs::get().set_core_id(core_id);
583  }
584 }
585 
586 } // 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: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 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 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
void set_core_id(const std::string &root)
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: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:551
@ 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 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__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.
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