The Battle for Wesnoth  1.19.10+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, [callback](auto&&...) { callback(); return true; });
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 namespace
106 {
107 void show_lua_console()
108 {
110 }
111 
112 void make_screenshot()
113 {
114  surface screenshot = video::read_pixels();
115  if(screenshot) {
116  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
118  gui2::dialogs::screenshot_notification::display(filename, screenshot);
119  }
120 }
121 
122 } // anon namespace
123 
124 #ifdef DEBUG_TOOLTIP
125 /*
126  * This function is used to test the tooltip placement algorithms as
127  * described in the »Tooltip placement« section in the GUI2 design
128  * document.
129  *
130  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
131  * - 0 to test with a normal tooltip placement.
132  * - 30 to test with a larger normal tooltip placement.
133  * - 60 to test with a huge tooltip placement.
134  * - 150 to test with a borderline to insanely huge tooltip placement.
135  * - 180 to test with an insanely huge tooltip placement.
136  */
137 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
138 {
139  std::string message = "Hello world.";
140 
141  for(int i = 0; i < 0; ++i) {
142  message += " More greetings.";
143  }
144 
146  gui2::tip::show("tooltip", message, coordinate);
147 
148  handled = true;
149 }
150 #endif
151 
153 {
154  set_click_dismiss(false);
155  set_enter_disabled(true);
156  set_escape_disabled(true);
157 
158 #ifdef DEBUG_TOOLTIP
159  connect_signal<event::SDL_MOUSE_MOTION>(
160  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
162 #endif
163 
164  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
165 
166  //
167  // General hotkeys
168  //
170  [this](auto&&...) { set_retval(RELOAD_GAME_DATA); return true; });
171 
173  [this](auto&&...) { hotkey_callback_select_tests(); return true; });
174 
176  [this](auto&&...) { button_callback_cores(); return true; });
177 
179  [](auto&&...) { show_lua_console(); return true; });
180 
181  /** @todo: should eventually become part of global hotkey handling. */
183  [](auto&&...) { make_screenshot(); return true; });
184 
185  //
186  // Background and logo images
187  //
189  // game works just fine if just one of the background images are missing
190  ERR_CF << "No titlescreen background defined in game config";
191  }
192 
195 
196  find_widget<image>("logo-bg").set_image(game_config::images::game_logo_background);
197  find_widget<image>("logo").set_image(game_config::images::game_logo);
198 
199  //
200  // Tip-of-the-day browser
201  //
202  if(auto tip_pages = find_widget<multi_page>("tips", false, false)) {
204  tip_pages->add_page({
205  { "tip", {
206  { "use_markup", "true" },
207  { "label", tip.text }
208  }},
209  { "source", {
210  { "use_markup", "true" },
211  { "label", tip.source }
212  }}
213  });
214  }
215 
216  update_tip(true);
217  }
218 
220  std::bind(&title_screen::update_tip, this, true));
221 
223  std::bind(&title_screen::update_tip, this, false));
224 
225  // Tip panel visiblity and close button
226  panel& tip_panel = find_widget<panel>("tip_panel");
227 
228  tip_panel.set_visible(prefs::get().show_tips()
231 
232  if(auto toggle_tips = find_widget<button>("toggle_tip_panel", false, false)) {
233  connect_signal_mouse_left_click(*toggle_tips, [&tip_panel](auto&&...) {
234  const bool currently_hidden = tip_panel.get_visible() == widget::visibility::hidden;
235 
236  tip_panel.set_visible(currently_hidden
239 
240  // If previously hidden, will now be visible, so we can reuse the same value
241  prefs::get().set_show_tips(currently_hidden);
242  });
243  }
244 
245  //
246  // Help
247  //
248  register_button("help", hotkey::HOTKEY_HELP, []() {
250  help::show_help();
251  });
252 
253  //
254  // About
255  //
256  register_button("about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
257 
258  //
259  // Campaign
260  //
261  register_button("campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
262  try{
263  if(game_.new_campaign()) {
264  // Suspend drawing of the title screen,
265  // so it doesn't flicker in between loading screens.
266  hide();
267  set_retval(LAUNCH_GAME);
268  }
269  } catch (const config::error& e) {
270  gui2::show_error_message(e.what());
271  }
272  });
273 
274  //
275  // Multiplayer
276  //
279 
280  //
281  // Load game
282  //
283  register_button("load", hotkey::HOTKEY_LOAD_GAME, [this]() {
284  if(game_.load_game()) {
285  // Suspend drawing of the title screen,
286  // so it doesn't flicker in between loading screens.
287  hide();
288  set_retval(LAUNCH_GAME);
289  }
290  });
291 
292  //
293  // Addons
294  //
295  register_button("addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
296  if(manage_addons()) {
298  }
299  });
300 
301  //
302  // Editor
303  //
305 
306  //
307  // Language
308  //
309  register_button("language", hotkey::HOTKEY_LANGUAGE, [this]() {
310  try {
311  if(game_.change_language()) {
312  on_resize();
313  update_static_labels();
314  }
315  } catch(const std::runtime_error& e) {
316  gui2::show_error_message(e.what());
317  }
318  });
319 
320  //
321  // Preferences
322  //
324  std::bind(&title_screen::show_preferences, this));
325 
326  //
327  // Achievements
328  //
330  [] { dialogs::achievements_dialog::display(); });
331 
332  //
333  // Community
334  //
336  [] { dialogs::game_version::display(4); }); // shows the 5th tab, community
337 
338  //
339  // Quit
340  //
342  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
343  find_widget<button>("quit", false, true);
344 
345  //
346  // Debug clock
347  //
349  std::bind(&title_screen::show_debug_clock_window, this));
350 
351  auto clock = find_widget<button>("clock", false, false);
352  if(clock) {
353  clock->set_visible(show_debug_clock_button);
354  }
355 
356  //
357  // GUI Test and Debug Window
358  //
359  register_button("test_dialog", hotkey::HOTKEY_NULL,
360  [] { dialogs::gui_test_dialog::display(); });
361 
362  auto test_dialog = find_widget<button>("test_dialog", false, false);
363  if(test_dialog) {
364  test_dialog->set_visible(show_debug_clock_button);
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>("revision_number", false, false)) {
381  version_label->set_label(version_string);
382  }
383 
384  get_canvas(1).set_variable("revision_number", wfl::variant(version_string));
385 
386  //
387  // Language menu label
388  //
389  if(auto* lang_button = find_widget<button>("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>("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  pref_dlg.show();
485  if (pref_dlg.get_retval() == RELOAD_UI) {
487  }
488 
489  // Currently blurred windows don't capture well if there is something
490  // on top of them at the time of blur. Resizing the game window in
491  // preferences will cause the title screen tip and menu panels to
492  // capture the prefs dialog in their blur. This workaround simply
493  // forces them to re-capture the blur after the dialog closes.
494  panel* tip_panel = find_widget<panel>("tip_panel", false, false);
495  if(tip_panel != nullptr) {
496  tip_panel->get_canvas(tip_panel->get_state()).queue_reblur();
497  tip_panel->queue_redraw();
498  }
499  panel* menu_panel = find_widget<panel>("menu_panel", false, false);
500  if(menu_panel != nullptr) {
501  menu_panel->get_canvas(menu_panel->get_state()).queue_reblur();
502  menu_panel->queue_redraw();
503  }
504 }
505 
507 {
508  while(true) {
510  dlg.show();
511 
512  if(dlg.get_retval() != gui2::retval::OK) {
513  return;
514  }
515 
516  const auto res = dlg.get_choice();
517 
518  if(res == decltype(dlg)::choice::HOST && prefs::get().mp_server_warning_disabled() < 2) {
519  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
520  continue;
521  }
522  }
523 
524  switch(res) {
525  case decltype(dlg)::choice::JOIN:
526  game_.select_mp_server(prefs::get().builtin_servers_list().front().address);
528  break;
529  case decltype(dlg)::choice::CONNECT:
532  break;
533  case decltype(dlg)::choice::HOST:
534  game_.select_mp_server("localhost");
536  break;
537  case decltype(dlg)::choice::LOCAL:
539  break;
540  }
541 
542  return;
543  }
544 }
545 
547 {
548  int current = 0;
549 
550  std::vector<config> cores;
551  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
552  cores.push_back(core);
553 
554  if(core["id"] == prefs::get().core()) {
555  current = cores.size() - 1;
556  }
557  }
558 
559  gui2::dialogs::core_selection core_dlg(cores, current);
560  if(core_dlg.show()) {
561  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
562 
563  prefs::get().set_core(core_id);
565  }
566 }
567 
568 } // 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:157
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:625
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
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 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, Func &&function)
Registers a hotkey.
Definition: dispatcher.hpp:444
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:
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:1022
static std::string _(const char *str)
Definition: gettext.hpp:103
#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:125
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:588
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 gui2::tracked_drawable &target)
Displays the fps report popup for the given tracked_drawable.
Definition: fps_report.cpp:131
void remove()
Removes a tip.
Definition: tooltip.cpp:94
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:62
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:163
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:43
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
int show_lua_console(lua_State *, lua_kernel_base *lk)
Definition: lua_gui2.cpp:250
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:572
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