The Battle for Wesnoth  1.17.4+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"
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"
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  : debug_clock_()
78  , game_(game)
79 {
80  set_restore(false);
81 
82  // Need to set this in the constructor, pre_show() / post_build() is too late
83  set_allow_plugin_skip(false);
84 }
85 
87 {
88 }
89 
90 using btn_callback = std::function<void()>;
91 
92 static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
93 {
94  if(hk != hotkey::HOTKEY_NULL) {
95  win.register_hotkey(hk, std::bind(callback));
96  }
97 
98  auto b = find_widget<button>(&win, id, false, false);
99  if(b != nullptr)
100  {
101  connect_signal_mouse_left_click(*b, std::bind(callback));
102  }
103 }
104 
105 static void launch_lua_console()
106 {
108 }
109 
110 static void make_screenshot()
111 {
113  if(screenshot) {
114  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
115  filename = filesystem::get_next_filename(filename, ".jpg");
116  gui2::dialogs::screenshot_notification::display(filename, screenshot);
117  }
118 }
119 
120 #ifdef DEBUG_TOOLTIP
121 /*
122  * This function is used to test the tooltip placement algorithms as
123  * described in the »Tooltip placement« section in the GUI2 design
124  * document.
125  *
126  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
127  * - 0 to test with a normal tooltip placement.
128  * - 30 to test with a larger normal tooltip placement.
129  * - 60 to test with a huge tooltip placement.
130  * - 150 to test with a borderline to insanely huge tooltip placement.
131  * - 180 to test with an insanely huge tooltip placement.
132  */
133 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
134 {
135  std::string message = "Hello world.";
136 
137  for(int i = 0; i < 0; ++i) {
138  message += " More greetings.";
139  }
140 
142  gui2::tip::show("tooltip", message, coordinate);
143 
144  handled = true;
145 }
146 #endif
147 
149 {
150  win.set_click_dismiss(false);
151  win.set_enter_disabled(true);
152  win.set_escape_disabled(true);
153 
154 #ifdef DEBUG_TOOLTIP
156  std::bind(debug_tooltip, std::ref(win), std::placeholders::_3, std::placeholders::_5),
158 #endif
159 
161 
162  //
163  // General hotkeys
164  //
166  std::bind(&gui2::window::set_retval, std::ref(win), RELOAD_GAME_DATA, true));
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" << std::endl;
182  }
183 
185 
187  ERR_CF << "No title background image defined" << std::endl;
188  }
189 
191 
192  find_widget<image>(&win, "logo-bg", false).set_image(game_config::images::game_logo_background);
193  find_widget<image>(&win, "logo", false).set_image(game_config::images::game_logo);
194 
195  //
196  // Version string
197  //
198  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
199 
200  if(label* version_label = find_widget<label>(&win, "revision_number", false, false)) {
201  version_label->set_label(version_string);
202  }
203 
204  win.get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
205 
206  //
207  // Tip-of-the-day browser
208  //
209  multi_page* tip_pages = find_widget<multi_page>(&win, "tips", false, false);
210 
211  if(tip_pages != nullptr) {
212  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
213  if(tips.empty()) {
214  WRN_CF << "There are no tips of day available." << std::endl;
215  }
216  for(const auto& tip : tips) {
218  std::map<std::string, string_map> page;
219 
220  widget["use_markup"] = "true";
221 
222  widget["label"] = tip.text();
223  page.emplace("tip", widget);
224 
225  widget["label"] = tip.source();
226  page.emplace("source", widget);
227 
228  tip_pages->add_page(page);
229  }
230 
231  update_tip(true);
232  }
233 
235  std::bind(&title_screen::update_tip, this, true));
236 
238  std::bind(&title_screen::update_tip, this, false));
239 
240  //
241  // Help
242  //
243  register_button(win, "help", hotkey::HOTKEY_HELP, []() {
244  if(gui2::new_widgets) {
245  gui2::dialogs::help_browser::display();
246  }
247 
248  help::show_help();
249  });
250 
251  //
252  // About
253  //
254  register_button(win, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
255 
256  //
257  // Campaign
258  //
259  register_button(win, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this, &win]() {
260  try{
261  if(game_.new_campaign()) {
262  // Suspend drawing of the title screen,
263  // so it doesn't flicker in between loading screens.
264  win.set_suspend_drawing(true);
265  win.set_retval(LAUNCH_GAME);
266  }
267  } catch (const config::error& e) {
269  }
270  });
271 
272  //
273  // Multiplayer
274  //
277 
278  //
279  // Load game
280  //
281  register_button(win, "load", hotkey::HOTKEY_LOAD_GAME, [this, &win]() {
282  if(game_.load_game()) {
283  // Suspend drawing of the title screen,
284  // so it doesn't flicker in between loading screens.
285  win.set_suspend_drawing(true);
286  win.set_retval(LAUNCH_GAME);
287  }
288  });
289 
290  //
291  // Addons
292  //
293  register_button(win, "addons", hotkey::TITLE_SCREEN__ADDONS, [&win]() {
294  if(manage_addons()) {
295  win.set_retval(RELOAD_GAME_DATA);
296  }
297  });
298 
299  //
300  // Editor
301  //
302  register_button(win, "editor", hotkey::TITLE_SCREEN__EDITOR, [&win]() { win.set_retval(MAP_EDITOR); });
303 
304  //
305  // Cores
306  //
307  win.register_hotkey(hotkey::TITLE_SCREEN__CORES,
308  std::bind(&title_screen::button_callback_cores, this));
309 
310  //
311  // Language
312  //
313  register_button(win, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
314  try {
315  if(game_.change_language()) {
316  on_resize();
317  }
318  } catch(const std::runtime_error& e) {
319  gui2::show_error_message(e.what());
320  }
321  });
322 
323  if(auto* lang_button = find_widget<button>(&win, "language", false, false); lang_button) {
324  const auto& locale = translation::get_effective_locale_info();
325  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
326  // and strip the charset from the Boost locale identifier.
327  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
328  const auto& langs = get_languages(true);
329 
330  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
331  return lang.localename == boost_name;
332  });
333 
334  if(lang_def != langs.end()) {
335  lang_button->set_label(lang_def->language.str());
336  } else if(boost_name == "c" || boost_name == "C") {
337  // HACK: sometimes System Default doesn't match anything on the list. If you fork
338  // Wesnoth and change the neutral language to something other than US English, you
339  // want to change this too.
340  lang_button->set_label("English (US)");
341  } else {
342  // If somehow the locale doesn't match a known translation, use the
343  // locale identifier as a last resort
344  lang_button->set_label(boost_name);
345  }
346  }
347 
348  //
349  // Preferences
350  //
351  register_button(win, "preferences", hotkey::HOTKEY_PREFERENCES, []() {
352  gui2::dialogs::preferences_dialog::display();
353  });
354 
355  //
356  // Credits
357  //
358  register_button(win, "credits", hotkey::TITLE_SCREEN__CREDITS, [&win]() { win.set_retval(SHOW_ABOUT); });
359 
360  //
361  // Quit
362  //
363  register_button(win, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [&win]() { win.set_retval(QUIT_GAME); });
364  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
365  find_widget<button>(&win, "quit", false, true);
366 
367  //
368  // Debug clock
369  //
370  register_button(win, "clock", hotkey::HOTKEY_NULL,
371  std::bind(&title_screen::show_debug_clock_window, this));
372 
373  auto clock = find_widget<button>(&win, "clock", false, false);
374  if(clock) {
376  }
377 }
378 
380 {
382 }
383 
384 void title_screen::update_tip(const bool previous)
385 {
386  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
387  if(tip_pages == nullptr) {
388  return;
389  }
390  if(tip_pages->get_page_count() == 0) {
391  return;
392  }
393 
394  int page = tip_pages->get_selected_page();
395  if(previous) {
396  if(page <= 0) {
397  page = tip_pages->get_page_count();
398  }
399  --page;
400  } else {
401  ++page;
402  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
403  page = 0;
404  }
405  }
406 
407  tip_pages->select_page(page);
408 
409  /**
410  * @todo Look for a proper fix.
411  *
412  * This dirtying is required to avoid the blurring to be rendered wrong.
413  * Not entirely sure why, but since we plan to move to SDL2 that change
414  * will probably fix this issue automatically.
415  */
416  get_window()->set_is_dirty(true);
417 }
418 
420 {
421  assert(show_debug_clock_button);
422 
423  if(debug_clock_) {
424  debug_clock_.reset(nullptr);
425  } else {
426  debug_clock_.reset(new debug_clock());
427  debug_clock_->show(true);
428  }
429 }
430 
432 {
434 
435  std::vector<std::string> options;
436  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
437  if(!sc["is_unit_test"].to_bool(false)) {
438  options.emplace_back(sc["id"]);
439  }
440  }
441 
442  std::sort(options.begin(), options.end());
443 
444  gui2::dialogs::simple_item_selector dlg(_("Choose Test"), "", options);
445  dlg.show();
446 
447  int choice = dlg.selected_index();
448  if(choice >= 0) {
449  game_.set_test(options[choice]);
451  }
452 }
453 
455 {
456  while(true) {
458  dlg.show();
459 
460  if(dlg.get_retval() != gui2::retval::OK) {
461  return;
462  }
463 
464  const auto res = dlg.get_choice();
465 
466  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
467  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
468  continue;
469  }
470  }
471 
472  switch(res) {
473  case decltype(dlg)::choice::JOIN:
476  break;
477  case decltype(dlg)::choice::CONNECT:
480  break;
481  case decltype(dlg)::choice::HOST:
482  game_.select_mp_server("localhost");
484  break;
485  case decltype(dlg)::choice::LOCAL:
487  break;
488  }
489 
490  return;
491  }
492 }
493 
495 {
496  int current = 0;
497 
498  std::vector<config> cores;
499  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
500  cores.push_back(core);
501 
502  if(core["id"] == preferences::core_id()) {
503  current = cores.size() - 1;
504  }
505  }
506 
507  gui2::dialogs::core_selection core_dlg(cores, current);
508  if(core_dlg.show()) {
509  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
510 
511  preferences::set_core_id(core_id);
513  }
514 }
515 
516 } // namespace dialogs
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:25
void remove()
Removes a tip.
Definition: tooltip.cpp:175
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
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:50
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:172
void set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:167
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:142
grid & add_page(const string_map &item)
Adds single page to the grid.
Definition: multi_page.cpp:44
#define WRN_CF
std::string game_title
This file contains the window object, this object is a top level container which has the event manage...
Base class for all widgets.
Definition: widget.hpp:49
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
static CVideo & get_singleton()
Definition: video.hpp:52
window * get_window() const
Returns a pointer to the dialog&#39;s window.
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:116
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()
surface clone() const
Makes a copy of this surface.
Definition: surface.cpp:63
static void display(lua_kernel_base *lk)
Display a new console, using given video and lua kernel.
#define b
const config & options()
Definition: game.cpp:562
static game_config_manager * get()
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:363
A simple one-column listbox with OK and Cancel buttons.
This file contains the settings handling of the widget library.
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:467
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)
void set_core_id(const std::string &core_id)
Definition: general.cpp:335
void select_mp_server(const std::string &server)
static lg::log_domain log_config("config")
std::string game_logo_background
const char * what() const noexcept
Definition: exceptions.hpp:36
static tooltip & tip()
Definition: tooltip.cpp:130
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:231
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
int mp_server_warning_disabled()
Definition: game.cpp:498
std::size_t i
Definition: function.cpp:967
surface & getDrawingSurface()
Returns a reference to the drawing surface.
Definition: video.cpp:607
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:49
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:107
void connect_signal(const F &func, const queue_position position=back_child)
Adds a callback to the appropriate queue based on event type.
Definition: dispatcher.hpp:342
Contains the gui2 timer routines.
std::map< std::string, t_string > string_map
Definition: widget.hpp:26
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:329
unsigned get_page_count() const
Returns the number of pages.
Definition: multi_page.cpp:101
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.
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:206
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
virtual void pre_show(window &window) override
Actions to be taken before showing the window.
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()
void set_retval(int retval)
Convenience wrapper to set the window&#39;s exit code.
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:65
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:140
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:571
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:286