The Battle for Wesnoth  1.17.8+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"
55 #include "sdl/surface.hpp"
56 #include "sdl/utils.hpp"
57 #include "video.hpp"
58 
59 #include <algorithm>
60 #include <functional>
61 
62 #include <boost/algorithm/string/erase.hpp>
63 
64 static lg::log_domain log_config("config");
65 #define ERR_CF LOG_STREAM(err, log_config)
66 #define WRN_CF LOG_STREAM(warn, log_config)
67 
68 namespace gui2::dialogs
69 {
70 
71 REGISTER_DIALOG(title_screen)
72 
74 
76  : modal_dialog(window_id())
77  , debug_clock_()
78  , game_(game)
79 {
80  set_allow_plugin_skip(false);
81  init_callbacks();
82 }
83 
85 {
86 }
87 
88 using btn_callback = std::function<void()>;
89 
90 static void register_button(window& win, const std::string& id, hotkey::HOTKEY_COMMAND hk, btn_callback callback)
91 {
92  if(hk != hotkey::HOTKEY_NULL) {
93  win.register_hotkey(hk, std::bind(callback));
94  }
95 
96  auto b = find_widget<button>(&win, id, false, false);
97  if(b != nullptr)
98  {
99  connect_signal_mouse_left_click(*b, std::bind(callback));
100  }
101 }
102 
103 static void launch_lua_console()
104 {
106 }
107 
108 static void make_screenshot()
109 {
110  surface screenshot = video::read_pixels();
111  if(screenshot) {
112  std::string filename = filesystem::get_screenshot_dir() + "/" + _("Screenshot") + "_";
113  filename = filesystem::get_next_filename(filename, ".jpg");
114  gui2::dialogs::screenshot_notification::display(filename, screenshot);
115  }
116 }
117 
118 #ifdef DEBUG_TOOLTIP
119 /*
120  * This function is used to test the tooltip placement algorithms as
121  * described in the »Tooltip placement« section in the GUI2 design
122  * document.
123  *
124  * Use a 1024 x 768 screen size, set the maximum loop iteration to:
125  * - 0 to test with a normal tooltip placement.
126  * - 30 to test with a larger normal tooltip placement.
127  * - 60 to test with a huge tooltip placement.
128  * - 150 to test with a borderline to insanely huge tooltip placement.
129  * - 180 to test with an insanely huge tooltip placement.
130  */
131 static void debug_tooltip(window& /*window*/, bool& handled, const point& coordinate)
132 {
133  std::string message = "Hello world.";
134 
135  for(int i = 0; i < 0; ++i) {
136  message += " More greetings.";
137  }
138 
140  gui2::tip::show("tooltip", message, coordinate);
141 
142  handled = true;
143 }
144 #endif
145 
147 {
148  set_click_dismiss(false);
149  set_enter_disabled(true);
150  set_escape_disabled(true);
151 
152 #ifdef DEBUG_TOOLTIP
153  connect_signal<event::SDL_MOUSE_MOTION>(
154  std::bind(debug_tooltip, std::ref(*this), std::placeholders::_3, std::placeholders::_5),
156 #endif
157 
158  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(&title_screen::on_resize, this));
159 
160  //
161  // General hotkeys
162  //
164  std::bind(&gui2::window::set_retval, std::ref(*this), RELOAD_GAME_DATA, true));
165 
168 
169  // A wrapper is needed here since the relevant display function is overloaded, and
170  // since the wrapper's signature doesn't exactly match what register_hotkey expects.
172 
174 
175  //
176  // Background and logo images
177  //
178  if(game_config::images::game_title.empty()) {
179  ERR_CF << "No title image defined";
180  }
181 
183 
185  ERR_CF << "No title background image defined";
186  }
187 
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  // Version string
195  //
196  const std::string& version_string = VGETTEXT("Version $version", {{ "version", game_config::revision }});
197 
198  if(label* version_label = find_widget<label>(this, "revision_number", false, false)) {
199  version_label->set_label(version_string);
200  }
201 
202  get_canvas(0).set_variable("revision_number", wfl::variant(version_string));
203 
204  //
205  // Tip-of-the-day browser
206  //
207  multi_page* tip_pages = find_widget<multi_page>(this, "tips", false, false);
208 
209  if(tip_pages != nullptr) {
210  std::vector<game_tip> tips = tip_of_the_day::shuffle(settings::tips);
211  if(tips.empty()) {
212  WRN_CF << "There are no tips of day available.";
213  }
214  for(const auto& tip : tips) {
216  widget_data page;
217 
218  widget["use_markup"] = "true";
219 
220  widget["label"] = tip.text();
221  page.emplace("tip", widget);
222 
223  widget["label"] = tip.source();
224  page.emplace("source", widget);
225 
226  tip_pages->add_page(page);
227  }
228 
229  update_tip(true);
230  }
231 
233  std::bind(&title_screen::update_tip, this, true));
234 
236  std::bind(&title_screen::update_tip, this, false));
237 
238  //
239  // Help
240  //
241  register_button(*this, "help", hotkey::HOTKEY_HELP, []() {
242  if(gui2::new_widgets) {
243  gui2::dialogs::help_browser::display();
244  }
245 
246  help::show_help();
247  });
248 
249  //
250  // About
251  //
252  register_button(*this, "about", hotkey::HOTKEY_NULL, std::bind(&game_version::display<>));
253 
254  //
255  // Campaign
256  //
257  register_button(*this, "campaign", hotkey::TITLE_SCREEN__CAMPAIGN, [this]() {
258  try{
259  if(game_.new_campaign()) {
260  // Suspend drawing of the title screen,
261  // so it doesn't flicker in between loading screens.
262  hide();
264  }
265  } catch (const config::error& e) {
267  }
268  });
269 
270  //
271  // Multiplayer
272  //
275 
276  //
277  // Load game
278  //
279  register_button(*this, "load", hotkey::HOTKEY_LOAD_GAME, [this]() {
280  if(game_.load_game()) {
281  // Suspend drawing of the title screen,
282  // so it doesn't flicker in between loading screens.
283  hide();
285  }
286  });
287 
288  //
289  // Addons
290  //
291  register_button(*this, "addons", hotkey::TITLE_SCREEN__ADDONS, [this]() {
292  if(manage_addons()) {
294  }
295  });
296 
297  //
298  // Editor
299  //
300  register_button(*this, "editor", hotkey::TITLE_SCREEN__EDITOR, [this]() { set_retval(MAP_EDITOR); });
301 
302  //
303  // Cores
304  //
306  std::bind(&title_screen::button_callback_cores, this));
307 
308  //
309  // Language
310  //
311  register_button(*this, "language", hotkey::HOTKEY_LANGUAGE, [this]() {
312  try {
313  if(game_.change_language()) {
314  on_resize();
315  }
316  } catch(const std::runtime_error& e) {
317  gui2::show_error_message(e.what());
318  }
319  });
320 
321  if(auto* lang_button = find_widget<button>(this, "language", false, false); lang_button) {
322  const auto& locale = translation::get_effective_locale_info();
323  // Just assume everything is UTF-8 (it should be as long as we're called Wesnoth)
324  // and strip the charset from the Boost locale identifier.
325  const auto& boost_name = boost::algorithm::erase_first_copy(locale.name(), ".UTF-8");
326  const auto& langs = get_languages(true);
327 
328  auto lang_def = std::find_if(langs.begin(), langs.end(), [&](language_def const& lang) {
329  return lang.localename == boost_name;
330  });
331 
332  if(lang_def != langs.end()) {
333  lang_button->set_label(lang_def->language.str());
334  } else if(boost_name == "c" || boost_name == "C") {
335  // HACK: sometimes System Default doesn't match anything on the list. If you fork
336  // Wesnoth and change the neutral language to something other than US English, you
337  // want to change this too.
338  lang_button->set_label("English (US)");
339  } else {
340  // If somehow the locale doesn't match a known translation, use the
341  // locale identifier as a last resort
342  lang_button->set_label(boost_name);
343  }
344  }
345 
346  //
347  // Preferences
348  //
349  register_button(*this, "preferences", hotkey::HOTKEY_PREFERENCES, []() {
350  gui2::dialogs::preferences_dialog::display();
351  });
352 
353  //
354  // Credits
355  //
356  register_button(*this, "credits", hotkey::TITLE_SCREEN__CREDITS, [this]() { set_retval(SHOW_ABOUT); });
357 
358  //
359  // Quit
360  //
361  register_button(*this, "quit", hotkey::HOTKEY_QUIT_TO_DESKTOP, [this]() { set_retval(QUIT_GAME); });
362  // A sanity check, exit immediately if the .cfg file didn't have a "quit" button.
363  find_widget<button>(this, "quit", false, true);
364 
365  //
366  // Debug clock
367  //
368  register_button(*this, "clock", hotkey::HOTKEY_NULL,
369  std::bind(&title_screen::show_debug_clock_window, this));
370 
371  auto clock = find_widget<button>(this, "clock", false, false);
372  if(clock) {
374  }
375 }
376 
378 {
380 }
381 
382 void title_screen::update_tip(const bool previous)
383 {
384  multi_page* tip_pages = find_widget<multi_page>(get_window(), "tips", false, false);
385  if(tip_pages == nullptr) {
386  return;
387  }
388  if(tip_pages->get_page_count() == 0) {
389  return;
390  }
391 
392  int page = tip_pages->get_selected_page();
393  if(previous) {
394  if(page <= 0) {
395  page = tip_pages->get_page_count();
396  }
397  --page;
398  } else {
399  ++page;
400  if(static_cast<unsigned>(page) >= tip_pages->get_page_count()) {
401  page = 0;
402  }
403  }
404 
405  tip_pages->select_page(page);
406 }
407 
409 {
410  assert(show_debug_clock_button);
411 
412  if(debug_clock_) {
413  debug_clock_.reset(nullptr);
414  } else {
415  debug_clock_.reset(new debug_clock());
416  debug_clock_->show(true);
417  }
418 }
419 
421 {
423 
424  std::vector<std::string> options;
425  for(const config &sc : game_config_manager::get()->game_config().child_range("test")) {
426  if(!sc["is_unit_test"].to_bool(false)) {
427  options.emplace_back(sc["id"]);
428  }
429  }
430 
431  std::sort(options.begin(), options.end());
432 
433  gui2::dialogs::simple_item_selector dlg(_("Choose Test Scenario"), "", options);
434  dlg.show();
435 
436  int choice = dlg.selected_index();
437  if(choice >= 0) {
438  game_.set_test(options[choice]);
440  }
441 }
442 
444 {
445  while(true) {
447  dlg.show();
448 
449  if(dlg.get_retval() != gui2::retval::OK) {
450  return;
451  }
452 
453  const auto res = dlg.get_choice();
454 
455  if(res == decltype(dlg)::choice::HOST && preferences::mp_server_warning_disabled() < 2) {
456  if(!gui2::dialogs::mp_host_game_prompt::execute()) {
457  continue;
458  }
459  }
460 
461  switch(res) {
462  case decltype(dlg)::choice::JOIN:
465  break;
466  case decltype(dlg)::choice::CONNECT:
469  break;
470  case decltype(dlg)::choice::HOST:
471  game_.select_mp_server("localhost");
473  break;
474  case decltype(dlg)::choice::LOCAL:
476  break;
477  }
478 
479  return;
480  }
481 }
482 
484 {
485  int current = 0;
486 
487  std::vector<config> cores;
488  for(const config& core : game_config_manager::get()->game_config().child_range("core")) {
489  cores.push_back(core);
490 
491  if(core["id"] == preferences::core_id()) {
492  current = cores.size() - 1;
493  }
494  }
495 
496  gui2::dialogs::core_selection core_dlg(cores, current);
497  if(core_dlg.show()) {
498  const std::string& core_id = cores[core_dlg.get_choice()]["id"];
499 
500  preferences::set_core_id(core_id);
502  }
503 }
504 
505 } // 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:563
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
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:331
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:967
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:325
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:570
void set_enter_disabled(const bool enter_disabled)
Disable the enter key.
Definition: window.hpp:286