The Battle for Wesnoth  1.17.0-dev
title_screen.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2021
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 {
112  surface screenshot = CVideo::get_singleton().getSurface().clone();
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  win.set_retval(LAUNCH_GAME);
263  }
264  } catch (const config::error& e) {
266  }
267  });
268 
269  //
270  // Multiplayer
271  //
274 
275  //
276  // Load game
277  //
278  register_button(win, "load", hotkey::HOTKEY_LOAD_GAME, [this, &win]() {
279  if(game_.load_game()) {
280  win.set_retval(LAUNCH_GAME);
281  }
282  });
283 
284  //
285  // Addons
286  //
287  register_button(win, "addons", hotkey::TITLE_SCREEN__ADDONS, [&win]() {
288  if(manage_addons()) {
289  win.set_retval(RELOAD_GAME_DATA);
290  }
291  });
292 
293  //
294  // Editor
295  //
296  register_button(win, "editor", hotkey::TITLE_SCREEN__EDITOR, [&win]() { win.set_retval(MAP_EDITOR); });
297 
298  //
299  // Cores
300  //
301  win.register_hotkey(hotkey::TITLE_SCREEN__CORES,
302  std::bind(&title_screen::button_callback_cores, this));
303 
304  //
305  // Language
306  //
307  register_button(win, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
308  try {
309  if(game_.change_language()) {
310  on_resize();
311  }
312  } catch(const std::runtime_error& e) {
313  gui2::show_error_message(e.what());
314  }
315  });
316 
317  if(auto* lang_button = find_widget<button>(&win, "language", false, false); lang_button) {
318  const auto& locale = translation::get_effective_locale_info();
319  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
320  // and strip the charset from the Boost locale identifier.
321  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
322  const auto& langs = get_languages(true);
323 
324  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
325  return lang.localename == boost_name;
326  });
327 
328  if(lang_def != langs.end()) {
329  lang_button->set_label(lang_def->language.str());
330  } else if(boost_name == "c" || boost_name == "C") {
331  // HACK: sometimes System Default doesn't match anything on the list. If you fork
332  // Wesnoth and change the neutral language to something other than US English, you
333  // want to change this too.
334  lang_button->set_label("English (US)");
335  } else {
336  // If somehow the locale doesn't match a known translation, use the
337  // locale identifier as a last resort
338  lang_button->set_label(boost_name);
339  }
340  }
341 
342  //
343  // Preferences
344  //
345  register_button(win, "preferences", hotkey::HOTKEY_PREFERENCES, []() {
346  gui2::dialogs::preferences_dialog::display();
347  });
348 
349  //
350  // Credits
351  //
352  register_button(win, "credits", hotkey::TITLE_SCREEN__CREDITS, [&win]() { win.set_retval(SHOW_ABOUT); });
353 
354  //
355  // Quit
356  //
357  register_button(win, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [&win]() { win.set_retval(QUIT_GAME); });
358  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
359  find_widget<button>(&win, "quit", false, true);
360 
361  //
362  // Debug clock
363  //
364  register_button(win, "clock", hotkey::HOTKEY_NULL,
365  std::bind(&title_screen::show_debug_clock_window, this));
366 
367  auto clock = find_widget<button>(&win, "clock", false, false);
368  if(clock) {
370  }
371 }
372 
374 {
376 }
377 
378 void title_screen::update_tip(const bool previous)
379 {
380  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
381  if(tip_pages == nullptr) {
382  return;
383  }
384  if(tip_pages->get_page_count() == 0) {
385  return;
386  }
387 
388  int page = tip_pages->get_selected_page();
389  if(previous) {
390  if(page <= 0) {
391  page = tip_pages->get_page_count();
392  }
393  --page;
394  } else {
395  ++page;
396  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
397  page = 0;
398  }
399  }
400 
401  tip_pages->select_page(page);
402 
403  /**
404  * @todo Look for a proper fix.
405  *
406  * This dirtying is required to avoid the blurring to be rendered wrong.
407  * Not entirely sure why, but since we plan to move to SDL2 that change
408  * will probably fix this issue automatically.
409  */
410  get_window()->set_is_dirty(true);
411 }
412 
414 {
415  assert(show_debug_clock_button);
416 
417  if(debug_clock_) {
418  debug_clock_.reset(nullptr);
419  } else {
420  debug_clock_.reset(new debug_clock());
421  debug_clock_->show(true);
422  }
423 }
424 
426 {
428 
429  std::vector<std::string> options;
430  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
431  if(!sc["is_unit_test"].to_bool(false)) {
432  options.emplace_back(sc["id"]);
433  }
434  }
435 
436  std::sort(options.begin(), options.end());
437 
438  gui2::dialogs::simple_item_selector dlg(_("Choose Test"), "", options);
439  dlg.show();
440 
441  int choice = dlg.selected_index();
442  if(choice >= 0) {
443  game_.set_test(options[choice]);
445  }
446 }
447 
449 {
450  while(true) {
452  dlg.show();
453 
454  if(dlg.get_retval() != gui2::retval::OK) {
455  return;
456  }
457 
458  const auto res = dlg.get_choice();
459 
460  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
461  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
462  continue;
463  }
464  }
465 
466  switch(res) {
467  case decltype(dlg)::choice::JOIN:
470  break;
471  case decltype(dlg)::choice::CONNECT:
474  break;
475  case decltype(dlg)::choice::HOST:
476  game_.select_mp_server("localhost");
478  break;
479  case decltype(dlg)::choice::LOCAL:
481  break;
482  }
483 
484  return;
485  }
486 }
487 
489 {
490  int current = 0;
491 
492  std::vector<config> cores;
493  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
494  cores.push_back(core);
495 
496  if(core["id"] == preferences::core_id()) {
497  current = cores.size() - 1;
498  }
499  }
500 
501  gui2::dialogs::core_selection core_dlg(cores, current);
502  if(core_dlg.show()) {
503  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
504 
505  preferences::set_core_id(core_id);
507  }
508 }
509 
510 } // 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 set_variable(const std::string &key, wfl::variant &&value)
Definition: canvas.hpp:168
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
An SDL resize request, coordinate is the new window size.
Definition: handler.hpp:52
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:49
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
surface & getSurface()
Returns a reference to the framebuffer.
Definition: video.cpp:484
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:569
static game_config_manager * get()
const std::vector< game_config::server_info > & builtin_servers_list()
Definition: game.cpp:370
A simple one-column listbox with OK and Cancel buttons.
This file contains the settings handling of the widget library.
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:172
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:466
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:329
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:229
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
int mp_server_warning_disabled()
Definition: game.cpp:505
std::size_t i
Definition: function.cpp:967
std::enable_if_t< is_general_event(E)> connect_signal(const signal_function &signal, const queue_position position=back_child)
Connect a signal for callback in set_event.
Definition: dispatcher.hpp:506
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:59
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
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
static int sort(lua_State *L)
Definition: ltablib.cpp:397
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::string core_id()
Definition: general.cpp:323
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:61
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.
An SDL mouse motion event.
Definition: handler.hpp:54
#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