The Battle for Wesnoth  1.17.12+dev
title_screen.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2022
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  // Version string
199  //
200  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
201 
202  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
203  version_label->set_label(version_string);
204  }
205 
206  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
207 
208  //
209  // Tip-of-the-day browser
210  //
211  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
212 
213  if(tip_pages != nullptr) {
214  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
215  if(tips.empty()) {
216  WRN_CF << "There are no tips of day available.";
217  }
218  for(const auto& tip : tips) {
220  widget_data page;
221 
222  widget["use_markup"] = "true";
223 
224  widget["label"] = tip.text();
225  page.emplace("tip", widget);
226 
227  widget["label"] = tip.source();
228  page.emplace("source", widget);
229 
230  tip_pages->add_page(page);
231  }
232 
233  update_tip(true);
234  }
235 
237  std::bind(&title_screen::update_tip, this, true));
238 
240  std::bind(&title_screen::update_tip, this, false));
241 
242  //
243  // Help
244  //
245  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
246  if(gui2::new_widgets) {
247  gui2::dialogs::help_browser::display();
248  }
249 
250  help::show_help();
251  });
252 
253  //
254  // About
255  //
256  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
257 
258  //
259  // Campaign
260  //
261  register_button(*this, "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();
268  }
269  } catch (const config::error& e) {
271  }
272  });
273 
274  //
275  // Multiplayer
276  //
279 
280  //
281  // Load game
282  //
283  register_button(*this, "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();
289  }
290  });
291 
292  //
293  // Addons
294  //
295  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
296  if(manage_addons()) {
298  }
299  });
300 
301  //
302  // Editor
303  //
304  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
305 
306  //
307  // Cores
308  //
310  std::bind(&title_screen::button_callback_cores, this));
311 
312  //
313  // Language
314  //
315  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
316  try {
317  if(game_.change_language()) {
318  on_resize();
319  }
320  } catch(const std::runtime_error& e) {
321  gui2::show_error_message(e.what());
322  }
323  });
324 
325  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
326  const auto& locale = translation::get_effective_locale_info();
327  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
328  // and strip the charset from the Boost locale identifier.
329  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
330  const auto& langs = get_languages(true);
331 
332  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
333  return lang.localename == boost_name;
334  });
335 
336  if(lang_def != langs.end()) {
337  lang_button->set_label(lang_def->language.str());
338  } else if(boost_name == "c" || boost_name == "C") {
339  // HACK: sometimes System Default doesn't match anything on the list. If you fork
340  // Wesnoth and change the neutral language to something other than US English, you
341  // want to change this too.
342  lang_button->set_label("English (US)");
343  } else {
344  // If somehow the locale doesn't match a known translation, use the
345  // locale identifier as a last resort
346  lang_button->set_label(boost_name);
347  }
348  }
349 
350  //
351  // Preferences
352  //
353  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, []() {
354  gui2::dialogs::preferences_dialog::display();
355  });
356 
357  //
358  // Credits
359  //
360  register_button(*this, "credits", hotkey::TITLE_SCREEN__CREDITS, [this]() { set_retval(SHOW_ABOUT); });
361 
362  //
363  // Quit
364  //
365  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
366  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
367  find_widget<button>(this, "quit", false, true);
368 
369  //
370  // Debug clock
371  //
372  register_button(*this, "clock", hotkey::HOTKEY_NULL,
373  std::bind(&title_screen::show_debug_clock_window, this));
374 
375  auto clock = find_widget<button>(this, "clock", false, false);
376  if(clock) {
378  }
379 }
380 
382 {
384 }
385 
386 void title_screen::update_tip(const bool previous)
387 {
388  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
389  if(tip_pages == nullptr) {
390  return;
391  }
392  if(tip_pages->get_page_count() == 0) {
393  return;
394  }
395 
396  int page = tip_pages->get_selected_page();
397  if(previous) {
398  if(page <= 0) {
399  page = tip_pages->get_page_count();
400  }
401  --page;
402  } else {
403  ++page;
404  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
405  page = 0;
406  }
407  }
408 
409  tip_pages->select_page(page);
410 }
411 
413 {
414  assert(show_debug_clock_button);
415 
416  if(debug_clock_) {
417  debug_clock_.reset(nullptr);
418  } else {
419  debug_clock_.reset(new debug_clock());
420  debug_clock_->show(true);
421  }
422 }
423 
425 {
427 
428  std::vector<std::string> options;
429  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
430  if(!sc["is_unit_test"].to_bool(false)) {
431  options.emplace_back(sc["id"]);
432  }
433  }
434 
435  std::sort(options.begin(), options.end());
436 
437  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
438  dlg.show();
439 
440  int choice = dlg.selected_index();
441  if(choice >= 0) {
442  game_.set_test(options[choice]);
444  }
445 }
446 
448 {
450  ach.show();
451 }
452 
454 {
455  while(true) {
457  dlg.show();
458 
459  if(dlg.get_retval() != gui2::retval::OK) {
460  return;
461  }
462 
463  const auto res = dlg.get_choice();
464 
465  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
466  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
467  continue;
468  }
469  }
470 
471  switch(res) {
472  case decltype(dlg)::choice::JOIN:
475  break;
476  case decltype(dlg)::choice::CONNECT:
479  break;
480  case decltype(dlg)::choice::HOST:
481  game_.select_mp_server("localhost");
483  break;
484  case decltype(dlg)::choice::LOCAL:
486  break;
487  }
488 
489  return;
490  }
491 }
492 
494 {
495  int current = 0;
496 
497  std::vector<config> cores;
498  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
499  cores.push_back(core);
500 
501  if(core["id"] == preferences::core_id()) {
502  current = cores.size() - 1;
503  }
504  }
505 
506  gui2::dialogs::core_selection core_dlg(cores, current);
507  if(core_dlg.show()) {
508  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
509 
510  preferences::set_core_id(core_id);
512  }
513 }
514 
515 } // namespace dialogs
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:23
void remove()
Removes a tip.
Definition: tooltip.cpp:111
void show_debug_clock_window()
Shows the debug clock.
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
surface read_pixels(SDL_Rect *r)
Copy back a portion of the render target that is already drawn.
Definition: video.cpp:568
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:490
std::vector< game_tip > tips
Definition: settings.cpp:55
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
void set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:137
Main class to show messages to the user.
Definition: message.hpp:35
void register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:149
#define WRN_CF
std::string game_title
const std::string & id() const
Definition: widget.cpp:111
This file contains the window object, this object is a top level container which has the event manage...
This class implements the title screen.
void set_click_dismiss(const bool click_dismiss)
Definition: window.hpp:375
std::string get_screenshot_dir()
void set_escape_disabled(const bool escape_disabled)
Disable the escape key.
Definition: window.hpp:299
bool change_language()
A label displays a text, the text can be wrapped but no scrollbars are provided.
Definition: label.hpp:57
int get_selected_page() const
Returns the selected page.
Definition: multi_page.cpp:114
static std::string _(const char *str)
Definition: gettext.hpp:93
bool show(const unsigned auto_close_time=0)
Shows the window.
static void launch_lua_console()
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
#define b
This shows a dialog displaying achievements.
const config & options()
Definition: game.cpp:556
static game_config_manager * get()
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:357
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
A simple one-column listbox with OK and Cancel buttons.
This file contains the settings handling of the widget library.
std::string game_title_background
language_list get_languages(bool all)
Return a list of available translations.
Definition: language.cpp:127
void set_test(const std::string &id)
static std::unique_ptr< tooltip > tip
Definition: tooltip.cpp:79
void set_core_id(const std::string &core_id)
Definition: general.cpp:338
void select_mp_server(const std::string &server)
static lg::log_domain log_config("config")
void hide()
Hides the window.
Definition: window.cpp:634
std::string game_logo_background
const char * what() const noexcept
Definition: exceptions.hpp:36
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:232
window * get_window()
Returns a pointer to the dialog&#39;s window.
int mp_server_warning_disabled()
Definition: game.cpp:492
std::size_t i
Definition: function.cpp:968
const std::string revision
void load_game_config_for_create(bool is_mp, bool is_test=false)
Clock to test the draw events.
Definition: debug_clock.hpp:50
Game configuration data as global variables.
Definition: build_info.cpp:60
The user set the widget invisible, that means:
void select_page(const unsigned page, const bool select=true)
Selects a page.
Definition: multi_page.cpp:105
grid & add_page(const widget_item &item)
Adds single page to the grid.
Definition: multi_page.cpp:44
Contains the gui2 timer routines.
Holds a 2D point.
Definition: point.hpp:24
Declarations for File-IO.
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:358
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::string core_id()
Definition: general.cpp:332
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:99
int get_retval() const
Returns the cached window exit code.
The user sets the widget visible, that means:
This shows the dialog to select the kind of MP game the user wants to play.
A multi page is a control that contains several &#39;pages&#39; of which only one is visible.
Definition: multi_page.hpp:49
void update_tip(const bool previous)
Updates the tip of day widget.
Abstract base class for all modal dialogs.
Standard logging facilities (interface).
std::string game_logo
std::unique_ptr< modeless_dialog > debug_clock_
Holds the debug clock dialog.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:204
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:35
This shows the dialog which allows the user to choose which core to play.
#define e
std::vector< game_tip > shuffle(const std::vector< game_tip > &tips)
Shuffles the tips.
Definition: tips.cpp:47
Dialog was closed with the OK button.
Definition: retval.hpp:35
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
canvas & get_canvas(const unsigned index)
static void make_screenshot()
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:66
std::function< void()> btn_callback
static void register_button(window &win, const std::string &id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
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
bool show_debug_clock_button
Do we wish to show the button for the debug clock.
#define ERR_CF
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:590
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:286